Reactフックのベストプラクティス
公開: 2022-03-10 ReactフックはReact16.8に新しく追加されたもので、 class
コンポーネントを記述せずに状態やその他のReact機能を使用できます。 言い換えると、フックは、関数コンポーネントからReact状態とライフサイクル機能に「フック」できる関数です。 ( class
コンポーネント内では機能しません。)
Reactは、 useState
のようないくつかの組み込みフックを提供します。 独自のフックを作成して、異なるコンポーネント間でステートフル動作を再利用することもできます。 次の例は、 useState()
フックを使用して状態が管理されるカウンターを示しています。 ボタンをクリックするたびに、 setCount()
を使用してcount
の値を1
ずつ更新します。
この例では、値が0
のカウンターをレンダリングします。 ボタンをクリックすると、値が1
ずつ増加します。 コンポーネントの初期値は、 useState
を使用して定義されます。
const [count, setCount] = useState(0)
ご覧のとおり、これを0
に設定します。 次に、値をインクリメントするときにonClick()
メソッドを使用してsetCount
を呼び出します。
<button onClick={() => setCount(count + 1)}> Click me </button>
React Hooksがリリースされる前は、 class
コンポーネントを使用する必要があったため、この例ではより多くのコード行が使用されていました。
Reactフックのルール
ベストプラクティスを深く掘り下げる前に、React Hooksのルールを理解する必要があります。これは、この記事で紹介するプラクティスの基本的な概念の一部でもあります。
React HooksはJavaScript関数ですが、使用する場合は2つのルールに従う必要があります。
- トップレベルでフックを呼び出します。
- Reactコンポーネントからのみフックを呼び出します。
注:これらの2つのルールは、JavaScript自体の一部ではなく、ReactHooksで導入されました。
これらのルールをさらに詳しく見ていきましょう。
トップレベルのコールフック
ループ、条件、またはネストされた関数内でフックを呼び出さないでください。 React関数のトップレベルで常にフックを使用してください。 このルールに従うことで、コンポーネントがレンダリングされるたびにフックが同じ順序で呼び出されるようになります。 これにより、Reactは複数のuseState
呼び出しとuseEffect
呼び出しの間でフックの状態を正しく保持できます。
2つの状態を持つForm
コンポーネントを作成しましょう。
-
accountName
-
accountDetail
これらの状態にはデフォルト値がありますuseEffect
フックを使用して、状態をブラウザーのローカルストレージまたはドキュメントのタイトルに永続化します。
これで、このコンポーネントは、 useState
とuseEffect
の複数の呼び出し間で同じままであれば、その状態を正常に管理できる可能性があります。
function Form() { // 1. Use the accountName state variable const [accountName, setAccountName] = useState('David'); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); // 3. Use the accountDetail state variable const [accountDetail, setAccountDetail] = useState('Active'); // 4. Use an effect for updating the title useEffect(function updateStatus() { document.title = accountName + ' ' + accountDetail; }); // ... }
フックの順序が変更された場合(ループまたは条件付きで呼び出された場合に可能になる可能性があります)、Reactはコンポーネントの状態を保持する方法を理解するのに苦労します。
// ------------ useState('David') // 1. Initialize the accountName state variable with 'David' useEffect(persistForm) // 2. Add an effect for persisting the form useState('Active') // 3. Initialize the accountdetail state variable with 'Active' useEffect(updateStatus) // 4. Add an effect for updating the status // ------------- // Second render // ------------- useState('David') // 1. Read the accountName state variable (argument is ignored) useEffect(persistForm) // 2. Replace the effect for persisting the form useState('Active') // 3. Read the accountDetail state variable (argument is ignored) useEffect(updateStatus) // 4. Replace the effect for updating the status // ...
これが、Reactがフックを呼び出すために従う順序です。 順序は同じままなので、コンポーネントの状態を保持できます。 しかし、条件内にフック呼び出しを入れるとどうなりますか?
// We're breaking the first rule by using a Hook in a condition if (accountName !== '') { useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); }
accountName !== ''
条件は最初のレンダリングでtrue
であるため、このフックを実行します。 ただし、次のレンダリングで、ユーザーがフォームをクリアして、条件をfalse
にする可能性があります。 レンダリング中にこのフックをスキップしたので、フック呼び出しの順序は異なります。
useState('David') // 1. Read the accountName state variable (argument is ignored) // useEffect(persistForm) // This Hook was skipped! useState('Active') // 2 (but was 3). Fail to read the accountDetails state variable useEffect(updateStatus) // 3 (but was 4). Fail to replace the effect
Reactは、2回目のuseState
呼び出しで何を返すかを知りません。 Reactは、このコンポーネントの2番目のフック呼び出しが前のレンダリング時と同じようにpersistForm
効果に対応することを期待していましたが、現在はそうではありません。 その時点から、スキップした後の次のHook
呼び出しも1つずつシフトし、バグが発生します。
これが、コンポーネントのトップレベルでフックを呼び出す必要がある理由です。 条件付きでエフェクトを実行したい場合は、その条件をフック内に置くことができます。
注:このトピックの詳細については、ReactHookのドキュメントをご覧ください。
Reactコンポーネントからのフックのみを呼び出す
通常のJavaScript関数からフックを呼び出さないでください。 代わりに、React関数コンポーネントからフックを呼び出すことができます。 以下のJavaScript関数とReactコンポーネントの違いを見てみましょう。
JavaScript関数
import { useState } = "react"; function toCelsius(fahrenheit) { const [name, setName] = useState("David"); return (5/9) * (fahrenheit-32); } document.getElementById("demo").innerHTML = toCelsius;
ここでは、ReactパッケージからuseState
フックをインポートしてから、関数を宣言します。 ただし、これはReactコンポーネントではないため、無効です。
React関数
import React, { useState} from "react"; import ReactDOM from "react-dom"; function Account(props) { const [name, setName] = useState("David"); return <p>Hello, {name}! The price is <b>{props.total}</b> and the total amount is <b>{props.amount}</b></p> } ReactDom.render( <Account total={20} amount={5000} />, document.getElementById('root') );
両方の本体は似ていますが、Reactをファイルにインポートすると、後者がコンポーネントになります。 これにより、JSXやReactフックなどを内部で使用できるようになります。
Reactをインポートせずに好みのフックをインポートした場合(これにより通常の関数になります)、フックはReactコンポーネントでのみアクセス可能であるため、インポートしたフックを使用できなくなります。
カスタムフックからフックを呼び出す
カスタムフックは、名前がuse
で始まり、他のフックを呼び出す可能性のあるJavaScript関数です。 たとえば、 useUserName
は、 useState
フックとuseEffect
フックを呼び出すカスタムフックの下で使用されます。 APIからデータをフェッチし、データをループして、受け取った特定のユーザー名がAPIデータに存在する場合はsetIsPresent()
を呼び出します。
export default function useUserName(userName) { const [isPresent, setIsPresent] = useState(false); useEffect(() => { const data = MockedApi.fetchData(); data.then((res) => { res.forEach((e) => { if (e.name === userName) { setIsPresent(true); } }); }); }); return isPresent; }
次に、このフックの機能を、アプリケーションで必要な他の場所で再利用できます。 そのような場所では、必要な場合を除いて、 useState
またはuseEffect
を呼び出す必要はありません。
このルールに従うことで、コンポーネント内のすべてのステートフルロジックがソースコードから明確に表示されるようになります。
ESLintプラグイン
eslint eslint-plugin-react-hooks
と呼ばれるESLintプラグインは、上記のルールを適用します。 これは、プロジェクトで作業するときにルールを適用するのに役立ちます。 プロジェクトで作業するとき、特に他の人と作業するときは、このプラグインを使用することをお勧めします。 試してみたい場合は、このプラグインをプロジェクトに追加できます。
// Your ESLint configuration { "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies } }
このプラグインは、Create ReactAppにデフォルトで含まれています。 したがって、Create-React-Appを使用してReactアプリケーションをブートストラップする場合は、追加する必要はありません。
フックで考える
いくつかのフックのベストプラクティスに飛び込む前に、 class
コンポーネントと機能コンポーネント(フックを使用)を簡単に見てみましょう。
Reactでコンポーネントを定義する最も簡単な方法は、React要素を返すJavaScript関数を作成することです。
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
Welcome
コンポーネントは、データを含むオブジェクトであるprops
を受け入れ、React要素を返します。 次に、このコンポーネントを別のコンポーネントにインポートしてレンダリングできます。
class
コンポーネントは、カプセル化と呼ばれるプログラミング手法を使用します。これは、基本的に、クラスコンポーネントに関連するすべてのものがその中に存在することを意味します。 ライフサイクルメソッド( constructors
、 componentDidMount()
、 render
など)は、コンポーネントに予測可能な構造を提供します。
カプセル化は、 OOP (オブジェクト指向プログラミング)の基本の1つです。 これは、そのデータを操作するメソッド内のデータのバンドルを指し、クラス内の構造化データオブジェクトの値または状態を非表示にして、許可されていない第三者がそれらに直接アクセスするのを防ぐために使用されます。
フックを使用すると、コンポーネントの構成がライフサイクルフックの組み合わせから、最後にレンダリングが行われる機能に変わります。
機能コンポーネント
以下の例は、カスタムフックを機能コンポーネントで使用する方法を示しています(本体が何であるかを示すことなく)。 ただし、何ができるか、何ができるかは制限されていません。 状態変数のインスタンス化、コンテキストの消費、コンポーネントのさまざまな副作用のサブスクライブ、またはカスタムフックを使用している場合は上記のすべての可能性があります。
function { useHook{...}; useHook{...}; useHook{...}; return (
..。); }
クラスコンポーネント
class
コンポーネントでは、 React.Component
から拡張し、React要素を返すrender
関数を作成する必要があります。 これにはより多くのコードが必要ですが、いくつかの利点もあります。
class { constructor(props) {...} componentDidMount() {...} componentWillUnmount() {...} render() {...} }
Reactで機能コンポーネントを使用することで得られるいくつかの利点があります。
- コンポーネントで
setState()
にアクセスできない場合は、コンポーネントの状態について詳しく考える必要があるため、コンテナコンポーネントとプレゼンテーションコンポーネントを簡単に分離できます。 - 機能コンポーネントは、状態やライフサイクルフックのないプレーンなJavaScript関数であるため、読み取りとテストがはるかに簡単です。
- 最終的にコードが少なくなります。
- Reactチームは、将来のReactバージョンで機能コンポーネントのパフォーマンスが向上する可能性があると述べました。
これは、Reactフックを使用するときの最初のベストプラクティスにつながります。
フックのベストプラクティス
1.フックを簡素化する
React Hooksをシンプルに保つことで、コンポーネントの存続期間を通じてコンポーネントで何が起こっているかを効果的に制御および操作できるようになります。 カスタムフックを作成することはできるだけ避けてください。 独自のフックを作成する代わりに、 useState()
またはuseEffect()
)をインライン化できます。
機能に関連する一連のカスタムフックを使用していることに気付いた場合は、これらのラッパーとして機能するカスタムフックを作成できます。 以下のフックを備えた2つの異なる機能コンポーネントを見てみましょう。
機能コンポーネントv1
function { useHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
機能コンポーネントv2
function { useCustomHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
v2はフックをシンプルに保ち、他のすべてのuseHook
がそれに応じてインラインになるため、より優れたバージョンです。 これにより、さまざまなコンポーネント間で再利用できる機能を作成でき、コンポーネントを効果的に制御および操作するためのより強力な機能も提供されます。 コンポーネントにフックが散らばっているv1を採用する代わりに、デバッグを容易にし、コードをよりクリーンにするv2を使用する必要があります。
2.フックを整理して構造化する
React Hooksの利点の1つは、読みやすいコードをより少なく書くことができることです。 場合によっては、 useEffect()
とuseState()
)の量が依然として混乱する可能性があります。 コンポーネントを整理しておくと、読みやすさが向上し、コンポーネントのフローの一貫性と予測可能性が維持されます。 カスタムフックが複雑すぎる場合は、いつでもサブカスタムフックに分割できます。 コンポーネントのロジックをカスタムフックに抽出して、コードを読みやすくします。
3.Reactフックスニペットを使用する
React Hooks Snippetsは、ReactHooksをより簡単かつ高速にするVisualStudioCode拡張機能です。 現在、5つのフックがサポートされています。
-
useState()
-
useEffect()
-
useContext()
-
useCallback()
-
useMemo()
他のスニペットも追加されました。 私はこれらのフックを使ってみましたが、それは私がそれらを使って作業しているときに個人的に使用したベストプラクティスの1つです。
ReactHooksスニペットをプロジェクトに追加する方法は2つあります。
- 指示
VS Codeクイックオープン( Ctrl + P )を起動し、ext install ALDuncanson.react-hooks-snippets
を貼り付けて、 Enterキーを押します。 - 拡張マーケットプレイス
「VSCodeExtension Marketplace」( Ctrl + Shift + X )を起動し、「ReactHookSnippets」を検索します。 次に、「Alduncanson」アイコンを探します。
最初のスニペットをお勧めします。 ここでスニペットの詳細を読むか、ここで最新のフックスニペットを確認してください。
4.フックルールを考慮に入れる
React Hooksを使用するときは、以前に学習したフックの2つのルールを常に考慮に入れるようにしてください。
- トップレベルでのみフックを呼び出します。 ループ、条件、またはネストされた関数内でフックを呼び出さないでください。
- 常にReact関数コンポーネントまたはカスタムフックからフックを呼び出します。通常のJavaScript関数からフックを呼び出さないでください。
eslint-plugin-react-hooks
と呼ばれるESlintプラグインは、これら2つのルールを適用します。フックのルールのセクションで前述したように、必要に応じてこのプラグインをプロジェクトに追加できます。
フックはまだ比較的新しいため、ベストプラクティスは完全には解決されていません。 したがって、初期のテクノロジーで採用する場合に採用する場合は、慎重に採用する必要があります。 それを念頭に置いて、フックはReactの未来への道です。
結論
このチュートリアルを楽しんでいただけたでしょうか。 React Hooksの2つの最も重要なルールと、Hooksで効果的に考える方法を学びました。 フックを正しく効果的な方法で作成するために、機能コンポーネントといくつかのベストプラクティスを検討しました。 ルールは簡単ですが、ルールを作成するときは、ルールをガイドコンパスにすることが重要です。 忘れがちな場合は、ESLintプラグインを使用して強制することができます。
ここで学んだすべてのレッスンを次のReactプロジェクトで受講していただければ幸いです。 幸運を!
資力
- 「フックの紹介」、React Docs
- 「Reactの機能コンポーネントとクラスコンポーネント」、David Joch、Medium
- 「Mixinsは有害だと考えられています」、Dan Abramov、React Blog
- 「ReactHooks:ベストプラクティスと考え方の転換」、Bryan Manuele、Medium
- 「VSCodeのReactHooks Snippets」、Anthony Davis、Visual Code Marketplace