カスタムフックスでタプル・オブジェクトどちらを返すべきか?
ReactのCustom HooksはロジックをHooksの中に隠蔽し、コンポーネントが必要とする値あるいは関数だけを提供することができます。そのCustom Hooksとコンポーネントとの接点である、Hooksの返り値は原則なんでも返せます。
もっともわかりやすいのはタプル(≒配列)で返す例。React提供のHooks(useState
やuseReducer
など)もこのかたちをとっています。そのため他のHooksと同じ書き方で統一できるという点がメリットでしょう。
export const useCounter = (initialValue: number) => { const [count, setCount] = useState(initialValue); const countUp = useCallback(() => { setCount((prev) => prev + 1); }, []); return [count, countUp] as const; };
const [count, countUp] = useCounter(0);
一部の記事では返り値と異なる変数名で受け取れることがタプルのメリットという意見もありましたが、正確には以下のオブジェクトのかたちでも再命名することで別変数名で受け取ることは可能です。
export const useCounter = (initialValue: number) => { const [count, setCount] = useState(initialValue); const countUp = useCallback(() => { setCount((prev) => prev + 1); }, []); return { count, countUp }; };
const { count: userCount, countUp: countUpFunc } = useCounter(0);
ですので、再命名可否に関してはタプル・オブジェクトどちらでも良さそうです。ただし、オブジェクトの方が再命名の場合の記述量が若干増えるのがデメリットですね。
とはいえ、Hooksはなんでも返して良い
Hooksの返り値はxxじゃないといけないという決まりはなく、なんでもいいというのが一般的な意見のようです。リファレンスでもいくつか例がありますが、多様な返り値を認めています。
これは単一の文字列を返り値としています。
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
こっちはJSXを返しています。
import React, { useState, useEffect } from 'react'; function FriendListItem(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }