Reactのフックを勉強中、公式ドキュメントを読んだが、
読んでいる間はわかったつもりでも、昼飯から返ってくる頃には忘れていたので、メモを書いて覚えようと思った次第。
書いてる本人はReactはズブの素人なので、ツッコミ等あればコメントお願いします。
フック(hook)とは
元々Reactはコンポーネントをクラスで書いてたのだけど、関数でもかけたりする。
ただし関数でコンポーネントを作るとstateが使えなくなる問題があった。
これを解決するのがフック。
フックを使うことで、関数コンポーネントの中にローカルなstateを作成することができる。
ステートフック
ざっくりした使い方をソースコードを元に解説。
const Calc = () => {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
return (
<div>
<p>{a}, {b}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
</div>
);
}
a
がthis.state.a
に相当。フックの場合はobjectにしなくてもいい。
setA
がthis.setState
に相当。
setA(a + 1)
がthis.setState({a: this.state.a + 1})
に相当する。
これで生成後も更新可能なローカル変数を持つ関数コンポーネントを作成できる。
副作用(Effect)フック
フックにライフサイクルイベントを紐づける。
レンダリング後に処理を実行したい時などに使う。
const Calc = () => {
const [a, setA] = useState(0);
useEffect(() => {
console.log(a);
}, [a]);
const [b, setB] = useState(0);
useEffect(() => {
console.log(b);
}, [b]);
return (
<div>
<p>{a}, {b}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
</div>
);
}
useEffect
が副作用フック。
関数を渡すとレンダリングされるたびに処理を実行してくれる。
第二引数に関数内で使用している変数を配列で渡すと、その値の変更があった場合のみ関数が実行される。
クリーンアップ
APIを叩いてデータを受け渡しするコンポーネントを作った場合、その中のライフサイクルイベントで購読処理がずっと動いているとメモリリークの危険がある。
ので、コンポーネントがなくなった時のライフサイクルイベントを登録する。
いいサンプルが思いつかなかったので公式コピペ。
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
useEffect
に渡す関数の返り値に関数を設定すれば、コンポーネントが破棄された時にその処理が実行される。
上記のコードではuseEffect
の中で購読を開始する処理を実行し、クリーンアップのタイミングで購読を終了する処理を返している。
フックのルール
フックを使用する際には2つのルールがある
- フックを呼び出すのはトップレベルのみ
- フックを呼び出すのはReactの関数内のみ
1. フックを呼び出すのはトップレベルのみ
ループや条件分岐、ネストされた関数の中などには書いてはいけない、というもの。
処理を実行される順番が毎回同じであることを保証するため。
2. フックを呼び出すのはReactの関数内のみ
プレーンなJavaScriptからフックを呼び出してはいけない。
以下のいずれかの方法で呼び出す。
- Reactの関数コンポーネント内から呼び出す。
- カスタムフック内から呼び出す。
カスタムフック
フックは自作できる。
同じフックを別のコンポーネントで使用したい場合がある。
その場合はフックを自作して使いまわせるようにする。
カスタムフックを作成する場合は、関数名の先頭を必ずuse
にする。
const useValue = () => {
const [val, setVal] = useState(0);
useEffect(() => {
console.log(val);
}, [val]);
return [val, setVal];
}
const Calc = () => {
const [a, setA] = useValue();
const [b, setB] = useValue();
return (
<div>
<p>{a}, {b}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
</div>
);
}
よく使うフックAPI
公式が提供しているフック APIでよく使うものをピックアップ。
useCallback
変数の変更を検知して処理を実行する。
第二引数に監視する変数を設定でき、監視している値が変更された時だけ処理が実行される。
返り値はイベントを返す。
const Calc = () => {
const [a, setA] = useValue();
const [b, setB] = useValue();
const handleClickA = useCallback(() => console.log(a, b), [a]);
return (
<div>
<p>{a}, {b}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
<button onClick={handleClickA}>1</button>
</div>
);
}
この場合、bを押しても出力される値に変更はないが、aを押すと押下後のaとbの値を出力する。
useMemo
useCallbackとやってることは同じ。
原則的に戻り値は変数やオブジェクトとする。
const Calc = () => {
// カスタムフック使用例
const [a, setA] = useValue();
const [b, setB] = useValue();
// useCallback: 変数の変更を検知して内部の変数を書き換える
const handleClickA = useCallback(() => console.log('useCallback:', a, b), [a]);
// useMemo: 値の変更を検知して処理を実行し、値を返す
const total = useMemo(() => a + b, [a, b]);
// 以下のようにも書けるが、使い分けのためにこの書き方はしない
// const total = useCallback(a + b, [a, b]);
return (
<div>
<p>{a} + {b} = {total}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
<button onClick={handleClickA}>1</button>
</div>
);
}
useRef
Refsを作れる。
受け渡した値が初期値となる。
const CommentForm = () => {
const ref = useRef();
const onClickComment = () => {
console.log(ref.current.value);
};
return (
<div>
<input ref={ref} type="text" />
<button onClick={onClickComment}>comment</button>
</div>
);
}
サンプルコード
以上を踏まえて自分で動かしたサンプル
https://stackblitz.com/edit/react-pxk6w7
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { render } from 'react-dom';
import './style.css';
// カスタムフック
const useValue = () => {
const [val, setVal] = useState(0);
useEffect(() => console.log(val), [val]);
return [val, setVal];
}
const Calc = () => {
// カスタムフック使用例
const [a, setA] = useValue();
const [b, setB] = useValue();
// useCallback: 変数の変更を検知して内部の変数を書き換える
const handleClickA = useCallback(() => {
c.current = a;
refC.current = b;
console.log('useCallback:', a, b, c, refC);
}, [a]);
// useMemo: 値の変更を検知して処理を実行し、値を返す
const total = useMemo(() => a + b, [a, b]);
// 以下のようにも書けるが、使い分けのためにこの書き方はしない
// const total = useCallback(a + b, [a, b]);
return (
<div>
<p>{a} + {b} = {total}</p>
<button onClick={() => setA(a + 1)}>a</button>
<button onClick={() => setB(b + 1)}>b</button>
<button onClick={handleClickA}>1</button>
</div>
);
}
const CommentForm = () => {
const ref = useRef();
const onClickComment = () => {
console.log(ref.current.value);
};
return (
<div>
<input ref={ref} type="text" />
<button onClick={onClickComment}>comment</button>
</div>
);
}
const App = () => {
return (
<div>
<Calc />
<CommentForm />
</div>
)
}
render(<App />, document.getElementById('root'));
完全に理解した。