React Hooksを学ぶ
どうも、てちこまです。
今度iOSとAndroid両対応のアプリを作ることになり色々デザイン設計やら要件定義、フレームワーク選定なりしていたのですが遂に実装段階まで来ました。
ということで今回はReact Nativeをやっていこうと思います。チームメンバーから
「記法はReact Hooksでよろしく」と言われたので学びます。
※当方ReactとReactNativeに関しては若干かじったことがある程度の知識はあります。ありません。
...とりあえず毎度おなじみ公式Docsの触りの部分は読んできました。
ざっくり理解したこと
- 今までの記法(class)も利用できる←今回は使用しない
- hookをclass内で使うことはできないのでごっちゃにしてはいけない。
- componentDidMount,componentDidUpdate,componentWillUnmountはuseEffectに統合された!(Wao!)
特に3のuseEffectが衝撃的でしたね。奴ら消滅してるとは...今までのライフサイクルが使えるんだから消滅ではないですね汗
1年前ぐらいに、Mount,Update,Unmount,stateで四苦八苦した記憶が蘇りますww
ではライフサイクルはどうなったのでしょうか?公式のコードから確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } } |
こちらが今までの書き方ですね。
そして。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } |
下がReactHooksで上を書き直したものです。
コードとしてはbuttonをクリックするとtitleのカウントを増やすというシンプルなものです。
上下比較すると一目瞭然でDidMountとDidUpdateがuseEffectにまとめられています。
useEffectはrender(DOM更新)されるたびに走るので似たような(厳密には違う)動きをするというわけです。
しかしそれだけだとWillUnmountでこれまで行ってきたイベントの解除ができないので
定期的に実行される外部処理(一定時間置きに外部データ取得)などは止めることができません。
そこでuseEffectには以下の代替機能が備わっています。
classの記法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class FriendStatus extends React.Component { constructor(props) { super(props); this.state = { isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } render() { if (this.state.isOnline === null) { return 'Loading...'; } return this.state.isOnline ? 'Online' : 'Offline'; } } |
Hooksの記法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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); // 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'; } |
これは一定時間おきにフレンドのオンライン状況を確認することができるChatAPIモジュールを利用することを想定したコードです。
useEffect内でcleanupという関数をreturnしているのを確認できると思います。
この記述で次のrenderが走る(次のEffectが実行される)前に自動的にreturnの関数が実行される(cleanupされる)仕組みになっています。
ではrender毎になぜcleanupされるのか?
これは従来のcomponentDidUpdateで行っていた更新処理の書き忘れによるバグの防止らしいです。
上記のオンライン状況を定期的に確認するコードですがフレンドidを指定してその状態を取得しようとしているのが
分かると思います。
仮にcomponentの表示中にフレンドidが変更された場合、DidUpdateで一度購読解除して新しいidで定期購読を開始しないと
モジュールのクラッシュ要因になります。
Effectではrender毎にcleanupされることでidが変更される(再render)と勝手に前のEffectは終了し
こちら側で何か考えることなく新しいEffectが立ち上がるといったわけです。
ただそのまま利用すると従来と同じくすべての再renderで更新が掛かってしまうのでuseEffectのオプションで
第2引数に配列を...この先は公式Docと有能な記事でよろしくw
感想
まだ実使用に耐えうる理解ではないですが今回公式Docを読むことでざっくりどんなフローで書いていくかは把握できました。(今までのソースとの比較で)
他にもuseContextとかあるみたいですがそれは書きながら確認していこうと思います。
それでは!