Hooksのalphaがきてやいのやいの言ってるので自分も追っておきますかということで予習。
この記事はReact v16.7.0-alpha時点の仕様と学習メモなので、Stableリリースされた時点でこの記事を参照にするのはよくないです。おそらくAPI変更が行われるはずなので。
ここではこの概要ページ以外のところに書かれているTipsとか詳細挙動とかFAQとかを中心にまとめて書いていく。基本的なことは概要ページで十分把握できると思う。
このエントリのモチベーションとしてはReactHooksの公式ガイドがいまのところ
- 概要はこうです
- それでは詳しくみていきましょう
- 経験豊富な開発者はこう思うでしょう、これについては後述します
- ここではこう書いていますが、これについてはFAQを参照してください
- それではAPIリファレンスです、どうぞ
- だがAPIリファレンスに具体的なことはあまり書いていない
という流れで、全部読まないと体系的に理解できない&情報があっちこっちに偏在しているという状態。
なので覚えておかないとあとあとハマりそうなことはこうして書いてリンク貼っておかないとわからなくなりそう。そういうモチベーションで書いている。
そういうわけであとあと忘れそうなことだけを書く。自分用のFAQともいう。あとコードブロックはほとんど公式ドキュメントからの引用。
useState
setHogeを使うときの挙動
classコンポーネントのthis.setState()
と違って、ステートはマージされるのではなくて置き換えになる。
useStateは浅く疎結に。あらゆるプロパティを1個にまとめないほうがよい
マージではなく置き換えになるので、ひとつ値が変わるたびにprevStateとマージする必要がでる。そもそも1個にまとめなかったらそんなことをしなくてもよいパターンって結構あるよねということらしい。
カスタムフックに置き換えたりするときも値のマージとかやってないから抽出が容易でよかったですねってなるらしい。
useEffect
どう便利?
- classコンポーネント
componentDidMount
,componentDidUpdate
の2箇所に同一のコードを書く必要が出るパターンがある
- ロジックを一箇所にまとめたい欲求がでてくる
- Reactコンポーネントのバグ要因のひとつは
componentDidUpdate
での適切な処理をミスることでもある
- Effect Hook
- ロジックをエフェクト1箇所にまとめられる
- ブロッキングが起こらない
- 基本的に毎レンダリング後に実行される
- クリーンナップ処理ができる
classコンポーネントだとcomponentDidMount
,componentDidUpdate
で副作用処理してると画面の更新をブロッキングするけど、useEffectはブロッキングしないらしい。
クリーンナップ処理
useEffect
で関数を返すと、クリーンナップ実行時にその関数を実行してくれる
- クリーンナップはいつ起こる?
- 2度目のEffect時
useEffect()
が実行されるとき前回のuseEffect()
があれば、まず前回エフェクトのクリーンナップが行われる
useEffect
がレンダリング毎に異なるものが使用されているのはこのため
- コンポーネントのアンマウント時にも実行される
function FriendStatus(props) {
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
useEffect内でリターンした関数がクリーンナップ時に実行される
useEffectのパフォーマンス最適化
基本的に毎レンダリングごとにエフェクトは実行される。でも毎回発火するのは困るとかパフォーマンスがよくないとかそういうのが起こってくる。
classコンポーネントでcomponentDidUpdate
をこう書いていたものが
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
こうなる
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
エフェクトを1回だけ実行したいなら空の配列をわたす
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []);
Hooksのルール
reactjs.org
ここを読む
- トップレベルでの呼び出しのみ
- React Functionsからの呼び出しのみ
という基本ルールがある
ReactはuseStateによる複数のステートをどう認識しているのか
Hooksが呼び出された順番に依存して認識している
条件分岐
useEffect
の条件分岐をしたい場合はuseEffect
内でif文
useEffect(function persistForm() {
if (name !== '') {
localStorage.setItem('formData', name);
}
});
ifブロックの中でuseEffectしてはいけない
ifブロックのなかにuseEffectを入れて条件によってuseEffectの実行が歯抜けになったりすると、Reactは困ってしまう。
なぜなら前述したとおりReactはHooksの呼び出された順番に依存して認識しているので、歯抜けになると順番が破綻してバグってしまう。
これらルールを守っていくには
eslintのルールプラグインが提供されているのでこれを使うと安心安全
www.npmjs.com
インラインコールバックで再レンダリングが起こる問題を防ぐには
classコンポーネントでthis.onClick = this.onClick.bind(this)
として回避していたようなやつをHooksではどうやるか。
useCallback
かuseReducer
を使う。
useCallback
この形式
function HelloButton(props) {
const handleClick = useCallback(()=> console.log('hello'), [])
return (
<button onClick={handleClick}>Greeting</button>
);
}
useReducer
この形式
function AddButton(props) {
const [todos, dispatch] = useReducer(todosReducer);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
React的にはネストが深いコンポーネントにコールバックをバケツリレーしなくてはいけない場合は、useContext
を使ってdispach関数を子コンポーネントで扱えるようにするパターンがグッドだよねという雰囲気を感じる(https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down)。
そのほか「〜するべき?」「どうすれば〜できる?」などの疑問
reactjs.org
身も蓋もないけどほかはだいたいFAQ読むとだいたい書かれている。
結構想定されるQuestionがしっかりカバーされている印象。
ただAPIの使い方や挙動を知った上でないと理解できないものがほとんどなので、そういう意味でも当エントリの上述部分を把握しておくとFAQが読みやすくなると思っている。
なんだこの雑なエントリは!