筋肉チョットデキル

アリババのカスタムフックコードリーディング~useToggle編~

2020年07月15日

ReactHooks

アリババのカスタムフックをコードリーディング

React の理解を深めるために、アリババのカスタムフックのコードを読んでいこうと思います!

カスタムフックとはuseから始まりほかのフックを呼び出せる関数です。
自分独自のフックを作成することで、コンポーネントから React のロジックを抽出して再利用可能な関数を作ることが可能です!

OSS として多くのカスタムフックが公開されているなかでアリババを選んだ理由は、
わかりやすいドキュメントが用意されていること、有名企業であること、GitHub の Star 数が多いことがあります。

useToggle フック

useToggleは状態を2つの値で切り替えるhooksです。

API

const [state, { toggle, setLeft, setRight }] = useToggle(
defaultValue?: boolean,
);
const [state, { toggle, setLeft, setRight }] = useToggle(
defaultValue: any = false,
reverseValue?: any,
);

引数

PropertyDescriptionTypeDefault
defaultValueデフォルト値を設定number | string | boolean | undefinedfalse
reverseValueデフォルト値ではない値を設定number | string | boolean | undefined-

戻り値

PropertyDescriptionType
statestate の値-
actionsstate の値を更新する関数たちobject

戻り値の関数たち

PropertyDescriptionType
togglestate の値を置き換える(state?: any) => void
setLeftstate の値をデフォルトにセットする() => void
setRightstate の値をデフォルトではない方にセットする() => void

Usage

Effects:Hello

function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle('Hello', 'World');
return (
<div>
<p>Effects:{state}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={() => toggle('Hello')}>Toggle Hello</Button>
<Button onClick={() => toggle('World')}>Toggle World</Button>
<Button onClick={setLeft}>Set Hello</Button>
<Button onClick={setRight}>Set World</Button>
</Stack>
</div>
);
}

🥰

使い所多そう!使い回せるので便利!!

Let’s コードリーディング!!!!

useToggleがどんなカスタムフックか分かったので、どんな実装になっているかコードを読んでいきます!

🙌

ここからが本命!実装を読めば自分でも良いカスタムフックを作れちゃうはず!

全体像

hooks/blob/master/packages/hooks/src/useToggle/index.ts
import { useState, useMemo } from 'react';
type IState = string | number | boolean | undefined;
export interface Actions<T = IState> {
setLeft: () => void;
setRight: () => void;
toggle: (value?: T) => void;
}
function useToggle<T = boolean | undefined>(): [boolean, Actions<T>];
function useToggle<T = IState>(defaultValue: T): [T, Actions<T>];
function useToggle<T = IState, U = IState>(
defaultValue: T,
reverseValue: U,
): [T | U, Actions<T | U>];
function useToggle<D extends IState = IState, R extends IState = IState>(
defaultValue: D = false as D,
reverseValue?: R,
) {
const [state, setState] = useState<D | R>(defaultValue);
const reverseValueOrigin = useMemo(
() => (reverseValue === undefined ? !defaultValue : reverseValue) as D | R,
[reverseValue],
);
const actions = useMemo(() => {
// 切换返回值
const toggle = (value?: D | R) => {
// 强制返回状态值,适用于点击操作
if (value !== undefined) {
setState(value);
return;
}
setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
};
// 设置默认值
const setLeft = () => setState(defaultValue);
// 设置取反值
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
setLeft,
setRight,
};
}, [setState]);
return [state, actions];
}
export default useToggle;
GitHub

useToggle の戻り値

まず、useToggleの戻り値を確認する。

return [state, actions];

stateactionsを配列で返している 👀

useToggle の戻り値 state

つぎはstateを見てみる。

const [state, setState] = (useState < D) | (R > defaultValue);

defaultValue を初期値で状態をもっているのか 👀

useToggle の戻り値 actions

つぎはactionsを見てみる。

