この記事は古くなっており、今後更新されません。新しい React 日本語ドキュメントであるja.react.dev をご利用ください。
新しいドキュメントではフックを使って React が学べます。
フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
このページではフックに関するよくある質問にいくつかお答えします。
React バージョン 16.8.0 より、以下においてフックの安定版の実装が含まれています。
フックを利用するには、すべての React のパッケージが 16.8.0 以上である必要があります。例えば React DOM の更新を忘れた場合、フックは動作しません。
React Native はバージョン 0.59 以降でフックをサポートします。
いいえ。React からクラスを削除する予定はありません — 我々はみなプロダクトを世に出し続ける必要があり、クラスを書き換えている余裕はありません。新しいコードでフックを試すことをお勧めします。
フックにより、コンポーネント間で機能を再利用するためのパワフルで表現力の高い手段が得られます。“独自フックの作成”を読めばできることの概要が掴めるでしょう。React のコアチームメンバーによって書かれたこの記事により、フックによって新たにもたらされる可能性についての洞察が得られます。
フックとは、state やライフサイクル、コンテクストや ref といった、あなたが既に知っている React の機能をより直接的に利用できるようにする手段です。React の動作が根本的に変わるようなものではありませんし、コンポーネントや props、トップダウンのデータの流れについての知識はこれまでと同様に重要です。
もちろんフックにはフックなりの学習曲線があります。このドキュメントに足りないことを見つけたらIssue を報告していただければ、お手伝いします。
準備ができしだい、新しいコンポーネントでフックを試すことをお勧めします。チームの全員の準備が完了し、このドキュメントに馴染んでいることを確かめましょう。(例えばバグを直すなどの理由で)何にせよ書き換える予定の場合を除いては、既存のクラスをフックに書き換えることはお勧めしません。
クラスコンポーネントの定義内でフックを使うことはできませんが、クラス型コンポーネントとフックを使った関数コンポーネントとを 1 つのコンポーネントツリー内で混在させることは全く問題ありません。あるコンポーネントがクラスで書かれているかフックを用いた関数で書かれているかというのは、そのコンポーネントの実装の詳細です。長期的には、フックが React のコンポーネントを書く際の第一選択となることを期待しています。
我々の目標はできるだけ早急にフックがすべてのクラスのユースケースをカバーできるようにすることです。まだ使用頻度の低いgetSnapshotBeforeUpdate
、getDerivedStateFromError
およびcomponentDidCatch
についてはフックでの同等物が存在していませんが、すぐに追加する予定です。
レンダープロップや高階コンポーネントは、ひとつの子だけをレンダーすることがよくあります。フックはこのようなユースケースを実現するより簡単な手段だと考えています。これらのパターンには引き続き利用すべき場面があります(例えば、バーチャルスクローラーコンポーネントはrenderItem
プロパティを持つでしょうし、コンテナコンポーネントは自分自身の DOM 構造を有しているでしょう)。とはいえ大抵の場合ではフックで十分であり、フックがツリーのネストを減らすのに役立つでしょう。
connect()
や React Router といった人気の API はフックによりどうなりますか?これまでと同様に全く同じ API を使用し続けることができます。それらは動作し続けます。
React Redux は v7.1.0 よりフック API をサポートしており、useDispatch
やuseSelector
といったフックを提供しています。
React Router は v5.1 よりフック API をサポート しています。
他のライブラリも、将来的にフックをサポートするかもしれません。
フックは静的型付けを念頭に設計されました。フックは関数ですので、高階コンポーネントのようなパターンと比較しても正しく型付けするのは容易です。最新版の Flow と TypeScript における React の型定義には、React のフックについてのサポートが含まれています。
重要なことですが、もしより厳密に型付けしたい場合は、カスタムフックを使うことで React API に何らかの制約を加えることが可能です。React は基本部品を提供しますが、最初から提供されているものと違う方法でそれらを様々に組み合わせることができます。
React の観点から見れば、フックを使ったコンポーネントは単なる普通のコンポーネントです。あなたのテストソリューションが React の内部動作に依存しているのでない場合、フックを使ったコンポーネントのテストのやり方は、あなたが普段コンポーネントをテストしているやり方と変わらないはずです。
補足
テストのレシピ集にコピー・ペーストで使えるたくさんの例が掲載されています。
例えばこのようなカウンタコンポーネントがあるとしましょう:
functionExample(){const[count, setCount]=useState(0);useEffect(()=>{ document.title=`You clicked${count} times`;});return(<div><p>You clicked{count} times</p><buttononClick={()=>setCount(count+1)}> Click me</button></div>);}
これを React DOM を使ってテストします。ブラウザでの挙動と確実に合致させるため、これをレンダーしたり更新したりするコードをReactTestUtils.act()
でラップします:
import Reactfrom'react';import ReactDOMfrom'react-dom/client';import{ act}from'react-dom/test-utils';import Counterfrom'./Counter';let container;beforeEach(()=>{ container= document.createElement('div'); document.body.appendChild(container);});afterEach(()=>{ document.body.removeChild(container); container=null;});it('can render and update a counter',()=>{// Test first render and effectact(()=>{ ReactDOM.createRoot(container).render(<Counter/>);});const button= container.querySelector('button');const label= container.querySelector('p');expect(label.textContent).toBe('You clicked 0 times');expect(document.title).toBe('You clicked 0 times');// Test second render and effectact(()=>{ button.dispatchEvent(newMouseEvent('click',{bubbles:true}));});expect(label.textContent).toBe('You clicked 1 times');expect(document.title).toBe('You clicked 1 times');});
act()
を呼び出すと内部の副作用も処理されます。
カスタムフックをテストしたい場合は、テスト内でコンポーネントを作って中でそのカスタムフックを使うようにしてください。そうすればそのコンポーネントをテストできます。
ボイラープレートを減らすため、エンドユーザが使うのと同じ形でコンポーネントを使ってテストが記述できるように設計されている、React Testing Library の利用をお勧めします。
詳細については、テストのレシピ集をご覧ください。
我々はESLint プラグインを提供しており、これによりフックのルールを強制してバグを減らすことができます。このルールは、use
で始まり大文字が続くような名前の関数はすべてフックであると仮定します。これは不完全な推測手段であり過剰検出があるかもしれないことは認識していますが、エコシステム全体での規約なくしてはフックはうまく動作しません。また名前を長くするとフックを利用したり規約を守ったりしてくれなくなるでしょう。
具体的には、このルールは以下を強制します:
PascalCase
名の関数内(コンポーネントと見なされます)か、あるいは他のuseSomething
式の名前の関数内(カスタムフックと見なされます)にあること。これ以外にも幾つかの推測を行っており、また、バグ検出と過剰検出抑制とのバランスを調整していくなかで、これらは将来的に変わる可能性があります。
constructor
: 関数コンポーネントはコンストラクタを必要としません。state はuseState
を呼び出すときに初期化します。初期 state の計算が高価である場合、useState
に関数を渡すことができます。getDerivedStateFromProps
:レンダー中に更新をスケジューリングします。shouldComponentUpdate
: ページ下部のReact.memo
についての説明を参照してください。render
: これは関数コンポーネントの本体そのものです。componentDidMount
,componentDidUpdate
,componentWillUnmount
: これらのあらゆる組み合わせはuseEffect
フックで表現できます(これやこれのような頻度の低いケースも含め)。getSnapshotBeforeUpdate
、componentDidCatch
およびgetDerivedStateFromError
: フックによる同等物はまだ存在していませんが、近日中に追加される予定です。まずはこちらの小さなデモをご覧ください。フックを使ってデータの取得をする方法について詳しく学ぶにはこちらの記事を参照してください。
はい!useRef()
フックは DOM への参照を保持するためだけにあるのではありません。“ref” オブジェクトは汎用のコンテナであり、そのcurrent
プロパティの値は書き換え可能かつどのような値でも保持することができますので、クラスのインスタンス変数と同様に利用できます。
例えばuseEffect
内から “ref” オブジェクトを書き換えることができます。
functionTimer(){const intervalRef=useRef();useEffect(()=>{const id=setInterval(()=>{// ...}); intervalRef.current= id;return()=>{clearInterval(intervalRef.current);};});// ...}
単にインターバルをセットしたいだけであれば(id
はこの副作用内でローカルでよいので)この ref は必要ないところですが、もしもイベントハンドラ内でインターバルをクリアしたい場合には便利です:
// ...functionhandleCancelClick(){clearInterval(intervalRef.current);}// ...
概念的には、ref はクラスにおけるインスタンス変数と似たものだと考えることができます。遅延初期化をしたい場合を除き、レンダーの最中に ref を書き換えることはしないでください。その代わり、通常 ref はイベントハンドラや副作用内でだけ書き換えるようにしましょう。
これまでクラスを使っていたなら、useState()
を 1 回だけ呼んで、1 つのオブジェクト内にすべての state を入れたくなるかもしれません。そうしたければそのようにすることもできます。以下はマウスの動作を追跡するコンポーネントの例です。位置やサイズの情報を 1 つのローカル state に保持しています。
functionBox(){const[state, setState]=useState({left:0,top:0,width:100,height:100});// ...}
ここで例えば、ユーザがマウスを動かしたときにleft
とtop
を変更したいとしましょう。この際、これらのフィールドを古い state に手動でマージしないといけないことに注意してください:
// ...useEffect(()=>{functionhandleWindowMouseMove(e){// Spreading "...state" ensures we don't "lose" width and heightsetState(state=>({...state,left: e.pageX,top: e.pageY}));}// Note: this implementation is a bit simplified window.addEventListener('mousemove', handleWindowMouseMove);return()=> window.removeEventListener('mousemove', handleWindowMouseMove);},[]);// ...
これは state 変数を更新する時には変数の値が置換されるからです。これは更新されるフィールドがオブジェクトにマージされるというクラスでのthis.setState
の挙動とは異なります。
自動マージがないとつらい場合は、useLegacyState
のようなカスタムフックを書いてオブジェクト型の state の更新をマージするようにすることはできます。しかし、我々は代わりに、どの値が一緒に更新されやすいのかに基づいて、state を複数の state 変数に分割することをお勧めします。
例えば、コンポーネントの state をposition
とsize
という複数のオブジェクトに分割して、マージを行わなくてもposition
を常に新たな値で置換するようにできるでしょう。
functionBox(){const[position, setPosition]=useState({left:0,top:0});const[size, setSize]=useState({width:100,height:100});useEffect(()=>{functionhandleWindowMouseMove(e){setPosition({left: e.pageX,top: e.pageY});}// ...
互いに独立した state 変数を分割することには別の利点もあります。そうすることで後からそのロジックをカスタムフックに抽出しやすくなるのです。例えば:
functionBox(){const position=useWindowPosition();const[size, setSize]=useState({width:100,height:100});// ...}functionuseWindowPosition(){const[position, setPosition]=useState({left:0,top:0});useEffect(()=>{// ...},[]);return position;}
position
の state 変数に対するuseState
の呼び出しとそれに関連する副作用を、それらのコードを変えずにカスタムフックに移行できたことに注意してください。もしすべての state が単一のオブジェクトに入っていたら、抽出するのはもっと困難だったでしょう。
すべての state を 1 つのuseState
呼び出しに含めても動作しますし、フィールドごとに別にuseState
を持たせることでも動作はします。しかしこれらの両極端の間でうまくバランスを取り、少数の独立した state 変数に関連する state をグループ化することで、コンポーネントは最も読みやすくなります。state のロジックが複雑になった場合は、それをリデューサで管理するか、カスタムフックを書くことをお勧めします。
これは稀なユースケースです。必要であれば、変更可能な ref を使って、初回レンダー中なのか更新中なのかに対応する真偽値を手動で保持し、副作用内でその値を参照するようにすることができます(このようなことを何度もやる場合は、そのためのカスタムフックを書くことができます)。
前回の props や state が欲しくなるというケースは 2 つあります。
ひとつは、前回の props を副作用のクリーンアップに使用したいという場合です。例えば、userId
プロパティに基づいてソケットを購読する副作用を書いている場合などです。userId
プロパティが変化した場合、ひとつ前のuseId
の購読を解除して次のものを購読したくなるでしょう。ですがこれを実現するのに、特別なことをする必要はありません:
useEffect(()=>{ ChatAPI.subscribeToSocket(props.userId);return()=> ChatAPI.unsubscribeFromSocket(props.userId);},[props.userId]);
上記の例では、userId
が3
から4
に変わった場合、ChatAPI.unsubscribeFromSocket(3)
が最初に走り、その後にChatAPI.subscribeToSocket(4)
が走ります。クリーンアップ関数は「前回」のuserId
をクロージャとしてキャプチャしていますので、前回の値を取得する必要はありません。
別の場面では、props や他の state の変更に基づいて state を調整したいということがあるかもしれません。これはめったに必要なものではありませんし、通常はコードに重複した冗長な state があるというサインです。しかしこれが必要な稀なパターンでは、前の state や props を state に保存してレンダー中に更新することができます。
これまでusePrevious
というカスタムフックを使うことを提案していましたが、ほとんどのユースケースは上記の 2 つのパターンのいずれかに当てはまることがわかりました。もしもこのユースケースが当てはまらない場合は、値を ref に保持し、必要に応じて手作業でアップデートすることができます。レンダー中に ref を読み出したり変更したりするとコンポーネントの挙動を予想・理解するのが難しくなるため、避けるようにしてください。
イベントハンドラにせよ副作用関数にせよ、コンポーネント内に書かれた関数からは、その関数が作成された時の props や state が「見え」ます。以下のような例を考えてみましょう:
functionExample(){const[count, setCount]=useState(0);functionhandleAlertClick(){setTimeout(()=>{alert('You clicked on: '+ count);},3000);}return(<div><p>You clicked{count} times</p><buttononClick={()=>setCount(count+1)}> Click me</button><buttononClick={handleAlertClick}> Show alert</button></div>);}
最初に “Show alert” ボタンをクリックして、次にカウンタを増加させた場合、アラートダイアログに表示されるのは“Show alert” ボタンをクリックした時点でのcount
変数の値になります。これにより props や state が変わらないことを前提として書かれたコードによるバグが防止できます。
非同期的に実行されるコールバック内で、意図的に state の最新の値を読み出したいという場合は、その値をref 内に保持して、それを書き換えたり読み出したりすることができます。
最後に、古い props や state が見えている場合に考えられる他の理由は、「依存の配列」による最適化を使った際に正しく依存する値の全部を指定しなかった、というものです。例えば副作用フックの第 2 引数に[]
を指定したにも関わらず副作用内でsomeProps
を読み出しているという場合、副作用関数内ではsomeProps
の初期値がずっと見え続けることになります。解決方法は依存配列自体を削除するか、配列の中身を修正することです。関数の扱い方、および依存する値の変化を誤って無視することなく副作用の実行回数を減らすためのよくある手法についてもご覧ください。
補足
exhaustive-deps
という ESLint のルールをeslint-plugin-react-hooks
パッケージの一部として提供しています。依存配列が正しく指定されていない場合に警告し、修正を提案します。
getDerivedStateFromProps
を実装できますか?おそらくそのようなものは必要ないのですが、これが本当に必要になる稀なケースでは(例えば<Transition>
コンポーネントを実装するときなど)、レンダーの最中に state を更新することができます。React は最初のレンダーの終了直後に更新された state を使ってコンポーネントを再実行しますので、計算量は高くなりません。
以下の例では、row
プロパティの前回の値を state 変数に格納し後で比較できるようにしています:
functionScrollView({row}){const[isScrollingDown, setIsScrollingDown]=useState(false);const[prevRow, setPrevRow]=useState(null);if(row!== prevRow){// Row changed since last render. Update isScrollingDown.setIsScrollingDown(prevRow!==null&& row> prevRow);setPrevRow(row);}return`Scrolling down:${isScrollingDown}`;}
これは最初は奇妙に見えるかもしれませんが、これまでも概念的にはgetDerivedStateFromProps
はレンダー中に更新を行うというのがまさに目的でした。
useState
フックとuseReducer
フックのいずれも、前回と今回で値が同じである場合は更新を回避します。適当な場所で state を変化させた後にsetState
を呼び出しても再レンダーは発生しません。
通常、React でローカル state を直接変更すべきではありません。しかし避難ハッチとして、カウンターを使って state が変化していない場合でも再レンダーを強制することが可能です。
const[ignored, forceUpdate]=useReducer(x=> x+1,0);functionhandleClick(){forceUpdate();}
可能であればこのパターンは避けるようにしてください。
このようなことをする必要はあまりないはずですが、命令型のメソッドを親コンポーネントに公開するためにuseImperativeHandle
フックを利用することができます。
DOM ノードの位置やサイズを測定するための基本的な方法として、コールバック形式の ref が利用できます。React は ref が異なるノードに割り当てられるたびにコールバックを呼び出します。こちらの小さなデモをご覧ください。
functionMeasureExample(){const[height, setHeight]=useState(0);const measuredRef=useCallback(node=>{if(node!==null){setHeight(node.getBoundingClientRect().height);}},[]);return(<><h1ref={measuredRef}>Hello, world</h1><h2>The above header is{Math.round(height)}px tall</h2></>);}
この例でuseRef
を使わなかったのは、オブジェクト型の ref には現在値が変わった時にそれを通知する機能がないためです。コールバック ref を使うことで、子コンポーネントが測定されたノードを(例えばクリックに応じて)後から表示する場合でも、親コンポーネントの側でその変更について通知を受け取り、測定値を反映させることができます。
useCallback
の依存値の配列として[]
を渡したことに注意してください。これにより我々の ref コールバックが再レンダーごとに変化しないことが保証され、React が不必要にその関数を呼ばないで済みます。
この例では、レンダーされている<h1>
はどの再レンダー間でも同じように存在するため、コールバック ref はコンポーネントのマウント時とアンマウント時にのみ呼び出されます。コンポーネントのリサイズが発生した際に毎回通知を受け取りたい場合は、ResizeObserver
や、これを使って作成されているサードパーティのフックの利用を検討してください。
お望みであれば再利用可能なフックとしてこのロジックを抽出できます。
functionMeasureExample(){const[rect, ref]=useClientRect();return(<><h1ref={ref}>Hello, world</h1>{rect!==null&&<h2>The above header is{Math.round(rect.height)}px tall</h2>}</>);}functionuseClientRect(){const[rect, setRect]=useState(null);const ref=useCallback(node=>{if(node!==null){setRect(node.getBoundingClientRect());}},[]);return[rect, ref];}
const [thing, setThing] = useState()
というのはどういう意味ですか?この構文に馴染みがない場合はステートフックのドキュメント内の説明をご覧ください。
はい。条件付きで副作用を実行するを参照してください。これがデフォルトの動作になっていないのは、更新時の対応を忘れることがバグの元になるからです。
いいえ、一般的には省略できません。
functionExample({ someProp}){functiondoSomething(){ console.log(someProp);}useEffect(()=>{doSomething();},[]);// 🔴 This is not safe (it calls `doSomething` which uses `someProp`)}
副作用関数の外側にある関数内でどの props や state が使われているのか覚えておくのは大変です。ですので副作用関数内で使われる関数は副作用関数内で宣言するのがよいでしょう。そうすればコンポーネントスコープ内のどの値に副作用が依存しているのかを把握するのは容易です。
functionExample({ someProp}){useEffect(()=>{functiondoSomething(){ console.log(someProp);}doSomething();},[someProp]);// ✅ OK (our effect only uses `someProp`)}
このようにした後で、やはりコンポーネントスコープ内のどの値も使用していないのであれば、[]
を指定することは安全です:
useEffect(()=>{functiondoSomething(){ console.log('hello');}doSomething();},[]);// ✅ OK in this example because we don't use *any* values from component scope
ユースケースによっては、以下に述べるような選択肢もあります。
補足
eslint-plugin-react-hooks
パッケージの一部としてexhaustive-deps
という ESLint のルールを提供しています。更新の一貫性が保たれていないコンポーネントを見つけるのに役立ちます。
これがなぜ重要なのか説明します。
useEffect
、useLayoutEffect
、useMemo
、useCallback
あるいはuseImperativeHandle
の最後の引数として依存する値のリストを渡す場合、コールバック内部で使われ React のデータの流れに関わる値が、すべて含まれている必要があります。すなわち props や state およびそれらより派生するあらゆるものです。
関数を依存のリストから安全に省略できるのは、その関数(あるいはその関数から呼ばれる関数)が props、state ないしそれらから派生する値のいずれも含んでいない場合のみです。以下の例にはバグがあります。
functionProductPage({ productId}){const[product, setProduct]=useState(null);asyncfunctionfetchProduct(){const response=awaitfetch('http://myapi/product/'+ productId);// Uses productId propconst json=await response.json();setProduct(json);}useEffect(()=>{fetchProduct();},[]);// 🔴 Invalid because `fetchProduct` uses `productId`// ...}
推奨される修正方法は、この関数を副作用内に移動することです。これにより、副作用がどの props や state を利用しているのか把握しやすくなり、それらが指定されていることを保証しやすくなります。
functionProductPage({ productId}){const[product, setProduct]=useState(null);useEffect(()=>{// By moving this function inside the effect, we can clearly see the values it uses.asyncfunctionfetchProduct(){const response=awaitfetch('http://myapi/product/'+ productId);const json=await response.json();setProduct(json);}fetchProduct();},[productId]);// ✅ Valid because our effect only uses productId// ...}
これにより、要らなくなったレスポンスに対して副作用内でローカル変数を使って対処することも可能になります。
useEffect(()=>{let ignore=false;asyncfunctionfetchProduct(){const response=awaitfetch('http://myapi/product/'+ productId);const json=await response.json();if(!ignore)setProduct(json);}fetchProduct();return()=>{ ignore=true};},[productId]);
副作用内に関数を移動したことで、依存リスト内にこの関数を含めないでよくなりました。
ヒント
何らかの理由で副作用内に関数を移動できないという場合、他にとりうる選択肢がいくつかあります。
useCallback
を使ってその定義をラップすることが可能です。これにより、関数自体の依存が変わらない限り関数も変化しないことを保証できます。functionProductPage({ productId}){// ✅ Wrap with useCallback to avoid change on every renderconst fetchProduct=useCallback(()=>{// ... Does something with productId ...},[productId]);// ✅ All useCallback dependencies are specifiedreturn<ProductDetailsfetchProduct={fetchProduct}/>;}functionProductDetails({ fetchProduct}){useEffect(()=>{fetchProduct();},[fetchProduct]);// ✅ All useEffect dependencies are specified// ...}
上記の例では関数を依存リストに含める必要があることに注意してください。これによりProductPage
のproductId
プロパティが変化した場合に自動的にProductDetail
コンポーネント内でデータの再取得が発生するようになります。
しばしば、ある副作用がとても頻繁に変化する state を利用しないといけない場合があります。依存のリストからその state を省略したくなるかもしれませんが、通常それはバグになります。
functionCounter(){const[count, setCount]=useState(0);useEffect(()=>{const id=setInterval(()=>{setCount(count+1);// This effect depends on the `count` state},1000);return()=>clearInterval(id);},[]);// 🔴 Bug: `count` is not specified as a dependencyreturn<h1>{count}</h1>;}
依存のリストが空であるということ ([]
) は、コンポーネントのマウント時に副作用が一度のみ実行され、毎回の再レンダー時には実行されないということを意味します。ここでの問題は、副作用コールバックが実行された時点でcount
の値が0
に設定されたクロージャを作成したため、setInterval
内のコールバックでcount
の値が変わらなくなってしまう、ということです。毎秒ごとにこのコールバックはsetCount(0 + 1)
を呼び出すので、カウントは 1 のまま変わらなくなってしまいます。
依存のリストとして[count]
を指定すればバグは起きなくなりますが、その場合値が変化するたびにタイマーがリセットされることになります。事実上それぞれのsetInterval
は一度しか実行されずに(setTimeout
のように)クリアされてしまうのです。これは望ましい動作ではありません。これを修正するため、setState
関数形式による更新を利用することができます。これにより state の現在値を参照せずに state がどのように更新されるべきかを指定できます。
functionCounter(){const[count, setCount]=useState(0);useEffect(()=>{const id=setInterval(()=>{setCount(c=> c+1);// ✅ This doesn't depend on `count` variable outside},1000);return()=>clearInterval(id);},[]);// ✅ Our effect doesn't use any variables in the component scopereturn<h1>{count}</h1>;}
(setCount
関数については同一性が保たれることが保証されているので、省略して構いません)
これで、setInterval
のコールバックは 1 秒に 1 回実行されますが、内部のsetCount
はcount
の最新の値(この例ではc
)を参照できるようになります。
より複雑なケース(ある state が別の state に依存している場合など)においては、state 更新のロジックをuseReducer
フックを使って副作用の外部に移動することを考慮してください。こちらの記事にこのやり方についての例があります。useReducer
から返されるdispatch
関数は常に同一性が保たれます。これはリデューサ (reducer) 関数がコンポーネント内で宣言されており props を読み出している場合でも同様です。
最終手段として、クラスにおけるthis
のようなものが欲しい場合は、ref を使ってミュータブルな値を保持させることができます。そうすればその値を読み書き可能です。例えば:
functionExample(props){// Keep latest props in a ref.const latestProps=useRef(props);useEffect(()=>{ latestProps.current= props;});useEffect(()=>{functiontick(){// Read latest props at any time console.log(latestProps.current);}const id=setInterval(tick,1000);return()=>clearInterval(id);},[]);// This effect never re-runs}
ミュータブルな値に依存することでコンポーネントの挙動が予測しづらくなるため、これは代替手段が思いつかない場合にのみ利用してください。うまくフックに移行できないパターンがあった場合は動作するコード例を添えてissue を作成していただければお手伝いします。
shouldComponentUpdate
を実装できますか?関数コンポーネントをReact.memo
でラップして props を浅く比較するようにしてください。
const Button= React.memo((props)=>{// your component});
これがフックになっていないのは、フックと違って組み合わせ可能ではないからです。React.memo
はPureComponent
の同等物ですが、props のみを比較するという違いがあります(新旧の props を受け取るカスタムの比較関数を 2 つめの引数として加えることができます。その関数が true を返した場合はコンポーネントの更新はスキップされます)。
React.memo
は state を比較しませんが、これは比較可能な単一の state オブジェクトが存在しないからです。しかし子コンポーネント側も純粋にしておくことや、useMemo
を使って個々のコンポーネントを最適化することが可能です。
useMemo
フックを使うと、前の計算結果を「記憶」しておくことで、複数のレンダー間で計算結果をキャッシュすることができます。
const memoizedValue=useMemo(()=>computeExpensiveValue(a, b),[a, b]);
このコードはcomputeExpensiveValue(a, b)
を呼び出します。しかし依存である[a, b]
の組み合わせが前回の値と変わっていない場合は、useMemo
はこの関数の 2 回目の呼び出しをスキップし、単に前回返したのと同じ値を返します。
useMemo
に渡した関数はレンダー中に実行されるということを覚えておいてください。レンダー中に通常やらないようなことをやらないようにしましょう。例えば副作用はuseMemo
ではなくuseEffect
の仕事です。
useMemo
はパフォーマンス最適化のために使うものであり、意味上の保証があるものだと考えないでください。将来的に React は、例えば画面外のコンポーネント用のメモリを解放する、などの理由で、メモ化された値を「忘れる」ようにする可能性があります。useMemo
なしでも動作するコードを書き、パフォーマンス最適化のためにuseMemo
を加えるようにしましょう(値が絶対に再計算されてはいけないというような稀なケースでは、ref の遅延初期化を行うことができます)。
便利なことに、useMemo
は子コンポーネントの計算量の高い再レンダーをスキップするのにも使えます:
functionParent({ a, b}){// Only re-rendered if `a` changes:const child1=useMemo(()=><Child1a={a}/>,[a]);// Only re-rendered if `b` changes:const child2=useMemo(()=><Child2b={b}/>,[b]);return(<>{child1}{child2}</>)}
フック呼び出しはループ内に配置できないため、このアプローチはループ内では動作しないことに注意してください。ただしリストのアイテムの部分を別のコンポーネントに抽出してその中でuseMemo
を呼び出すことは可能です。
useMemo
を使えば同じ値に依存している高価な計算の結果をメモ化することができます。しかしこれはあくまでヒントとして使われるものであり、計算が再実行されないということを保証しません。しかし時にはオブジェクトが一度しか作られないことを保証したい場合があります。
まずよくあるユースケースは state の初期値を作成することが高価な場合です。
functionTable(props){// ⚠️ createRows() is called on every renderconst[rows, setRows]=useState(createRows(props.count));// ...}
次回以降のレンダーでは無視される初期 state を毎回作成しなおすことを防ぐため、useState
に関数を渡すことができます。
functionTable(props){// ✅ createRows() is only called onceconst[rows, setRows]=useState(()=>createRows(props.count));// ...}
React は初回レンダー時のみこの関数を呼び出します。useState
の API リファレンスを参照してください。
また、まれにuseRef()
の初期値を毎回再作成することを避けたいということもあります。例えば、命令型で作成するクラスのインスタンスが一度しか作成されないことを保証したいということがあるかもしれません。
functionImage(props){// ⚠️ IntersectionObserver is created on every renderconst ref=useRef(newIntersectionObserver(onIntersect));// ...}
useRef
はuseState
のような関数を渡す形式のオーバーロード記法が使えません。代わりに、自分で関数を書いて高価なオブジェクトを遅延型で ref に設定することが可能です。
functionImage(props){const ref=useRef(null);// ✅ IntersectionObserver is created lazily oncefunctiongetObserver(){if(ref.current===null){ ref.current=newIntersectionObserver(onIntersect);}return ref.current;}// When you need it, call getObserver()// ...}
これにより、本当に必要になるまで高価なオブジェクトの作成を避けることができます。Flow や TypeScript を使っているなら、getObserver()
を non-nullable な型にしておくと便利でしょう。
いいえ。モダンブラウザでは、特殊な場合を除いて、クラスと比較してクロージャーの生の性能はそれほど変わりません。
しかも、フックの設計は幾つかの点においてはより効率的です。
過去には、インライン関数によるパフォーマンスの懸念というのは、レンダー毎に新しいコールバック関数を作って渡すと子コンポーネントでのshouldComponentUpdate
による最適化が動かなくなる、という問題と関連していました。フックではこの問題について 3 つの側面から対応します。
useCallback
フックを使えば再レンダーをまたいで同じコールバックを保持できるので、shouldComponentUpdate
がうまく動作し続けます
// Will not change unless `a` or `b` changesconst memoizedCallback=useCallback(()=>{doSomething(a, b);},[a, b]);
useMemo
フックを使うことで個々の子コンポーネントをいつ更新するのかを制御しやすくなるため、コンポーネントが純粋である必要性は低くなっていますuseReducer
フックを使えば、複数のコールバックを深い階層に受け渡していく必要があまりなくなります我々が見たところ、ほとんどの人はコンポーネントツリーの各階層で手作業でコールバックを受け渡ししていく作業が好きではありません。それはより明示的ではありますが、面倒な『配管工事』をしている気分になることがあります。
大きなコンポーネントツリーにおいて我々がお勧めする代替手段は、useReducer
でdispatch
関数を作って、それをコンテクスト経由で下の階層に渡す、というものです。
const TodosDispatch= React.createContext(null);functionTodosApp(){// Note: `dispatch` won't change between re-rendersconst[todos, dispatch]=useReducer(todosReducer);return(<TodosDispatch.Providervalue={dispatch}><DeepTreetodos={todos}/></TodosDispatch.Provider>);}
TodosApp
ツリーの中にいるあらゆる子コンポーネントはこのdispatch
関数を使うことができ、上位にいるTodosApp
にアクションを伝えることができます。
functionDeepChild(props){// If we want to perform an action, we can get dispatch from context.const dispatch=useContext(TodosDispatch);functionhandleClick(){dispatch({type:'add',text:'hello'});}return(<buttononClick={handleClick}>Add todo</button>);}
これは(複数のコールバックを何度も受け渡しする必要がないので)メンテナンスの観点から便利だ、というだけではなく、コールバックにまつわる問題をすべて回避できます。深い更新においてはこのようにdispatch
を渡すのがお勧めのパターンです。
アプリケーションのstate については、props として渡していくか(より明示的)、あるいはコンテクスト経由で渡すか(深い更新ではより便利)を選ぶ余地が依然あります。もしもコンテクストを使って state も渡すことにする場合は、2 つの別のコンテクストのタイプを使ってください —dispatch
のコンテクストは決して変わらないため、dispatch
だけを使うコンポーネントは(アプリケーションの state も必要でない限り)再レンダーする必要がなくなります。
useCallback
からの頻繁に変わる値を読み出す方法は?補足
我々は個別のコールバックを props として渡すのではなく、コンテクスト経由で
dispatch
を渡すことを推奨しています。以下のアプローチは網羅性と避難ハッチの目的で掲載しているものです。
稀なケースですが、コールバックをuseCallback
でメモ化しているにも関わらず、内部関数を何度も再作成しないといけないためメモ化がうまく働かない、ということがあります。あなたがメモ化しようとしている関数がレンダー最中には使われないイベントハンドラなのであれば、インスタンス変数としての ref を使って最後に使われた値を手動で保持しておくことができます。
functionForm(){const[text, updateText]=useState('');const textRef=useRef();useEffect(()=>{ textRef.current= text;// Write it to the ref});const handleSubmit=useCallback(()=>{const currentText= textRef.current;// Read it from the refalert(currentText);},[textRef]);// Don't recreate handleSubmit like [text] would doreturn(<><inputvalue={text}onChange={e=>updateText(e.target.value)}/><ExpensiveTreeonSubmit={handleSubmit}/></>);}
これはやや複雑なパターンですが、このような避難ハッチ的最適化は必要であれば可能だということです。カスタムフックに抽出すれば多少は読みやすくなります:
functionForm(){const[text, updateText]=useState('');// Will be memoized even if `text` changes:const handleSubmit=useEventCallback(()=>{alert(text);},[text]);return(<><inputvalue={text}onChange={e=>updateText(e.target.value)}/><ExpensiveTreeonSubmit={handleSubmit}/></>);}functionuseEventCallback(fn, dependencies){const ref=useRef(()=>{thrownewError('Cannot call an event handler while rendering.');});useEffect(()=>{ ref.current= fn;},[fn,...dependencies]);returnuseCallback(()=>{const fn= ref.current;returnfn();},[ref]);}
いずれにせよ、このパターンは薦められず、網羅性のために示しているに過ぎません。代わりにコールバックを深く受け渡していくことを回避するのが望ましいパターンです。
React は現在どのコンポーネントがレンダー中なのかを把握しています。フックのルールのお陰で、フックは React のコンポーネント内(あるいはそれらから呼び出されるカスタムフック内)でのみ呼び出されるということが分かっています。
それぞれのコンポーネントに関連付けられる形で、React 内に「メモリーセル」のリストが存在しています。それらは単に何らかのデータを保存できる JavaScript のオブジェクトです。あなたがuseState()
のようなフックを呼ぶと、フックは現在のセルの値を読み出し(あるいは初回レンダー時はセル内容を初期化し)、ポインタを次に進めます。これが複数のuseState()
の呼び出しが個別のローカル state を得る仕組みです。
フックは複数の異なった出典からのアイディアを総合したものです:
adopt
keyword。フックはSebastian Markbåge が最初のデザインを作り、Andrew Clark、Sophie Alpert、Dominic Gannaway およびその他の React チームのメンバーが洗練させました。