✍️
Reactの存在チェックを目的とした条件付きレンダーをスマートに表現するためのアイデア
条件付きレンダーとは
条件付きレンダーとは公式のページでこう書かれています。
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>}
</>
)
}
このように、三項演算子や論理演算子を用いて表現を良くしていますが、この使い方を行っていて次のような悩みが出てきています。
- 条件に使用するターゲットが複数に増えていくと可読性が悪くなる
- ターゲットに対して毎回参考演算子を行うには大変だけど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