✍️

Reactの存在チェックを目的とした条件付きレンダーをスマートに表現するためのアイデア

2022/05/08に公開

条件付きレンダーとは

条件付きレンダーとは公式のページでこう書かれています。

React における条件付きレンダーは JavaScript における条件分岐と同じように動作します。if もしくは条件演算子のような JavaScript 演算子を使用して現在の状態を表す要素を作成すれば、React はそれに一致するように UI を更新します。

悩み

わたしは条件付きレンダーを表現したい時、

interface Props {
  name: string | null;
}

const Component: React.FC<Props> = ({ name }) => {
  return (
    <>
      {name ? <p>{name}</p> : <p>名前未設定</p>}
      {name && <p>{name}</p>}
    </>
  )
}

このように、三項演算子や論理演算子を用いて表現を良くしていますが、この使い方を行っていて次のような悩みが出てきています。

  1. 条件に使用するターゲットが複数に増えていくと可読性が悪くなる
  2. ターゲットに対して毎回参考演算子を行うには大変だけどnull guardしたコンポーネントを毎回作るのも悩む

解決策

今回この二つの悩みはともにtype NonNullableにObjectを対応したNonNullableObjectを作成して解決することを考えました。

type NonNullableObject<T> = { [K in keyof T]: NonNullable<T[K]> };

const isNonNullableObject = <T extends {}>(obj: T): obj is NonNullableObject<T> => {
  return Object.values(obj).every((val) => val != null);
};

1. に対しての解決策

上記の関数を使えば、以下のような引数が沢山必要とするケース

interface Props {
}

const Component: React.FC<Props> = () => {
  const foo = useFetchFoo();
  const bar = useFetchBar();
  return (
    <>
      {foo && bar && <ChildComponent foo={foo} bar={bar} />}
    </>
  )
}

のようなものが

interface Props {
}

const Component: React.FC<Props> = () => {
  const foo = useFetchFoo();
  const bar = useFetchBar();
  const combineObject = { foo, bar };
  const isNonNullable = isNonNullableObject(combineObject);
  return (
    <>
      {isNonNullable && <ChildComponent {...combineObject} />}
    </>
  )
}

というように記述できます。

2. に対しての解決策

先述したisNonNullableObjectの関数を用いて、引数の値が存在する場合のみコンポーネントの中身を表示するケースは以下のように作ります。

interface Props<ChildProps> {
  nullableValues: ChildProps;
  children: (values: NonNullableObject<ChildProps>) => React.ReactNode;
}

const NonNullableGuard = <ChildProps extends {}>({
  nullableValues,
  children
}: Props<ChildProps>) => {
  const isValid = isNonNullable(nullableValues);
  return isValid ? <>{children(nullableValues)}</> : null;
};

NonNullableGuardではnullableValuesという引数に任意の値をセットします。
それの値が全て存在する場合、childrenの関数の引数ではNonNullableな値となった変数を使用してコンポーネントを表示できます。

上記を使用した例は以下になります。

interface Props {
  user: { name: string, age: number } | null;
}

const UserComponent: React.FC<Props> = (props: Props) => {
  return (
    <NonNullableGuard nullableValues={props}>
      {({ user }) => (
        <>
          <p>名前:{user.name}</p>
          <p>年齢:{user.age}</p>
        </>
      )}
    </NonNullableGuard>
  );
};

このようにNonNullableGuardの子の要素はuserが存在する時にしか呼び出されないのでuserのnullチェックが不要になります。

Discussion