Twitterを読んだ場合、フックは新しいReact機能であることをご存知でしょうが、実際にフックを使用するにはどうすればよいでしょうか? この記事では、フックの使用例をいくつか紹介します。
理解すべき重要なアイデアの1つは、 フックを使用すると、クラスを記述せずに状態やその他のReact機能を使用できることです 。
フックの背後にある動機
コンポーネント指向アーキテクチャにより、アプリケーションでビューを再利用できますが、開発者が直面する最大の問題の1つは、 コンポーネント間で状態ベースのロジックを再利用する方法です 。 同様の状態ロジックを持つコンポーネントがある場合、コンポーネントを再利用するための優れたソリューションはありません。これにより、コンストラクターおよびライフサイクルメソッドでロジックが重複する場合があります。
この問題を解決するには、通常次を使用します。
- 高次コンポーネント
- 小道具をレンダリングする
しかし、これらのパターンの両方には、コードベースの複雑さの一因となる欠陥があります。
フックはこれらのすべての問題を解決することを目的としており、クラスを作成せずに、状態、コンテキスト、ライフサイクルメソッド、refなどにアクセスできる機能コンポーネントを作成できます。
アルファのフック
ダイブする前に、Hooks APIの開発はまだ完了していないことに言及することが重要です。
さらに、 公式ドキュメントは非常に優れており、フックの導入の背後にある動機について広範囲に説明しているため、これも読むことをお勧めします。
UPDあなたが読んでいる元の記事は、このAPIがアルファテスト中であったときに書かれたもので、現時点ではReact Hooksが正式に使用できる状態になっています。 リリースに加えられた不可逆的な変更(アルファと比較)は、記事の最後またはリリースノートに記載されています 。
フックとクラスの関係
Reactに精通している場合、フックを理解する最良の方法の1つは、フックを使用したクラスの操作に慣れている動作をどのように再現できるかを確認することです。
コンポーネントクラスを記述するとき、次のことが必要になることが多いことを思い出してください。
- 状態を管理する
- componentDidMount()やcomponentDidUpdate()などのライフサイクルメソッドを使用する
- アクセスコンテキスト(静的contextType)
React Hooksを使用すると、機能コンポーネントで同様の動作を再現できます。
- accessState()フックを使用してコンポーネントの状態にアクセスします
- componentDidMount()やcomponentDidUpdate()などのライフサイクルメソッドを使用する代わりに、useEffect()フックを使用します。
- 静的contextTypeプロパティの代わりに、useContext()フックを使用します。
フックを使用するには、Reactの最新バージョンが必要です。
package.jsonのreactとreact-domの値を「next」に変更することで、すぐにフックを開始できます。
UseState()フックの例
状態はReactの不可欠な部分です。 これにより、データを含む変数を宣言することができ、そのデータはアプリケーションで使用されます。 クラスを使用すると、状態は通常次のように定義されます。
Hooks以前は、通常、状態はコンポーネント(クラス)でのみ使用されていましたが、前述のように、 Hooksでは機能コンポーネントにも状態を追加できます 。
以下の例を見てみましょう。 ここでは、状態値に応じて色を変えるバックライトスイッチを作成します。 このために、フックuseState()を使用します。
完全なコード(および実行可能な例)は次のとおりです。以下で何が起こるかを見ていきます。 画像をクリックすると、CodeSandBoxでこの例を見ることができます。
私たちのコンポーネントは関数です
上記のコードブロックでは、ReactからuseStateをインポートすることから始めます。 UseStateは、this.stateが提供できる機能を活用する新しい方法です。
次に、このコンポーネントがクラスではなく関数であることに注意してください。 おもしろい!
状態の読み取りと書き込み
この関数内で、useStateを呼び出して状態変数を作成します。
useStateは状態変数の宣言に使用され 、任意のタイプの値で初期化できます(クラスの状態はオブジェクトである必要があります)。
上で見たように、useStateの戻り値に構造化を使用しています。
- 最初の値(この場合はlight)は現在の状態です(this.stateなど)
- 2番目の値は、状態値(最初の値)の更新に使用される関数です(this.setStateなど)。
次に、2つの関数を作成します。各関数は、状態を異なる値0または1に設定します。
次に、それらをビューのボタンのイベントハンドラーとして適用します。
Reactトラックの状態
「オン」ボタンが押されると、setOn関数が呼び出され、setLight(1)が呼び出されます。 setLight(1)を呼び出すと、次のrenderのライト値が更新されます 。 少し不思議に思えるかもしれませんが、 Reactはこの変数の値を追跡し、このコンポーネントが再レンダリングされると新しい値を渡します。
次に、現在の状態( light )を使用して、ランプをオンにするかどうかを決定します。 つまり、 lightの値に応じてSVGの塗りつぶし色を設定します。 ライトが0(オフ)の場合、fillColorは#000000に設定されます(1(オン)の場合、fillColorは#ffbb73に設定されます)。
複数の状態
上記の例ではこれを行いませんが、useStateを複数回呼び出すことで複数の状態を作成できます。 例:
注
フックの使用にはいくつかの制限があることに注意してください。 最も重要なことは、関数のトップレベルでのみフックを呼び出すことです。 詳細については、「 フックルール 」を参照してください。
UseEffect()フックの例
UseEffectフックを使用すると、機能コンポーネントで副作用を実行できます 。 副作用には、APIへのアクセス、DOMの更新、イベントハンドラーのサブスクライブなどがあります。必要なのは、「命令型」アクションが発生することだけです。
useEffect()フックを使用して、Reactはレンダリング後に特定のアクションを実行したいことを認識します。
以下の例を見てみましょう。 useEffect()を使用してAPIを呼び出し、応答を取得します。
このコードサンプルではuseStateとuseEffectの両方を使用しています。これは、stateへのAPI呼び出しの結果を書きたいためです。
データを受信して状態を更新する
「エフェクトを使用」するには、アクションをuseEffect関数に配置する必要があります。つまり、「アクション」エフェクトを匿名関数としてuseEffectの最初の引数として渡します。
上記の例では、名前のリストを返すAPIを参照しています。 応答が返されたら、それをJSONに変換し、 setNames(データ)を使用して状態を設定します。
エフェクト使用時のパフォーマンスの問題
ただし、 useEffectの使用については他にも説明があります。
最初に考えることは、デフォルトでは、 useEffectがすべてのレンダリングで呼び出されるということです! 良いニュースは、古いデータについて心配する必要がないことですが、悪いニュースは、おそらくこの場合のように、レンダリングごとにHTTPリクエストを行いたくないことです。
この場合のように、2番目のuseEffect引数を使用してエフェクトをスキップできます。 useEffectの2番目の引数は、「観察」したい変数のリストです。これらの値のいずれかが変更された場合にのみ、効果を再実行します。
上記のコード例では、2番目の引数として空の配列を渡していることに注意してください。 コンポーネントをマウントするときにのみこのエフェクトに名前を付けたいとReactに伝えます。
エフェクトのパフォーマンスの詳細については、ホワイトペーパーのこのセクションを参照してください。
さらに、useState関数と同様に、useEffectでは複数のインスタンスを使用できます。つまり、複数のuseEffect関数を使用できます。
UseContext()フックの例
コンテキストポイント
Reactのコンテキストは、子コンポーネントが親コンポーネントの値にアクセスする方法です。
コンテキストの必要性を理解するために:Reactアプリケーションを作成するとき、多くの場合、Reactツリーの一番上から下に値を渡す必要があります。 コンテキストを使用しないことにより、プロップを知る必要のないコンポーネントにプロップを渡します。
「関連のない」コンポーネントのツリーにプロップを渡すことは、愛情を込めてプロップドリルと呼ばれます。
React Contextは、コンポーネントツリーを通じて、これらの値を要求するコンポーネントと値を共有できるようにすることで、小道具のドリルの問題を解決します。
useContext()コンテキストの使用を簡素化します
useContext Hookを使用すると、コンテキストの使用がこれまで以上に簡単になります。
useContext()関数は、最初にReact.createContext()から返されるコンテキストオブジェクトを受け取り 、現在のコンテキスト値を返します。 以下の例を見てみましょう。
上記のコードでは、JediContextのコンテキストはReact.createContext()を使用して作成されます。
AppコンポーネントでJediContext.Providerを使用し、そこで値を「Luke」に設定します。 これは、コンテキストにアクセスする必要があるすべてのコンポーネントがこの値を読み取ることができるようになることを意味します。
Display()関数でこの値を読み取るには、useContextを呼び出して、JediContext引数を渡します。
次に、React.createContextから取得したコンテキストオブジェクトを渡すと、値が自動的に表示されます。 プロバイダー値が更新されると、このフックは最後のコンテキスト値で自動的に機能します。
大規模なアプリケーションでコンテキストへのリンクを取得する
上記では、両方のコンポーネント内にJediContextを作成しましたが、より大きなアプリケーションでは、DisplayとAppは異なるファイルにあります。 したがって、同様の状況がある場合、「ファイル間でJediContextへのリンクをどのように取得しますか?」
答えは、JediContextをエクスポートする新しいファイルを作成する必要があるということです。
たとえば、次のような内容を含むcontext.jsファイルがあるとします。
その後、App.js(およびDisplay.js)で次のように記述します。
ありがとう、 デイブ )
UseRef()フックの例
Refsはrender()メソッドで作成されたReact要素にアクセスする方法を提供します。
React refを初めて使用する場合は、このReact refの概要をご覧ください 。
useRef()関数はrefオブジェクトを返します。
useRef()および入力付きのフォーム
useRef()フックを使用した例を見てみましょう。
上記の例では、useRef()をuseState()と組み合わせて使用して、入力値をpタグにレンダリングします。
Refは変数nameRefに作成されます。 次に、refとして指定された変数nameRefを入力で使用できます。 基本的に、これは入力フィールドの内容がrefを介してアクセスできることを意味します。
コードの送信ボタンには、submitButtonというonClickイベントハンドラーがあります。 submitButton関数はsetName(useStateを介して作成された)を呼び出します。
hookStateを使用して既に行ったように、状態名を設定するためにsetNameが使用されます。 入力タグから名前を抽出するには、nameRef.current.valueの値を読み取ります。
useRefに関するもう1つの注意点は、ref属性よりも多く使用できることです。
カスタムフックの使用
フックの最も優れた機能の1つは、 独自のフックを作成することにより、複数のコンポーネント間でロジックを簡単に共有できることです。
以下の例では、状態を追跡してカスタム状態を提供できるカスタムsetCounter()フックを作成します!
また、 react-useの useCounterフックとKentのuseCounterを参照してください。
上記のコードブロックでは、フックのロジックを格納するuseCounter関数を作成します。
useCounterは他のフックを使用する可能性があることに注意してください! useStateを使用して新しいフック状態を作成することから始めましょう。
次に、 setCountを呼び出し、それに応じて現在のカウントを調整する2つのヘルパー関数、 incrementおよびdecrementを定義します。
最後に、フックと対話するために必要なリンクを返します。
Q:オブジェクトを含む配列を返すとどうなりますか?
A:フックのほとんどのものと同様に、APIの規則はまだ完全ではありません。 ただし、ここで行うことは配列を返します。
- 最初の項目は、フックの現在の値です。
- 2番目の要素は、フックとの対話に使用される関数を含むオブジェクトです。
この規則により、上記のmyCountのように、フックの現在の値を簡単に「名前変更」できます。
ただし、カスタムフックから必要なものを返すことができます。
上の例では、ビューでonClickハンドラーとしてインクリメントとデクリメントを使用しています。 ユーザーがボタンをクリックすると、カウンターが更新され、ビューに( myCountのように)再表示されます。
React Hookのテストを書く
フックのテストを作成するには、 react-testing-libraryを使用してテストします。
react-testing-libraryは、Reactコンポーネントをテストするための非常に軽量なソリューションです。 これは、 react-domおよびreact-dom / test-utilsの拡張です。 react-testing-libraryを使用すると、テストがDOMノードで直接動作することが保証されます。
テストフックでは、すべてがまだ明確ではありません。 現在、単独でフックをテストすることはできません。 代わりに、フックをコンポーネントに接続して、このコンポーネントをテストする必要があります。
そのため、以下ではフックのテストを作成し、フックではなくコンポーネントと対話します。 良いニュースは、テストが通常のReactテストのように見えることです。
useState()フックのテスト
useState Hookのテストを作成する例を見てみましょう。 上記のチュートリアルでは、上記で使用したuseStateサンプルのバリエーションをさらにテストします。 「オフ」ボタンを押すと状態が0に設定され、「オン」ボタンを押すと状態が1に設定されることを確認するテストを作成します。
上記のコードブロックでは、 react-testing-libraryとテスト対象のコンポーネントからいくつかのヘルパーをインポートすることから始めます。
- render 、これはコンポーネントの表示に役立ちます。 document.bodyに追加されるコンテナにレンダリングされます
- getByTestIdは、 data-testidによって DOM要素を取得します
- fireEvent 、これはDOMイベントを「トリガー」するために使用されます。 たとえば、イベントハンドラをドキュメントに添付し、一部のDOMイベントをイベント委任を通じて処理します。 ボタンをクリックします。
さらに、テスト承認機能では、テストで使用するdata-testidとその値を持つ要素の変数の値を設定します。 DOM上の要素へのリンクにより、 fireEventメソッドを使用してボタンのクリックをシミュレートできます。
テストでは、 onButtonが押されると状態値が1に設定され、 offButton状態をクリックすると1になることを確認します。
useEffect()フックのテスト
この例では、 useEffect Hookを使用してカートに製品を追加するテストを作成します。 アイテムの数もlocalStorageに保存されます。 以下のCodeSandboxのindex.jsファイルには、アイテムをカートに追加するために使用される実際のロジックが含まれています。
カートアイテムの数の更新がlocalStorageにも反映されることを確認するテストを作成します。ページがリロードされても、カートアイテムの数は同じままです。
テストを確認する関数では、まずlocalStorageのcartItemを0に設定します 。これは、カートアイテムの数が0であることを意味します。次に、 コンテナとデレンダーの両方をAppコンポーネントから再取得します。 Rerenderを使用すると、ページのリロードをシミュレートできます。
次に、ボタンとpタグへのリンクを取得します。これにより、現在のバスケット値が表示され、変数に設定されます。
これが完了すると、テストはaddButtonのクリックをシミュレートし、現在のバスケットカウンターが1かどうかを確認し、ページをリロードします。その後localStorage 、 cartItemが1に設定されているかどうかを確認します現在のカートアイテムの数が0に設定されているかどうかを確認します。
useRef()フックのテスト
この例では、 useRefフックをテストし、上記の元のuseRefの例をテストの基礎として使用します。 UseRefは 、入力フィールドから値を取得するために使用され、値をstateに設定します。 以下のCodeSandboxのindex.jsファイルには、値を入力して送信するためのロジックが含まれています。
テストを承認する関数では、入力フィールドの変数、現在のref値を表示するpタグ、および送信ボタンを設定します。 また、newName変数の入力フィールドに入力する値を設定します。 これは、テストでの検証に使用されます。
fireEvent.changeメソッドを使用して入力フィールドに値を入力します。この場合、定数newNameに保存されている名前が使用され、その後送信ボタンが押されます。
次に、テストは、ボタンを押した後のref値がnewNameの値と一致するかどうかを確認します。
最後に、コンソールに「テストがクラッシュせず、おめでとうございます!」というメッセージが表示されます。
フックに対するコミュニティの反応
React Hooksの導入以来、コミュニティはこの機能に喜んでおり、React Hooksの使用例や例を見てきました。 主なものは次のとおりです。
- このサイトには、React Hookのコレクションが含まれています。
- react-use 、React Hookの束が付属するライブラリー。
- このCodeSandboxの例は、useEffectフックを使用して、 react-springを使用してアニメーションを作成する方法を示しています
- 例は useMutableReducerです。これにより、 reducerで状態を単純に変更して更新できます。
- この例は CodeSandboxにあり、親子通信の複雑な統合使用とレドックスの使用を示しています。
- React Hooks Switchコンポーネント
- 入力値、デバイスの向き、およびドキュメントの可視性のためのフックを持つReactフックの別のコレクション 。
さまざまなタイプのフック
Reactアプリケーションで使用を開始できるさまざまなタイプのフックがあります。 それらは以下のとおりです。
- useState-状態にアクセスできる純粋な関数を作成できます。
- useEffect-副作用を実行できます。 副作用には、API呼び出し、DOMの更新、イベントハンドラーのサブスクライブがあります。
- useContext-コンテキストを含む純粋な関数を作成できます。
- useReducer -Reduxのようなレデューサーへのリンクを提供します
- useRef-可変のrefオブジェクトを返す純粋な関数を作成できます。
- useMemo-格納された値を返すために使用されます。
- useCallback-フックは、メモされたコールバックを返すために使用されます。
- useImperativeMethods -refを使用するときに親コンポーネントに渡されるインスタンス値をカスタマイズします。
- useMutationEffects -DOMミューテーションを実行できるという意味でuseEffectフックに似ています。
- useLayoutEffect -DOMからレイアウトを読み取り、同期的に再レンダリングするために使用されます。
- カスタムフック -再利用可能な関数でコンポーネントロジックを記述できます。
将来のフック
フックの素晴らしいところは、既存のコードと並行して動作するため、フックを実装する変更をゆっくりと行えることです。 必要なのは、Reactをフックをサポートするバージョンにアップグレードすることだけです。
ただし、フックはまだ実験的な機能であり、ReactチームはAPIが変更される可能性があることを繰り返し警告しています。 警告されていることを考慮してください。
フックの出現はクラスにとって何を意味しますか? Reactチームが報告しているように、クラスはまだ残っており、それらはReactコードベースの大きな部分を占めており、おそらく今後しばらくはそうなるでしょう。
クラスを非難する予定はありません。 Facebookには、クラスによって記述された何万ものコンポーネントがありますが、ご理解のとおり、これらのコンポーネントを書き換えるつもりはありません。 しかし、Reactコミュニティがフックを承認する場合、コンポーネントを記述するための2つの異なる推奨方法-Dan Abramovを持つことは意味がありません
特定のHooks APIは今日実験的ですが、コミュニティはHooksのアイデアを支持しているので、Hooksは長い間私たちに留まると思います。
追加のリソース
- Reactチームは、React Hooksを文書化する素晴らしい仕事をしました。詳細はここにあります
- 公式ドキュメントのAPIはこちらです。
- 永続的なRFCもありますので、 そこに行って質問したりコメントを残したりできます。
UPD
翻訳者注:
今日、ご存知のように、彼らはHooks APIを公式にサポートするReact 16.8バージョンをリリースしました。 変更ログには、アルファバージョンとの以下の互換性のない変更が示されています。
-useMutationEffectを削除しました。
-useImperativeMethodsの名前をuseImperativeHandleに変更しました。
-必要な値をuseStateおよびuseReducerフックに渡すときにレンダラーを回避することができました。
-useEffect / useMemo / useCallback Hooksに渡された最初の引数を比較する必要はありません。
-Object.isアルゴリズムを使用して、useStateとuseReducerの値を比較します。
-コンポーネントは、厳密モードで2回レンダリングされます(DEVのみ)。
-useReducer Hookの遅延初期化APIを改善しました。
詳細はこちら 。