hookについて①
hookとは
関数コンポーネント内でstateやライフサイクルなどのReactの機能を接続
する為の関数。
useState
関数コンポーネント内でローカルなstateを使用するための関数。
引数にはstateの初期値を渡す。this.setState
と違い、オブジェクトである必要はない。
// 関数コンポーネント import React, { useState } from 'react'; // hookを使用するにはuseStateをインポートする function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } // クラスコンポーネント class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
また[count, setCount]
のように配列の分割代入構文を使用することで、useState
を呼び出して宣言した
state変数に異なる名前をつける事ができる。
上記ではcountがゲッター, setCountがセッターみたいな感じ。
1つのコンポーネント内で複数のステートフックを使用できる
function ExampleWithManyStates() { const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); // ... }
stateの呼び出し
クラスではthis.state.count
を使用する。
<p>You clicked {this.state.count} times</p>
関数ではcount
を使用する。
<p>You clicked {count} times</p>
stateの更新
クラスではthis.setState()
を呼び出すことでステートを更新する。
<button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button>
関数では、setCount
を変数として受け取っているのでthis
は不要
<button onClick={() => setCount(count + 1)}> Click me </button>
副作用(side-effect)フック
副作用とはデータの取得、購読(subscription)の設定、コンポーネント内のDOMの手動での変更などのこと。
Reactのライフサイクル(componentDidMount, componentDidUpdate, componentWillUnmount)がまとまったもの
だと考えられる
クリーンアップを必要としない副作用
ネットワークリクエストの送信、手動でのDOM改変、ログの記録などのコードが実行された後、すぐに忘れても良い
のもの。
クラスコンポーネントの場合
componentDidMount
, componentDidUpdate
に記載する。
毎回のrender後にマウント直後、更新後に関係なく副作用を起こしたいが、それを可能にする関数はクラスコンポーネントには
存在しない。そのため、同じコードを2回書く必要がある
。
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> ); } }
フックを使用した場合
render後に何らかの処理をしてほしい
ということをReactに伝える。
そのため、DOMの更新後に副作用を呼び出してくれる。
デフォルトでは初回のrenderと毎回の更新時
に呼び出される。
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> ); }
クリーンアップを必要とする場合
何らかの外部データソースへの購読をセットアップする
場合、メモリリークが発生しないようにクリーンアップが必要。
クラスコンポーネントの場合
典型的にはデータの購読をcomponentDidMount
で行い、クリーンアップをcomponentWillUnmoun
で行う。
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'; } }
ライフルメソッドを使用した場合、概念上は同一の副作用に関連していても、それらを分割して書かないといけない。
フックの場合
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 function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
useEffect()はクリーンアップするための関数を返すことができる。これによりライフルメソッドと違い、同一の関数内で 宣言することができる。