🧭【React】関数型プログラミングを実践する上での、条件分岐の俺的ベストプラクティス2022/10/09に公開2022/10/1110件JavaScriptReactTypeScriptfrontendtechDiscussionkage10202022/10/11オプショナルチェーン(?.)と同じES2020で追加されたnull合体演算子を使うともう少しコンパクトに書けます. const userName = user?.name ?? 'ユーザーはいません'; // user.name が null または undefined のときのみ代入される const genderColor = (user?.gender ?? '') && (user.gender === '男性' ? 'blue' : 'pink') // 複雑なので記事のようにuser.genderの存在判定を先にした方がよさそうです. https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator 基本この辺の演算子は可読性とのトレードオフですが,Reactを書いていると手続き型や行数が増えることを避けがちですね. Ita2022/10/11コメントありがとうございます! そうですね。null合体演算子とか、場合によっては||等のショートサーキット評価もありなのかなと思います。 基本的に、可読性を損なわずに1-2行程度で書けるのであればこういった演算子を用いる方向で考えています。 返信を追加クロパンダ2022/10/12に更新説明変数を足すこと自体は賛成ですが、userNameの導入には反対です。userNameに"ユーザーはいません"というフォールバックが入ることを知らずに <p>ユーザー名: {userName}</p> だけ見たときに挙動を勘違いしうるからです。userNameという変数名をやめるか、単に user?.name ?? 'ユーザーはいません' と書き下した方が誤解が生じづらいです。 他の例も賛同しかねます。例えば「条件分岐が階層になっている場合や、3択以上あるとき」の例ですが、if(!user) return <p>ユーザー名: ユーザーはいません</p> という early return があれば narrowing も効いてかなり読みやすくなるはずです。<p className={genderColor()}>ユーザー名:{userName}</p> と書かれるといちいち genderColor の実装と userName の中身を調べないと動作を読み取れないので、あまり上手くない抽象化だと思います(僕ならレビューで弾きます) Ita2022/10/16に更新ご指摘ありがとうございます! そうですね、例として挙げたものが悪かった気がします。 説明変数を定義してあげて可読性を高める、という趣旨で書いていました。 userNameという変数名はご指摘の通りふさわしくないと思うので修正いたします。 (上記の例、「ユーザーがいないときは『ユーザーはいません』という文字列を出力するという例を変更するかもしれません) 2つ目の以下の指摘は僕も書いている途中に気づきました。 if(!user) return <p>ユーザー名: ユーザーはいません</p> という early return があれば narrowing も効いてかなり読みやすくなるはずです。 しかし、「UIの変更はしない」「関数内で条件分岐させてあげた方がいい」例として見せたかったので無理矢理このようにした次第です。(FC内での早期リターンは3つ目の例として出したかったというエゴです。) 多分これも例が悪く、一つ前の例から引き継ぎの形で書いたことが原因のように思います。 2つ目に関してはもっといい例を示したく思います。(2つ目の例が極端に少ないorないようなら内容自体見つめ直そうと思います。) 返信を追加nuko_suke_dev2022/10/12良い記事ですね! React で条件分岐は私もよく考えされられます! 本筋とちょっと外れますが、コード例にある getSpecificUser は単純に Promise を返すだけなので async と await はいらないと思いました! // 例にある関数 const getSpecificUser = async () => { const specificUser = await getUser(userId); return specificUser; }; // こっちの方が簡潔 const getSpecificUser = () => getUser(userId); Ita2022/10/12ご指摘ありがとうございます! 確かにそちらの方がベターですね! 返信を追加A Kid2022/10/12三項演算子って、入れ子にせずに同じ方向に延ばしていく分には、3択以上になっても実は可読性が下がらないと思いますね。 const userName = user == null ? '' : user.gender === '男性' ? 'blue' : user.gender === '女性' ? 'pink' : 'white'; 返信を追加takezoux22022/10/12ts-pattern を採用すると、かなりスッキリ分岐を書けます。 なんなら、条件分岐ではなくパターンマッチングにできます。 返信を追加いわもとたかあき2022/10/13に更新こっちの方が良いというより、説得難しいっていう感想です。 三項演算子が許可されてるなら、単純な比較と値を返すだけならif ~ returnを1行で書くことも許されるかと。。。 例示されたコードはきれいだけど、行数の差が大きすぎて、PRなどで「ネストした三項演算子は読みづらい」って指摘したときに「なれれば1行の方が読みやすい」って言われそうだなぁと思いました。 const genderColor = () => { if (!user) return ''; if (user.gender === '男性') return 'blue'; return 'pink'; }; コーディングに対して合意を得るのって難しいですよねぇ。 返信を追加nap52023/03/09 条件分岐により別のUIを出力したいとき ts-patternでデモ作ってみました。 https://codesandbox.io/p/sandbox/blissful-tu-bs6pjm?file=%2Fsrc%2Ffeatures%2Ftodo%2Fcomponents%2FTodos.tsx /todosページがデモになります。 const Todos = () => { const { neatLabelName } = useFormatter() const { data, error, refetch } = useListUpTodoHook() const { decidePageState } = useDecidePageState() const renderContent = () => { return match(decidePageState(data, error)) .with('error', () => { return ( <TodoLayout> <NiceButton type='button' labelName={neatLabelName(data, error)} onClick={() => { queryClient.removeQueries([TODO_KEY]) refetch() }} /> <Spacer /> <ShowMe data={error?.response?.data} /> </TodoLayout> ) }) .with('loading', () => { return ( <TodoLayout> <Loading /> </TodoLayout> ) }) .with('success', () => { const neatData = safeParseTodosData(data) return ( <TodoLayout> <NiceButton type='button' labelName={neatLabelName(neatData, error, 'Latest Refresh')} onClick={() => { queryClient.removeQueries([TODO_KEY]) refetch() }} /> <Spacer /> <div className='flex items-center flex-col justify-center gap-4'> {neatData.map((item, index) => { return ( <div key={index} className='shadow-bebop rounded-2xl p-4'> <h1>{item.title}</h1> <p>{item.body}</p> <b>{`u${item.userId}`}</b> <span>{`#${item.id}`}</span> </div> ) })} </div> </TodoLayout> ) }) .run() } return <>{renderContent()}</> } 簡単ですが、以上です。 返信を追加
kage10202022/10/11オプショナルチェーン(?.)と同じES2020で追加されたnull合体演算子を使うともう少しコンパクトに書けます. const userName = user?.name ?? 'ユーザーはいません'; // user.name が null または undefined のときのみ代入される const genderColor = (user?.gender ?? '') && (user.gender === '男性' ? 'blue' : 'pink') // 複雑なので記事のようにuser.genderの存在判定を先にした方がよさそうです. https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator 基本この辺の演算子は可読性とのトレードオフですが,Reactを書いていると手続き型や行数が増えることを避けがちですね. Ita2022/10/11コメントありがとうございます! そうですね。null合体演算子とか、場合によっては||等のショートサーキット評価もありなのかなと思います。 基本的に、可読性を損なわずに1-2行程度で書けるのであればこういった演算子を用いる方向で考えています。 返信を追加
Ita2022/10/11コメントありがとうございます! そうですね。null合体演算子とか、場合によっては||等のショートサーキット評価もありなのかなと思います。 基本的に、可読性を損なわずに1-2行程度で書けるのであればこういった演算子を用いる方向で考えています。
クロパンダ2022/10/12に更新説明変数を足すこと自体は賛成ですが、userNameの導入には反対です。userNameに"ユーザーはいません"というフォールバックが入ることを知らずに <p>ユーザー名: {userName}</p> だけ見たときに挙動を勘違いしうるからです。userNameという変数名をやめるか、単に user?.name ?? 'ユーザーはいません' と書き下した方が誤解が生じづらいです。 他の例も賛同しかねます。例えば「条件分岐が階層になっている場合や、3択以上あるとき」の例ですが、if(!user) return <p>ユーザー名: ユーザーはいません</p> という early return があれば narrowing も効いてかなり読みやすくなるはずです。<p className={genderColor()}>ユーザー名:{userName}</p> と書かれるといちいち genderColor の実装と userName の中身を調べないと動作を読み取れないので、あまり上手くない抽象化だと思います(僕ならレビューで弾きます) Ita2022/10/16に更新ご指摘ありがとうございます! そうですね、例として挙げたものが悪かった気がします。 説明変数を定義してあげて可読性を高める、という趣旨で書いていました。 userNameという変数名はご指摘の通りふさわしくないと思うので修正いたします。 (上記の例、「ユーザーがいないときは『ユーザーはいません』という文字列を出力するという例を変更するかもしれません) 2つ目の以下の指摘は僕も書いている途中に気づきました。 if(!user) return <p>ユーザー名: ユーザーはいません</p> という early return があれば narrowing も効いてかなり読みやすくなるはずです。 しかし、「UIの変更はしない」「関数内で条件分岐させてあげた方がいい」例として見せたかったので無理矢理このようにした次第です。(FC内での早期リターンは3つ目の例として出したかったというエゴです。) 多分これも例が悪く、一つ前の例から引き継ぎの形で書いたことが原因のように思います。 2つ目に関してはもっといい例を示したく思います。(2つ目の例が極端に少ないorないようなら内容自体見つめ直そうと思います。) 返信を追加
Ita2022/10/16に更新ご指摘ありがとうございます! そうですね、例として挙げたものが悪かった気がします。 説明変数を定義してあげて可読性を高める、という趣旨で書いていました。 userNameという変数名はご指摘の通りふさわしくないと思うので修正いたします。 (上記の例、「ユーザーがいないときは『ユーザーはいません』という文字列を出力するという例を変更するかもしれません) 2つ目の以下の指摘は僕も書いている途中に気づきました。 if(!user) return <p>ユーザー名: ユーザーはいません</p> という early return があれば narrowing も効いてかなり読みやすくなるはずです。 しかし、「UIの変更はしない」「関数内で条件分岐させてあげた方がいい」例として見せたかったので無理矢理このようにした次第です。(FC内での早期リターンは3つ目の例として出したかったというエゴです。) 多分これも例が悪く、一つ前の例から引き継ぎの形で書いたことが原因のように思います。 2つ目に関してはもっといい例を示したく思います。(2つ目の例が極端に少ないorないようなら内容自体見つめ直そうと思います。)
nuko_suke_dev2022/10/12良い記事ですね! React で条件分岐は私もよく考えされられます! 本筋とちょっと外れますが、コード例にある getSpecificUser は単純に Promise を返すだけなので async と await はいらないと思いました! // 例にある関数 const getSpecificUser = async () => { const specificUser = await getUser(userId); return specificUser; }; // こっちの方が簡潔 const getSpecificUser = () => getUser(userId); Ita2022/10/12ご指摘ありがとうございます! 確かにそちらの方がベターですね! 返信を追加
A Kid2022/10/12三項演算子って、入れ子にせずに同じ方向に延ばしていく分には、3択以上になっても実は可読性が下がらないと思いますね。 const userName = user == null ? '' : user.gender === '男性' ? 'blue' : user.gender === '女性' ? 'pink' : 'white'; 返信を追加
いわもとたかあき2022/10/13に更新こっちの方が良いというより、説得難しいっていう感想です。 三項演算子が許可されてるなら、単純な比較と値を返すだけならif ~ returnを1行で書くことも許されるかと。。。 例示されたコードはきれいだけど、行数の差が大きすぎて、PRなどで「ネストした三項演算子は読みづらい」って指摘したときに「なれれば1行の方が読みやすい」って言われそうだなぁと思いました。 const genderColor = () => { if (!user) return ''; if (user.gender === '男性') return 'blue'; return 'pink'; }; コーディングに対して合意を得るのって難しいですよねぇ。 返信を追加
nap52023/03/09 条件分岐により別のUIを出力したいとき ts-patternでデモ作ってみました。 https://codesandbox.io/p/sandbox/blissful-tu-bs6pjm?file=%2Fsrc%2Ffeatures%2Ftodo%2Fcomponents%2FTodos.tsx /todosページがデモになります。 const Todos = () => { const { neatLabelName } = useFormatter() const { data, error, refetch } = useListUpTodoHook() const { decidePageState } = useDecidePageState() const renderContent = () => { return match(decidePageState(data, error)) .with('error', () => { return ( <TodoLayout> <NiceButton type='button' labelName={neatLabelName(data, error)} onClick={() => { queryClient.removeQueries([TODO_KEY]) refetch() }} /> <Spacer /> <ShowMe data={error?.response?.data} /> </TodoLayout> ) }) .with('loading', () => { return ( <TodoLayout> <Loading /> </TodoLayout> ) }) .with('success', () => { const neatData = safeParseTodosData(data) return ( <TodoLayout> <NiceButton type='button' labelName={neatLabelName(neatData, error, 'Latest Refresh')} onClick={() => { queryClient.removeQueries([TODO_KEY]) refetch() }} /> <Spacer /> <div className='flex items-center flex-col justify-center gap-4'> {neatData.map((item, index) => { return ( <div key={index} className='shadow-bebop rounded-2xl p-4'> <h1>{item.title}</h1> <p>{item.body}</p> <b>{`u${item.userId}`}</b> <span>{`#${item.id}`}</span> </div> ) })} </div> </TodoLayout> ) }) .run() } return <>{renderContent()}</> } 簡単ですが、以上です。 返信を追加
Discussion
オプショナルチェーン(?.)と同じES2020で追加されたnull合体演算子を使うともう少しコンパクトに書けます.
基本この辺の演算子は可読性とのトレードオフですが,Reactを書いていると手続き型や行数が増えることを避けがちですね.
コメントありがとうございます!
そうですね。null合体演算子とか、場合によっては
||等のショートサーキット評価もありなのかなと思います。基本的に、可読性を損なわずに1-2行程度で書けるのであればこういった演算子を用いる方向で考えています。
説明変数を足すこと自体は賛成ですが、userNameの導入には反対です。userNameに"ユーザーはいません"というフォールバックが入ることを知らずに
<p>ユーザー名: {userName}</p>だけ見たときに挙動を勘違いしうるからです。userNameという変数名をやめるか、単にuser?.name ?? 'ユーザーはいません'と書き下した方が誤解が生じづらいです。他の例も賛同しかねます。例えば「条件分岐が階層になっている場合や、3択以上あるとき」の例ですが、
if(!user) return <p>ユーザー名: ユーザーはいません</p>という early return があれば narrowing も効いてかなり読みやすくなるはずです。<p className={genderColor()}>ユーザー名:{userName}</p>と書かれるといちいち genderColor の実装と userName の中身を調べないと動作を読み取れないので、あまり上手くない抽象化だと思います(僕ならレビューで弾きます)ご指摘ありがとうございます!
そうですね、例として挙げたものが悪かった気がします。
説明変数を定義してあげて可読性を高める、という趣旨で書いていました。
userNameという変数名はご指摘の通りふさわしくないと思うので修正いたします。(上記の例、「ユーザーがいないときは『ユーザーはいません』という文字列を出力するという例を変更するかもしれません)
2つ目の以下の指摘は僕も書いている途中に気づきました。
しかし、「UIの変更はしない」「関数内で条件分岐させてあげた方がいい」例として見せたかったので無理矢理このようにした次第です。(FC内での早期リターンは3つ目の例として出したかったというエゴです。)
多分これも例が悪く、一つ前の例から引き継ぎの形で書いたことが原因のように思います。
2つ目に関してはもっといい例を示したく思います。(2つ目の例が極端に少ないorないようなら内容自体見つめ直そうと思います。)
良い記事ですね!
React で条件分岐は私もよく考えされられます!
本筋とちょっと外れますが、コード例にある
getSpecificUserは単純にPromiseを返すだけなのでasyncとawaitはいらないと思いました!ご指摘ありがとうございます!
確かにそちらの方がベターですね!
三項演算子って、入れ子にせずに同じ方向に延ばしていく分には、3択以上になっても実は可読性が下がらないと思いますね。
ts-pattern を採用すると、かなりスッキリ分岐を書けます。
なんなら、条件分岐ではなくパターンマッチングにできます。
こっちの方が良いというより、説得難しいっていう感想です。
三項演算子が許可されてるなら、単純な比較と値を返すだけなら
if ~ returnを1行で書くことも許されるかと。。。例示されたコードはきれいだけど、行数の差が大きすぎて、PRなどで「ネストした三項演算子は読みづらい」って指摘したときに「なれれば1行の方が読みやすい」って言われそうだなぁと思いました。
コーディングに対して合意を得るのって難しいですよねぇ。
ts-patternでデモ作ってみました。
/todosページがデモになります。簡単ですが、以上です。