본문으로 바로가기
728x90

아래 코드 중 어떻게 조건부 렌더링을 사용하고 있었나요?

  {props.title ? (
    <strong className="text-gray-800 font-semibold text-xl">
      {props.title}
    </strong>
  ) : null}
  {props.title ? (
    <strong className="text-gray-800 font-semibold text-xl">
      {props.title}
    </strong>
  ) : undefined}
  {props.title && (
    <strong className="text-gray-800 font-semibold text-xl">
      {props.title}
    </strong>
  )}

간만에 서비스 개발을 하면서 제각각인 코드가 눈에 보였다. @wise님께서는 아래와 같은 리뷰도 남겨주셨는데,

@wise: 삼항연산자를 사용하는 과정에 return null 인 경우 &&로 변경하는게 가독성이 좋을 것 같습니다.

같은 코드라면 가독성이 좋은 &&를 사용하는게 맞아 보였다. 과연 같을까?

 

TL;DR

JSX 조건부 렌더에 &&구분자를 사용하면 알 수 없는 값(NaN 또는 0)이 렌더될 수 있습니다, 또는 렌더링이 중단될 수 있습니다.

const Example = () => {
  return (
    <>
      {0 && <Something />}
      {/* React: 0이 렌더링됨 */}
      {/* React Native: crashes 💥 */}

      {NaN && <Something />}
      {/* React: NaN이 렌더링됨 */}
      {/* React Native: crashes 💥 */}

      {'' && <Something />}
      {/* React: 아무것도 렌더링 되지 않음 */}
      {/* React Native, with React < 18: crashes 💥 */}
    </>
  )
}

해결 방법으로는 두 가지 방법을 제시합니다.

  1. {someValue ? <Something /> : null}
  2. {!!someValue && }

 

잘못된 방법의 예시

const Component = ({ count, title }) => {
  return <div>{count && title}</div>
}
const Component = ({ count }) => {
  return <div>{count && <span>There are {count} results</span>}</div>
}
const Component = ({ elements }) => {
  return <div>{elements.length && <List elements={elements} />}</div>
}
const Component = ({ nestedCollection }) => {
  return (
    <div>
      {nestedCollection.elements.length && <List elements={nestedCollection.elements} />}
    </div>
  )
}
const Component = ({ elements }) => {
  return <div>{elements[0] && <List elements={elements} />}</div>
}
const Component = ({ numberA, numberB }) => {
  return <div>{(numberA || numberB) && <Results>{numberA + numberB}</Results>}</div>
}
// 조건이 부울 값인 경우 이 규칙은 논리식을 eslint에 보고합니다.
// 조건의 유형을 추론할 수 없기 때문입니다.
const Component = ({ someBool }) => {
  return <div>{someBool && <Results>{numberA + numberB}</Results>}</div>
}

 

올바른 방법의 예시

const Component = ({ elements }) => {
  return <div>{elements}</div>
}
// OR 조건은 방법으로 간주되므로 유효한 것으로 간주됩니다.
// 첫 번째 값이 거짓인 경우 일부 대체를 렌더링하고 조건부로 렌더링하지 않습니다.
const Component = ({ customTitle }) => {
  return <div>{customTitle || defaultTitle}</div>
}
const Component = ({ elements }) => {
  return <div>There are {elements.length} elements</div>
}
const Component = ({ elements, count }) => {
  return <div>{!count && 'No results found'}</div>
}
const Component = ({ elements }) => {
  return <div>{!!elements.length && <List elements={elements} />}</div>
}
const Component = ({ elements }) => {
  return <div>{Boolean(elements.length) && <List elements={elements} />}</div>
}
const Component = ({ elements }) => {
  return <div>{elements.length > 0 && <List elements={elements} />}</div>
}
const Component = ({ elements }) => {
  return <div>{elements.length ? <List elements={elements} /> : null}</div>
}
const Component = ({ elements }) => {
  return <div>{elements.length ? <List elements={elements} /> : <EmptyList />}</div>
}

 

규칙 설정하기

ValidStrategies 옵션으로 "coerce"와 "tenary"를 또는 둘 다 ["tenary", 'coerce"]와 같이 줄 수 있습니다.

  • coerce는 조건부 jsx expression을 boolean으로 변환시켜주는 옵션입니다. 
  • tenary는 이진 연산식의 잘못된 값에 대해 null을 반환하는 삼항 표현식으로 변환합니다.

배열로 제공한다면 첫 번째 인자가 autofixing에 사용되므로 순서가 중요합니다.

{
  // ...
  "react/jsx-no-leaked-render": [<enabled>, { "validStrategies": ["ternary", "coerce"] }]
  // ...
}

 

만약 tenary만 줬다면 어떻게 변할지 예시 또한 친절하게 제공하고 있습니다.

// X
const Component = ({ count, title }) => {
  return <div>{count && title}</div>
}

// O
const Component = ({ count, title }) => {
  return <div>{count ? title : null}</div>
}
// X
const Component = ({ count, title }) => {
  return <div>{!!count && title}</div>
}

// O
const Component = ({ count, title, empty }) => {
  return <div>{count ? title : empty}</div>
}

 

coerce만 줬다면 다음과 같이 autofixing을 제공합니다.

// X
const Component = ({ count, title }) => {
  return <div>{count && title}</div>
}

// O
const Component = ({ count, title }) => {
  return <div>{!!count && title}</div>
}
// X
const Component = ({ count, title }) => {
  return <div>{count ? title : null}</div>
}

// O
const Component = ({ count, title, empty }) => {
  return <div>{count ? title : empty}</div>
}

당시에 리뷰에 답변을 달 때는 제가 갖고 있던 eslint 규칙이 { "validStrategies": ["ternary", "coerce"] } 와 같이 제공됐는데 만약 팀에서 가독성을 위해서라고 했을 때 배열의 순서를 바꾸는 방법을 제안했어도 괜찮았을거라는 생각이 드네요.

 

사용하지 않아야 할 때

Typescript와 같은 정적 언어를 사용한다면, 불리언 조건과 관련된 많은 에러나 경고를 타입 검사를 통해 이미 처리할 수 있기 때문에 이럴 땐 규칙을 꺼도 좋다고 합니다. 그러나 여전히 다른 ESLint 규칙은 코드의 가독성이나 일관성을 강화하는 데 도움을 줄 수 있으므로 저는 키겠습니다.

 

참조

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-leaked-render.md