Reactとは
Reactとは Facebookが開発をしているUIの作成に特化したオープンソースライブラリーである。 仮想DOMが特徴的で、さまざまなプラットフォームで最適化されたUIの描画を実現する。
Reactの歴史
Reactでコンポーネントを作成するとき、Reactリリース当初は React.createClass
関数を使用しコンポーネントを作成していた。(現在は非推奨)
var HelloComponent = React.createClass({ render: function() { return <p>Hello</p>; } });
しかし ES2015(ES6) が class構文 に対応したことにより、 class構文を使用したクラスコンポーネント時代が到来した。
class Component extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } }
その後、
- コード量の削減
- 可読性の向上
- テストしやすさ
this
を使用しなくても良い
という理由からクラスコンポーネントよりも関数コンポーネントを使用する流れが生まれた。 しかし関数コンポーネントではクラスコンポーネント同等の機能が再現できないため、 高階コンポーネントという手法が使われるようになった。
import React from 'react'; export default function enhance(WrappedComponent) { return class extends React.Component { constructor(props) { super(props); this.state = { open: false, }; } onClickOpen() { this.setState({ open: true }); } onClickClose() { this.setState({ open: false }); } render() { // 機能を追加した新しいコンポーネントを返す return ( <WrappedComponent {...this.props} onClickOpen={this.onClickOpen} onClickClose={this.onClickClose} /> ); } }; }
// 関数コンポーネント function TestComponent(props) { return <div>{props.title}</div> }; // 関数を使って機能を合成 const EnhancedComponent = enhance(BaseComponent); // クリックするとコンソールが出力されるdiv要素が表示される <EnhancedComponent />
上記の例では enhanse
という高階コンポーネントでクラスコンポーネント固有のthis.setState
を内包している onClickOpen
, onClickClose
関数を作成し、引数で渡ってきたコンポーネントにpropsとして渡している。
しかし高階コンポーネントでも結局クラスコンポーネントを使用しており、また
-
props名が高階コンポーネントとラップされるコンポーネントで重複すると使えない
-
refを使った高階コンポーネントをさらにラップすると意図しないrefを取ってしまう
-
ラップコンポーネントからは、親から渡すpropsなのか高階コンポーネントからくるpropsなのか判断できない
-
ネストが増えるためデバッグツールの表示が見ずらい
-
高階コンポーネントと良く一緒に使用される recompose というオープンソースの開発が止まっている(開発者がReactチームに参加した)
という問題点が介在していた。
そして、この流れのあとに開発されたのが今回対象となる Hooksである。
Hooks について
Hooks が React公式で開発され、関数コンポーネントにもクラスコンポーネント同等の機能を高階コンポーネントを使用せずに導入することができるようになった。
Hooks を使用することで今までクラスでしか再現できなかった機能(stateやライフサイクルなど)を実現できるようになり、またこれらの機能を外部に切り出せるため、ロジックがコンポーネントに張り付いてしまう問題を解決することができるようになった。
さらに state
やコンポーネントのライフサイクル のみに特化した機能を外部にHooksとして切り出せる(カスタムフック)ため、
高階コンポーネントよりもコード量を少なく且つ細分化し機能を切り出すことができ、単なるクラスコンポーネント相当の機能を提供するだけではなく、Hooksはそれ以上の恩恵が得られるようになったと考えられる。
公式で取り上げられていた mediumの記事に良い例えが載っていた。
They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!
意訳すると Hookは状態を共有する方法ではなく、状態管理を伴うロジックを共有するものだ。上位コンポーネントから下位コンポーネントへデータを渡す流れを壊したいわけではない。
まとめとして Hooks を使用することによって
- クラスコンポーネントを使用せずにクラスコンポーネント同等機能の実現(高階コンポーネントなど複雑な処理はいらない)
- コンポーネントのコード削減
- テストしやすい
- ネストが増えないためデバッグしやすい
- ロジック再利用性のさらなる向上
というメリットがあり、これらから コード量の削減・可読性の向上・テストのしやすさという恩恵が得られる。
使用例
代表的なHooks(useState, useEffect, useContext)を使い、クラスコンポーネントを関数コンポーネントへ変換する使用例を示す。
useState, useEffectの使用例
初回は読み込み中という文言が表示され、1秒後にメールアドレスとユーザー名を入力するフォームが表示されるコンポーネントを考える。
これをクラスコンポーネントで書くと下記のようになる。
import React from "react"; export default class Form extends React.Component { constructor(props) { super(props); this.state = { email: null, name: null, loading: true, }; } componentDidMount() { setInterval(() => this.setState({ loading: false }), 1000); } onChangeEmail = event => { this.setState({ email: event.target.value }); }; onChangeName = event => { this.setState({ name: event.target.value }); }; render() { const { email, name, loading } = this.state; if (!loading) return <p>読込中</p>; return ( <div> <form> <div> <label>メールアドレス</label> <input onChange={this.onChangeEmail} type="email" /> </div> <div> <label>名前</label> <input onChange={this.onChangeName} type="text" /> </div> </form> <p> {email} : {name} </p> </div> ); } }
これを react-hook を使って関数コンポーネントで書き換えると下記のようになる
import React, { useState, useEffect } from "react"; export default function Form() { const [email, setEmail] = useState(null); const [name, setName] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { setInterval(() => setLoading(false), 1000); }, []); function onChangeEmail(event) { setEmail(event.target.value); } function onChangeName(event) { setName(event.target.value); } if (loading) return <p>読込中</p>; return ( <div> <form> <div> <label>メールアドレス</label> <input onChange={onChangeEmail} type="email" /> </div> <div> <label>名前</label> <input onChange={onChangeName} type="text" /> </div> </form> <p> {email} : {name} </p> </div> ); }
さらに input
から event.target.value
を受け取りセットしている箇所をcustom hook として下記のように切り出すことができる。
function useInput() { const [value, setValue] = useState(null); function onChange(event) { setValue(event.target.value); } return [value, onChange]; }
また1秒後に読み込みが完了するロジックも custom hookとして切り出す。(他コンポーネントでも使っていると仮定する)
function useLoading() { const [loading, setLoading] = useState(true); useEffect(() => { setInterval(() => setLoading(false), 1000); }, []); return loading; }
最終的には下記のようになる。
import React, { useState } from "react"; import useInput from "useInput"; import useLoading from "useLoading"; export default function Form() { const [email, onChangeEmail] = useInput(); const [name, onChangeName] = useInput(); const loading = useLoading(); if (loading) return <p>読込中</p>; return ( <div> <form> <div> <label>メールアドレス</label> <input onChange={onChangeEmail} type="email" /> </div> <div> <label>名前</label> <input onChange={onChangeName} type="text" /> </div> </form> <p> {email} : {name} </p> </div> ); }
コード量が減り、再利用性が向上したことがわかる。
useContextの使用例
Reactには Context
という機能があり、これを使用することであるコンポーネントから、その下に位置するコンポーネントへ props
で値をバケツリレーしなくても、値を参照することができる。
まず値を渡すためにまず独自スコープ(この場合はテーマの値を渡す)となるContextを作成する。
const ThemeContext = React.createContext('light');
その後、親に位置するコンポーネントに
<ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider>
と書くことで Provider
以下にあるコンポーネントでは ThemeContext
の値を参照することができる。
次に子に位置するコンポーネントを作成する。
function Toolbar() { return ( <div> <ThemedButton /> </div> ); }
見ててわかるようにこのコンポーネントでは props
を親から受け取っておらず、また子にも渡していない。
そして孫に位置するコンポーネントを下記のように書く。
class ThemedButton extends React.Component { static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
従来のContextで参照するためには2つの方法があり、一つはクラスコンポーネントの静的変数 contextType
に指定する方法である。
これにより this.context
から Contextの値を取得することができる。m
全体のコードを書くと下記のようになる。
const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Use a Provider to pass the current theme to the tree below. // Any component can read it, no matter how deep it is. // In this example, we're passing "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to // pass the theme down explicitly anymore. function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
また、もう一つ Contextの値を取得する方法 は Consumer
を使用することである。
function ThemedButton() { return <ThemeContext.Consumer>{(theme) => <Button theme={theme} />}</ThemeContext.Consumer>; }
そして今回紹介する useContext
により、新たに Context
から値を取得する方法が加わった。
useContext
を使用すると下記にように書くことができる。
export default function ThemedButton() { const theme= useContext(ThemeContext); return <Button theme={theme} />; }
このように Hookを使用することで、クラスコンポーネントまたはコンポーネントでラップしなくても Context
から値が取得することができる。
また他の Hook と同様にuseContext
もカスタムフックとして外部へ切り出すことができる。
ただし useContext
で使用するコンポーネントの上位には Provider
が書かれている必要がある。
FAQ
公式サイトの FAQを元に疑問に感じられる点をピックアップしまとめる。
どのようなタイミングでカスタムフックを作るべきか?
React公式サイトには下記のように書かれている。
Now that function components can do more, it’s likely that the average function component in your codebase will become longer. This is normal — don’t feel like you have to immediately split it into Hooks. But we also encourage you to start spotting cases where a custom Hook could hide complex logic behind a simple interface, or help untangle a messy component.
意訳すると コンポーネントが肥大化することは普通であり、この事によりすぐに hookとして切り出す必要は無い。 しかし、custom Hookを使うことで複雑なロジックを簡潔なインターフェースで隠すことができ、複雑で絡み合ったコンポーネントを紐解くのに役立つ となる。
以上のことから、まずはcustom hookを使用せずに書いてみてロジックが複雑で分かりづらいと感じたときに、custom hookで簡素化できないかを考える流れで進めるのが良いと考える。それに加え各コンポーネントにて共通化できそうなロジックがあった場合に custom hookとして切り出すのが良いのではないかと考えた。
hookとクラスどちらを使えばよいか?
公式ホームページでは下記のようなことが書かれている。
クラスコンポーネントを全部書き換える必要があるのですか?
いいえ。React からクラスを削除する予定はありません — 我々はみなプロダクトを世に出し続ける必要があり、クラスを書き換えている余裕はありません。新しいコードでフックを試すことをお勧めします。
React本体から 取り除かれた createClass
とは異なり、クラスコンポーネントから削除される予定は無いらしい。
フック、クラスのいずれを使うべきですか、あるいはその両方でしょうか?
準備ができしだい、新しいコンポーネントでフックを試すことをお勧めします。チームの全員の準備が完了し、このドキュメントに馴染んでいることを確かめましょう。(例えばバグを直すなどの理由で)何にせよ書き換える予定の場合を除いては、既存のクラスをフックに書き換えることはお勧めしません。
長期的には、フックが React のコンポーネントを書く際の第一選択となることを期待しています。
また公式サイトで取り上げられていた mediumの記事 には下記のようなことが書かれている。
I think that while there is definitely going to be a short-term cognitive cost to learning them, the end result will be the opposite.
意訳すると
Hookを学ぶことは短期的にはコストになるが、長期で見るとその逆の結果が得られる
つまり新しいコンポーネントでは学習コストがかかったとしても、Hookを使用することが長期的に良いのではないかと考える。
フックはクラスのユースケースのすべてをカバーしていますか?
我々の目標はできるだけ早急にフックがすべてのクラスのユースケースをカバーできるようにすることです。まだ使用頻度の低い getSnapshotBeforeUpdate、getDerivedStateFromError および componentDidCatch についてはフックでの同等物が存在していませんが、すぐに追加する予定です。
フックはレンダープロップや高階コンポーネントを置き換えるものですか?
レンダープロップや高階コンポーネントは、ひとつの子だけをレンダーすることがよくあります。フックはこのようなユースケースを実現するより簡単な手段だと考えています。これらのパターンには引き続き利用すべき場面があります(例えば、バーチャルスクローラーコンポーネントは renderItem プロパティを持つでしょうし、コンテナコンポーネントは自分自身の DOM 構造を有しているでしょう)。とはいえ大抵の場合ではフックで十分であり、フックがツリーのネストを減らすのに役立つでしょう。
以上のことから、
- 新規で始める場合は関数コンポーネントでHookを使うことがおすすめ
- 既存のクラスコンポーネントを書き換える必要は無い
- Hookで再現できないライフサイクルを使いたい場合はクラスコンポーネントを使用(ただし今後 Hookで対応する可能性あり)
- レンダープロップや高階コンポーネントは必要であれば使うが大抵 Hookで対応できる
と考えられる。
react-hooks API一覧
useState
const [state, setState] = useState(initialState);
state
はコンポーネントが更新される対象となる値、 setState
は state
を更新するための関数。
state
の初期値は useState
に渡される第一引数になる。
setState
は state
の値を更新し、その際にコンポーネントも再レンダリングされる。
また、もし setState
に渡される値が既存の state
と同じなのであれば、コンポーネントの再レンダリングはスキップされる。
使用例
下記例は -
ボタンを押すと count
が1小さくなり、 +
ボタンを押すと count
が一つ大きくなる例である。
function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> Count: {count} <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> </> ); }
注意
Component
クラスで使用される setState
は、既存のstateに新しいstateをマージするが、useState
ではこの値が上書きされるため、もし Component
クラス同様の処理を行いたい場合は下記のような処理を加える必要がある。
setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
useEffect
混乱を招くバグが発生する or UIに対して一貫性が持てなくなるため、Mutations
、購読、タイマー、ロギングや他副作用の発生する処理は関数コンポーネント内に書くべきではない。
その代わりに useEffect
を使うべきだ。この関数は 画面が renderされた後に実行される。
純粋なReactの宣言的プログラミングから命令的プログラミングによってしまうことによってどのような影響があるかについて考えよう。
デフォルトではすべてレンダーが完了したときに useEffect
が実行されるが、このタイミングを任意に変えることができる。
副作用のクリア
コンポーネントがページから離れる前に 購読やタイマーなどの処理をクリアする必要が度々ある。
これを実現するため useEffect
は返り値として、クリーンアップ処理を受け取れるようになっている。
useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; });
このような処理を書くことによってメモリーリークを防ぐことができる。
また上記に加えて、もしコンポーネントが複数回レンダーを行う時、 *次の副作用が実行される前に前回の副作用のクリーンアップ処理が実行される。 *
上記の例でいうと、もしレンダーが複数回行われるのであればその都度購読が新規に行われる。
もし何度も副作用を実行させたくないのであれば、次に説明する方法を参照して欲しい。
副作用のタイミング
componentDidMount
や componentDidUpdate
とは違い、 useEffect
に渡される関数はレイアウトやレンダー処理の後に実行される。
殆どの処理はブラウザーへのレンダリングをブロックするべきではないため、この挙動は副作用の共通的な動作として適している。
しかしすべての副作用を遅らせることはできない。
例えばユーザーに不自然なUIの動きを見せないためにDOM操作などの視覚的な処理は同期的に行わなくてはならない。
これらの種類の副作用のためにReactは useLayoutEffect
という Hook
を提供している。
この Hook
はほとんど useEffect
と同じであるが、渡される関数の実行されるタイミングが異なる。
useEffect
に渡した関数は ブラウザーに描画されるまで実行が延期されるが、他の新規レンダーよりも前に実行されることが保証されている。
Reactは常に新しいレンダーが実行される前に、その前のレンダー処理後に実行される副作用がすべて実行される。
副作用の条件
useEffect(didUpdate);
デフォルトで副作用はレンダーが完了した後に呼ばれる。
しかしこの場合まえのセクションで説明した購読のケースのように一度呼び出されるものを想定していた場合、副作用が過多に呼び出されてしまう可能性がある。
更新ごとに購読処理を呼び出すのではなく、ある 値が変わったときのみに処理を呼び出すようにしたい。
これを実現するために useEffect
の第2引数に配列を渡すことができ、この配列の値が変わったときに副作用の処理が呼び出される。
useEffect( () => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source], );
上記の例の場合 props.source
が変わったときにのみ購読処理が走る。
useContext
const value = useContext(MyContext);
React.createContext
で作成した context
オブジェクトを引数として受け取り、その context
オブジェクトの値を返す。
この時受け取る value
は木構造で最も近くにある <MyContext.Provider>
によって定義されたものになる。
最も近くにある <MyContext.Provider>
で value
が更新されたときに、このフックが この新しい value
と共に rerenderのイベントを発火させる。
たとえ、このHookを呼び出している先祖が React.memo
や shouldComponentUpdate
を使っていたとしても、 useContext
を使っているコンポーネントでは rerenderが発生する。
useContext
の引数は context
オブジェクト自身にしなくてはならない。
- OK:
useContext(MyContext)
- NG:
useContext(MyContext.Consumer)
- NG:
useContext(MyContext.Provider)
useContext
を使用しているコンポーネントは、その Context
の value
が変わったときに必ず rerender される。
もしこの rerender処理の頻度が多いと思うのであればメモ化を使用して最適化することができる。
Tip
もし Hook が登場する前に Context
に馴染み深いのであれば、 useContext
は classにおける static contextType = MyContext
または <MyContext.Consumer>
と同義である。
useContext(MyContext)
はただ Context
の中身を読み、変更を購読するだけである。
なので Context
の value
を供給するため、依然として <MyContext.Provider>
は親コンポーネントに書く必要がある。
onst themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
の代替となるフックである。
reducerのタイプ (state, action) => newState
を受け取り、 dispatch関数の対となる現在のstateを返す。もし Reduxを知っているのであれば、この処理についてすでにわかるだろう。
次の state に依存していたり、計算をするのに複雑な処理が絡んでくる場合 useState
よりも useReducer
を使用するのが好ましい。
useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
コールバックの代わりに dispatch を渡すことができるので、子コンポーネントを更新するときにパフォーマンスを最適化するのに useReducer
は役立つ。
下記は useState
の章で説明した例を reducer で書き直したものである。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
初期値の設定
useReducer
では初期値を設定するのに2つの方法があり、開発状況によってどちらかを選ぶのが良い。
もっとも単純なやり方は、初期値を引数として渡す方法である。
const [state, dispatch] = useReducer( reducer, {count: initialCount} );
初期化の遅延
初期値は遅延して作成することもできる。
これを行うために、第3引数に初期化用の関数を渡す必要がある。
初期値は `init(initialArg) のように設定することができる。
これにより初期化にロジックが絡んでいる場合、初期化処理を外部へ切り離すことができる。
また状態をリセットするときもこの関数を使用できるので便利である。
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> );
dispatchから取り出す
If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
現在の値と同様の値を reducerが返すときは子コンポーネントを再レンダリングや副作用が発生しない。(この時stateの比較にReactは Object.is
を使用している。)
しかし Reactは依然として値を取り出す前に特定のコンポーネントを再レンダリングする必要があるかもしれない。
Reactは不必要に木構造を深く見ていかないため、このように不安になる必要はない。
もし計算量の多い処理がレンダリング時に実行されるのであれば、 useMemo
を使用し最適化することができる。
useCallback
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
メモ化されたコールバックを返す。
インラインコールバックと変更を監視する配列を引数として渡す。
useCallback
は変更を監視する配列のうち一つが変わったときに再計算されるメモ化されたコールバックを返す。
これは shouldComponentUpdate
のように不必要なレンダリングを防ぐために参照値が等しいかを見ている子コンポーネントを最適化するときに有用である。
また useCallback(fn, deps)
は useMemo(() => fn, deps)
と等しい。
コールバックに渡す引数と変更を監視する配列の値は等しくなると思うが、これを2度書くのは冗長だと考え将来的にはコンパイラがコールバックの引数を元に配列を作成するようになるらしい。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
メモ化 された値を返す。
作成用の関数と変更をウォッチする配列を引数として渡す。
useMemo
は変更を監視しているしている値の一つが変わったときにのみメモ化された値を再計算する。
この最適化はレンダーごとに計算量の多い処理をすることを防ぐのに役に立つ。
useMemo
に渡した関数はレンダリング時に実行されることを思い出して欲しい。
つまりレンダリング時に実行されてほしくない処理は渡すべきではない。
例えば useEffect
に渡すような副作用は useMemo
にわたすべきではない。
もし配列が渡されていないのであれば、新しい値はレンダーごとに計算される。
意味的な保証(??) ではなく、パフォーマンスの最適化として useMemo
を使用することができる。
将来的には React は上記で説明した幾つかのメモ化された値は敢えて処理を無くし、次のレンダー時に再計算できるようになるかもしれない (たとえばコンポーネントがスクリーンから外れたときにメモリーを回復するなど)
最初は useMemo
を使わずにコードを書き、その後パフォーマンスの最適化が必要であれば useMemo
を使用すべきである。
useRef
const refContainer = useRef(initialValue);
useRef
は引数として渡される initialValue
で初期化された .current
をプロパティとして持つ ref
オブジェクトを返します。
返されるオブジェクトはコンポーネントが破棄されない間生存します。
よくある用途は子コンポーネントに命令的にアクセスすることです。
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
根本的に useRef
は 変更可能な値をプロパティの .current
に持つはこのようなものです。
従来の使い方でも、Reactは <div ref={myRef} />
のようにして ref
オブジェクトを渡すと 、DOMノードが反抗されるたびに.current
プロパティへ対応するノードを格納していました。
しかし useRef()
は ref
属性よりも有用だ。
useRef()
はプレーンな JavaScriptオブジェクトを作成するので、クラス内のインスタンスフィールドを使うように、useRef()
はミュータブルな値を管理しやすい。
useRef()
と引数として {current: ...}
と refオブジェクトを渡す唯一の違いは useRef
はレンダーごとに常に同じrefオブジェクトを使う点である。
useRef
は中身が変更されたとしても通知しないことを覚えておいて欲しい。
つまり .current
プロパティを変更したとしても再レンダリングは発生しない。
もし Reactが ref にDOMを格納したり破棄したときにレンダリングさせたいのであれば、useRef
の代わりにコールバック ref を使ったほうが良い。
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
は refs
を使用するときに親コンポーネントに向けられるインスタンスの値をカスタマイズすることができる。
殆どの場合で refs
を使用した命令的なコードは避けるべきである。
また useImperativeHandle
は forwardRef
と一緒に使用すべきである。
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
この例の場合、 <FancyInput ref={inputRef} />
をレンダーする親コンポネントは inputRef.current.focus()
を呼び出すことができる。
useLayoutEffect
ほとんど useEffect
と同じであるが、 useLayoutEffect
はすべての DOM
が操作された後に同期的に発火されるという点で異なる。
useLayoutEffect
は DOM のレイアウトを参照したり、同期的に再レンダーをするために使用すべきである。
useLayoutEffect
の内部に設定された更新処理は、ブラウザーが描画をする前に同期的に実行される。
また基本的に 視覚的な更新をブロックしてしまうのを防ぎたいのであれば useEffect
を使用することが好ましい。
TODO useLayoutEffect
を入れるケースが有ったほうが良いかも
Tip
もしクラスコンポーネントからマイグレーションする場合、 useLayoutEffect
は componentDidMount
と componentDidUpdate
と同じフェーズで発火することに気をつけるべきである。
しかし、初めは useEffect
を使用し、もし問題があるのであれば useLayoutEffect
を使うアプローチをおすすめする。
もしサーバーサイドレンダリングを使用しているのであれば、JavaScriptがダウンロードされるまで useLayoutEffect
と useEffect
の両方が発火されないということを覚えておくと良い。
なぜかというと、Reactは useLayoutEffect
を含んでいるサーバーサイドレンダリングされるコンポーネントがある時警告をするからである。
これを修正したいのであれば、ロジックを useEffect
へ移すか (もし初回レンダーで必要でないのであれば)
、クライアントがレンダーするまでコンポネントを表示させるのを遅らせるかである(もし useLayoutEffect
が走るまでに HTMLが壊れているように見えてしまうのであれば)。
サーバーサイドレンダリングからの影響をレイアウトを必要とするコンポネントへ与えないために、showChild && <Child />
と書きレンダーをし、 useEffect(() => { setShowChild(true); }, [])
で表示を遅らせる方法が考えられる。
このようにすればクライアント上で JavaScriptが実行される前に UIが崩れるのを防ぐことができる。
useDebugValue
useDebugValue(value)
useDebugValue
は React DevTools
にてカスタムフックを表示するために使用することができる。
例えば Building Your Own Hooks にて紹介されている useFriendStatus
というカスタムフックで考えてみると下記のようになる。
function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); // ... // DevToolsにてこのフック名の隣にラベルを表示する // e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline'); return isOnline; }
Defer formatting debug values
値のフォーマットを揃えることはしばしば処理が増える場合があり、 またその処理はフックの検証を行わないのであれば不要である。
この理由により useDebugValue
はフォーマット用関数を第2引数として任意に受け取れる。
この関数はフックが検証されるときのみに呼び出される。
このフックはデバッグ用の値をパラメーターとして受け取り、フォーマットされたディスプレイ用の値を返すはずだ。
例えば日付データを返すフックは、下記のように引数にフォーマット用の関数を渡すことで toDateString
を不必要に呼び出すことを防ぐことができる。
useDebugValue(date, date => date.toDateString());