const actions = useMemo(() => {
const toggle = (value?: D | R) => {
if (value !== undefined) {
setState(value);
return;
}
setState(s => (s === defaultValue ? reverseValueOrigin : defaultValue));
};
const setLeft = () => setState(defaultValue);
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
setLeft,
setRight,
};
}, [setState]);

メモ化されたオブジェクトを返していて、setStateが変更されたときに再評価される 👀
オブジェクトの値はそれぞれ関数なのでそれらを見ていく。

useToggle の戻り値 actions の 1 つ toggle 関数
toggle
const toggle = (value?: D | R) => {
if (value !== undefined) {
setState(value);
return;
}
setState(s => (s === defaultValue ? reverseValueOrigin : defaultValue));
};

toggleは引数をうけとれば、それをsetStateにわたす 👀

引数をうけとらなければ、
statedefaultValueならreverseValueOriginを、
defaultValueでなければdefaultValueを、 setStateにわたす 👀

reverseValueOriginというのは、

reverseValueOrigin
const reverseValueOrigin = useMemo(
() => (reverseValue === undefined ? !defaultValue : reverseValue) as D | R,
[reverseValue],
);

メモ化された値で、reverseValueが変更されると再評価される 👀
(reverseValueはuseToggle()の第二引数)
reverseValue
undefinedなら!defaultValueを返すし
undefinedでなければreverseValueを返す 👀


つまりこんな感じかな?
toggle()で引数に渡した「Hello」と「World」をトグルする。
toggle('Macho')みたいに引数渡すとその値にstateが変わる。

Effects:Hello

useToggleに第二引数を渡した場合
function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle('Hello', 'World');
return (
<div>
<p>Effects:{state}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={() => toggle('Macho')}>Toggle Macho</Button>
<Button onClick={() => toggle('Hello')}>Toggle Hello</Button>
<Button onClick={() => toggle('World')}>Toggle World</Button>
</Stack>
</div>
);
}

Effects:Hello

useToggleに第二引数を渡さなかった場合
function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle('Hello');
return (
<div>
<p>Effects:{state}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={() => toggle('Macho')}>Toggle Macho</Button>
<Button onClick={() => toggle('Hello')}>Toggle Hello</Button>
<Button onClick={() => toggle('World')}>Toggle World</Button>
</Stack>
</div>
);
}

Effects:Hello

useToggleに引数を渡さなかった場合
function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle();
return (
<div>
<p>Effects:{state ? 'World' : 'Hello'}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={() => toggle(true)}>Toggle Hello</Button>
<Button onClick={() => toggle(false)}>Toggle World</Button>
</Stack>
</div>
);
}
useToggle の戻り値 actions の 1 つ setLeft 関数
const setLeft = () => setState(defaultValue);

これはdefaultValuesetStateに渡す関数 👀

Effects:Hello

function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle('Hello', 'World');
return (
<div>
<p>Effects:{state}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={setLeft}>Set Hello</Button>
</Stack>
</div>
);
}
useToggle の戻り値 actions の 1 つ setRight 関数
const setRight = () => setState(reverseValueOrigin);

これは先述のreverseValueOriginsetStateに渡す関数 👀

Effects:Hello

function Demo() {
const [state, { toggle, setLeft, setRight }] = useToggle('Hello', 'World');
return (
<div>
<p>Effects:{state}</p>
<Stack isInline spacing='2'>
<Button onClick={() => toggle()}>Toggle</Button>
<Button onClick={setRight}>Set World</Button>
</Stack>
</div>
);
}

まとめ

このフックスはいろんな用途に対応できるように汎用的に作られていて勉強になる!
ただはじめは汎用的でなくても、シンプルなロジックでも共通化するのは簡単そう!

ほかのフックスも読んで学ぶ!!


岡本 侑貴@筋肉チョットデキル

筋肉バカ。筋トレしてコード書いて、毎日幸せに生きてる。ボディビルにドハマリ。2021年東京オープンボディビル選手権で最高の身体に仕上げてつよつよエンジニアになる!!

Made with Gatsby& ChakraUi

Yuuki Okamoto • 2020