プロジェクトで使用できる便利なReactフック

公開: 2022-03-10
簡単な要約↬Reactクラスベースのコンポーネントは、人間やマシンにとって厄介で、混乱を招き、困難です。 しかし、React 16.8より前は、状態、ライフサイクルメソッド、およびその他の多くの重要な機能を必要とするプロジェクトでは、クラスベースのコンポーネントが必須でした。 これらはすべて、React16.8にフックが導入されたことで変更されました。 フックはゲームチェンジャーです。 彼らはReactを簡素化し、すっきりと書きやすく、デバッグしやすくし、学習曲線も短縮しました。

フックは、React機能にフックしたり利用したりできる機能です。 これらはReactConf2018で導入され、クラスコンポーネントの3つの主要な問題、ラッパーヘル、巨大なコンポーネント、および紛らわしいクラスに対処しました。 フックはReactの機能コンポーネントに力を与え、それを使ってアプリケーション全体を開発することを可能にします。

クラスコンポーネントの前述の問題は関連しており、一方を他方なしで解決すると、さらに問題が発生する可能性があります。 ありがたいことに、フックはReactのより興味深い機能のための余地を作りながら、すべての問題を簡単かつ効率的に解決しました。 フックは、既存のReactの概念とクラスを置き換えるものではなく、それらに直接アクセスするためのAPIを提供するだけです。

Reactチームは、React16.8にいくつかのフックを導入しました。 ただし、アプリケーションでサードパーティプロバイダーのフックを使用したり、カスタムフックを作成したりすることもできます。 このチュートリアルでは、Reactのいくつかの便利なフックとその使用方法を見ていきます。 各フックのいくつかのコード例を確認し、カスタムフックを作成する方法についても説明します。

注:このチュートリアルでは、Javascript(ES6 +)とReactの基本を理解している必要があります。

ジャンプした後もっと! 以下を読み続けてください↓

フックの背後にある動機

前に述べたように、フックは3つの問題を解決するために作成されました:ラッパー地獄、巨大なコンポーネント、そして紛らわしいクラス。 これらのそれぞれをより詳細に見てみましょう。

ラッパー地獄

クラスコンポーネントで構築された複雑なアプリケーションは、ラッパーの地獄に簡単にぶつかります。 React Dev Toolsでアプリケーションを調べると、深くネストされたコンポーネントに気付くでしょう。 これにより、コンポーネントの操作やデバッグが非常に困難になります。 これらの問題は高次のコンポーネントレンダリングプロップで解決できますが、コードを少し変更する必要があります。 これは、複雑なアプリケーションで混乱を招く可能性があります。

フックは簡単に共有でき、ロジックを再利用する前にコンポーネントを変更する必要はありません。

この良い例は、Redux connect Higher Order Component(HOC)を使用してReduxストアにサブスクライブすることです。 すべてのHOCと同様に、接続HOCを使用するには、定義された高階関数と一緒にコンポーネントをエクスポートする必要があります。 connectの場合、この形式のものがあります。

 export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

ここで、 mapStateToPropsmapDispatchToPropsは定義する関数です。

一方、フックの時代では、ReduxのuseSelectorフックとuseDispatchフックを使用することで、同じ結果を簡単かつ簡潔に実現できます。

巨大なコンポーネント

クラスコンポーネントには通常、副作用とステートフルロジックが含まれています。 アプリケーションの複雑さが増すにつれて、コンポーネントが乱雑で混乱するのが一般的です。 これは、副作用が機能ではなくライフサイクルメソッドによって整理されることが予想されるためです。 コンポーネントを分割して単純化することは可能ですが、これにより、より高いレベルの抽象化が導入されることがよくあります。

フックは機能ごとに副作用を整理し、機能に基づいてコンポーネントを分割することができます。

紛らわしいクラス

クラスは一般に、関数よりも難しい概念です。 Reactクラスベースのコンポーネントは冗長で、初心者には少し難しいです。 Javascriptを初めて使用する場合は、クラスと比較して構文が軽量であるため、関数を使い始めるのが簡単です。 構文が混乱する可能性があります。 場合によっては、コードを壊す可能性のあるイベントハンドラーのバインドを忘れることがあります。

Reactは、機能コンポーネントとフックを使用してこの問題を解決し、開発者がコード構文ではなくプロジェクトに集中できるようにします。

たとえば、次の2つのReactコンポーネントは、まったく同じ結果になります。

 import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
 import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }

最初の例はクラスベースのコンポーネントであり、2番目の例は機能コンポーネントです。 これは単純な例ですが、最初の例が2番目の例と比較していかに偽物であるかに注意してください。

フックのコンベンションとルール

さまざまなフックを詳しく調べる前に、それらに適用される規則と規則を確認すると役立つ場合があります。 フックに適用されるルールのいくつかを次に示します。

  1. フックの命名規則は、接頭辞useで始まる必要があります。 したがって、useState、useEffectなどを使用できますuseStateuseEffectなどの最新のコードエディターを使用している場合、ESLintプラグインはReactフックにとって非常に便利な機能になる可能性があります。 プラグインは、ベストプラクティスに関する有用な警告とヒントを提供します。
  2. フックは、returnステートメントの前に、コンポーネントの最上位で呼び出す必要があります。 条件文、ループ、またはネストされた関数内で呼び出すことはできません。
  3. フックは、React関数(Reactコンポーネントまたは別のフック内)から呼び出す必要があります。 VanillaJS関数から呼び出すべきではありません。

useStateフック

useStateフックは、最も基本的で便利なReactフックです。 他の組み込みフックと同様に、このフックは、アプリケーションで使用するためにreactからインポートする必要があります。

 import {useState} from 'react'

状態を初期化するには、状態とそのアップデータ関数の両方を宣言し、初期値を渡す必要があります。

 const [state, updaterFn] = useState('')

状態とアップデータ関数は自由に呼び出すことができますが、慣例により、配列の最初の要素が状態になり、2番目の要素がアップデータ関数になります。 アップデータ関数の前にプレフィックスセットを付け、その後にキャメルケース形式の州名を付けるのが一般的な方法です。

たとえば、カウント値を保持する状態を設定しましょう。

 const [count, setCount] = useState(0)

count状態の初期値が0に設定されており、空の文字列ではないことに注意してください。 つまり、状態を任意の種類のJavaScript変数、つまり数値、文字列、ブール値、配列、オブジェクト、さらにはBigIntに初期化できます。 useStateフックを使用した状態の設定とクラスベースのコンポーネントの状態には明らかな違いがあります。 useStateフックが配列(状態変数とも呼ばれる)を返すことは注目に値します。上記の例では、配列をstateupdater関数に分解しました。

コンポーネントのレンダリング

useStateフックを使用して状態を設定すると、対応するコンポーネントが再レンダリングされます。 ただし、これは、Reactが以前の状態または古い状態と新しい状態の違いを検出した場合にのみ発生します。 Reactは、 Object.isアルゴリズムを使用して状態比較を行います。

useStateを使用した状態の設定

次のようにsetCountアップデータ関数に新しい値を渡すだけで、 count状態を新しい状態値に設定できsetCount(newValue)

このメソッドは、以前の状態値を参照したくない場合に機能します。 これを行うには、関数をsetCount関数に渡す必要があります。

ボタンがクリックされるたびにcount変数に5を追加したいとすると、次のようになります。

 import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample

上記のコードでは、最初にreactからuseStateフックをインポートし、次にデフォルト値0でcount状態を初期化しました。ボタンがクリックされるたびにcountの値を5ずつインクリメントするonClickハンドラーを作成しました。 次に、結果をh1タグで表示しました。

配列とオブジェクトの状態の設定

配列とオブジェクトの状態は、他のデータ型とほぼ同じ方法で設定できます。 ただし、既存の値を保持したい場合は、状態を設定するときにES6スプレッド演算子を使用する必要があります。

Javascriptのspread演算子は、既存のオブジェクトから新しいオブジェクトを作成するために使用されます。 Reactは状態をObject.is操作と比較し、それに応じて再レンダリングするため、これはここで役立ちます。

ボタンクリックで状態を設定するための以下のコードを考えてみましょう。

 import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample

上記のコードでは、2つの状態arrobjを作成し、それらをそれぞれいくつかの配列値とオブジェクト値に初期化しました。 次に、 handleArrClickおよびhandleObjClickというonClickハンドラーを作成して、それぞれ配列とオブジェクトの状態を設定しました。 handleArrClickが起動すると、 setArrを呼び出し、ES6 Spread演算子を使用して、既存の配列値を拡散し、それにnewArrを追加します。

handleObjClickハンドラーに対しても同じことを行いました。 ここでは、 setObjを呼び出し、ES6 Spread演算子を使用して既存のオブジェクト値を拡散し、 nameageの値を更新しました。

useStateの非同期の性質

すでに見てきたように、アップデーター関数に新しい値を渡すことにより、 useStateで状態を設定します。 アップデータが複数回呼び出された場合、新しい値がキューに追加され、 Object.is比較を使用してそれに応じて再レンダリングが行われます。

状態は非同期で更新されます。 これは、新しい状態が最初に保留状態に追加され、その後、状態が更新されることを意味します。 したがって、設定された状態にすぐにアクセスすると、古い状態の値を取得できる場合があります。

この動作を観察するために、次の例を考えてみましょう。

上記のコードでは、 useStateフックを使用してcount状態を作成しました。 次に、ボタンがクリックされるたびにcount状態をインクリメントするonClickハンドラーを作成しました。 h2タグに表示されているように、 count状態は増加しましたが、以前の状態は引き続きコンソールに記録されていることに注意してください。 これは、フックの非同期性によるものです。

新しい状態を取得したい場合は、非同期関数を処理するのと同じ方法で処理できます。 これを行う1つの方法があります。

ここでは、作成されたnewCountValueを保存して、更新されたカウント値を保存してから、更新された値でcount状態を設定しました。 次に、更新されたカウント値をコンソールに記録しました。

useEffectフック

useEffectは、ほとんどのプロジェクトで使用されるもう1つの重要なReactフックです。 これは、クラスベースのコンポーネントのcomponentDidMountcomponentWillUnmount 、およびcomponentDidUpdateライフサイクルメソッドと同様のことを行います。 useEffectは、アプリケーションに副作用をもたらす可能性のある命令コードを作成する機会を提供します。 このような影響の例には、ロギング、サブスクリプション、ミューテーションなどが含まれます。

ユーザーはuseEffectをいつ実行するかを決定できますが、設定されていない場合、副作用はすべてのレンダリングまたは再レンダリングで実行されます。

以下の例を考えてみましょう。

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }

上記のコードでは、 useEffectcountを記録しただけです。 これは、コンポーネントがレンダリングされるたびに実行されます。

コンポーネントでフックを(マウント上で)1回実行したい場合があります。 これは、 useEffectフックに2番目のパラメーターを提供することで実現できます。

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }

useEffectフックには2つのパラメーターがあり、最初のパラメーターは実行する関数であり、2番目のパラメーターは依存関係の配列です。 2番目のパラメーターが指定されていない場合、フックは継続的に実行されます。

空の角かっこをフックの2番目のパラメーターに渡すことにより、マウントでuseEffectフックを1回だけ実行するようにReactに指示します。 コンポーネントがマウントされると、カウントが0から1に1回更新されるため、これによりh1タグに値1が表示されます。

また、いくつかの依存値が変更されるたびに副作用を実行させることもできます。 これは、依存関係のリストでこれらの値を渡すことによって実行できます。

たとえば、次のようにcountが変更されるたびにuseEffectを実行することができます。

 import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;

上記のuseEffectは、これら2つの条件のいずれかが満たされたときに実行されます。

  1. マウント時—コンポーネントがレンダリングされた後。
  2. countの値が変わったとき。

マウント時に、 console.log式が実行され、 countが0に記録されます。 countが更新されると、2番目の条件が満たされるため、 useEffectが再度実行され、ボタンがクリックされるたびにこれが続行されます。

useEffectに2番目の引数を指定すると、すべての依存関係が渡されることが期待されます。 ESLINTがインストールされている場合、依存関係がパラメーターリストに渡されないと、lintエラーが表示されます。 これにより、特に渡されないパラメーターに依存する場合は、副作用が予期しない動作をする可能性もあります。

効果のクリーンアップ

useEffectを使用すると、コンポーネントをアンマウントする前にリソースをクリーンアップすることもできます。 これは、メモリリークを防ぎ、アプリケーションをより効率的にするために必要になる場合があります。 これを行うには、フックの最後にクリーンアップ関数を返します。

 useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })

上記のuseEffectフックは、コンポーネントがマウントされたときにログmountedれます。 アンマウント…コンポーネントがアンマウントされると、ここでのクリーンアップがログに記録されます。 これは、コンポーネントがUIから削除されたときに発生する可能性があります。

クリーンアッププロセスは通常、以下の形式に従います。

 useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })

useEffectサブスクリプションのユースケースはそれほど多くないかもしれませんが、サブスクリプションとタイマーを処理する場合に役立ちます。 特に、Webソケットを処理する場合、リソースを節約し、コンポーネントのアンマウント時のパフォーマンスを向上させるために、ネットワークからのサブスクライブを解除する必要がある場合があります。

useEffectを使用したデータのフェッチと再フェッチ

useEffectフックの最も一般的なユースケースの1つは、APIからのデータのフェッチとプリフェッチです。

これを説明するために、 JSONPlaceholderから作成した偽のユーザーデータを使用して、 useEffectフックでデータをフェッチします。

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }

上記のコードでは、 useStateフックを使用してusers状態を作成しました。 次に、Axiosを使用してAPIからデータをフェッチしました。 これは非同期プロセスであるため、async / await関数を使用したので、ドットを使用してから構文を使用することもできます。 ユーザーのリストを取得したので、それをマッピングしてデータを表示しました。

空のパラメータをフックに渡したことに注意してください。 これにより、コンポーネントのマウント時に1回だけ呼び出されるようになります。

条件が変わったときにデータを再フェッチすることもできます。 これを以下のコードで示します。

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }

ここでは、2つのuseEffectフックを作成しました。 最初の例では、ドットと構文を使用して、APIからすべてのユーザーを取得しました。 これは、ユーザー数を決定するために必要です。

次に、別のuseEffectフックを作成して、 idに基づいてユーザーを取得しました。 このuseEffectは、IDが変更されるたびにデータを再フェッチします。 これを確実にするために、依存関係リストでidを渡しました。

次に、ボタンがクリックされるたびにidの値を更新する関数を作成しました。 idの値が変更されると、 useEffectが再度実行され、データが再フェッチされます。

必要に応じて、Axiosでpromiseベースのトークンをクリーンアップまたはキャンセルすることもできます。これは、上記のクリーンアップ方法を使用して行うことができます。

 useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);

ここでは、Axiosのトークンを2番目のパラメーターとしてaxios.getに渡しました。 コンポーネントがアンマウントされると、ソースオブジェクトのcancelメソッドを呼び出してサブスクリプションをキャンセルしました。

useReducerフック

useReducerフックは、 useStateフックと同様のことを行う非常に便利なReactフックです。 Reactのドキュメントによると、このフックは、 useStateフックよりも複雑なロジックを処理するために使用する必要があります。 useStateフックはuseStateフックで内部的に実装されていることに注意してください。

フックはレデューサーを引数として取り、オプションで初期状態とinit関数を引数として取ることができます。

 const [state, dispatch] = useReducer(reducer, initialState, init)

ここで、 initは関数であり、初期状態を怠惰に作成したいときに使用されます。

以下のサンドボックスに示すように、簡単なTo Doアプリを作成して、 useReducerフックを実装する方法を見てみましょう。

Todoの例

まず、状態を保持するためのレデューサーを作成する必要があります。

 export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;

アクションタイプに対応する3つの定数を作成しました。 文字列を直接使用することもできますが、タイプミスを避けるためにこの方法をお勧めします。

次に、レデューサー関数を作成しました。 Reduxと同様に、レデューサーは状態とアクションオブジェクトを取得する必要があります。 ただし、Reduxとは異なり、ここでレデューサーを初期化する必要はありません。

さらに、多くの状態管理のユースケースでは、 useReducerとコンテキストを介して公開されるdispatchにより、より大きなアプリケーションでアクションを実行し、 stateを更新してリッスンすることができます。

次に、 switchステートメントを使用して、ユーザーから渡されたアクションタイプを確認しました。 アクションタイプがADD_TODOの場合、新しいTo Doを渡し、REMOVE_TODOの場合、 REMOVE_TODOをフィルタリングして、ユーザーが渡したidに対応するものを削除します。 COMPLETE_TODOの場合は、To Doをマップして、ユーザーから渡されたidでタスクを切り替えます。

これが、 reducerを実装したApp.jsファイルです。

 import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }

ここでは、ユーザーの入力を収集するためのinput要素と、アクションをトリガーするためのボタンを含むフォームを作成しました。 フォームが送信されると、タイプADD_TODOのアクションがディスパッチされ、新しいIDとToDoテキストが渡されます。 以前のID値を1ずつ増やして、新しいIDを作成しました。次に、入力テキストボックスをクリアしました。 To Doを削除して完了するには、適切なアクションをディスパッチするだけです。 これらは、上記のようにレデューサーにすでに実装されています。

ただし、 useReducerフックを使用しているため、魔法が発生します。 このフックは、レデューサーと初期状態を受け入れ、状態とディスパッチ関数を返します。 ここで、dispatch関数はuseStateフックのsetter関数と同じ目的を果たし、 dispatchの代わりに任意の名前を呼び出すことができます。

To Do項目を表示するには、上記のコードに示すように、状態オブジェクトで返されたToDoのリストを単純にマッピングしました。

これは、 useReducerフックの能力を示しています。 useStateフックを使用してこの機能を実現することもできますが、上記の例からわかるように、 useReducerフックは物事をすっきりさせるのに役立ちました。 useReducerは、状態オブジェクトが複雑な構造であり、単純な値の置換とは異なる方法で更新される場合に役立つことがよくあります。 また、これらの更新関数がより複雑になると、 useReducerを使用すると、レデューサー関数(純粋なJS関数)でその複雑さをすべて簡単に保持できるため、レデューサー関数のみのテストを非常に簡単に作成できます。

3番目の引数をuseReducerフックに渡して、初期状態を遅延的に作成することもできます。 これは、 init関数で初期状態を計算できることを意味します。

たとえば、次のようにinit関数を作成できます。

 const initFunc = () => [ { id: id, text: "First Item", completed: false } ]

次に、それをuseReducerフックに渡します。

 const [state, dispatch] = useReducer(reducer, initialState, initFunc)

これを行うと、 initFuncは指定したinitialStateをオーバーライドし、初期状態は遅延して計算されます。

useContextフック

React Context APIは、Reactコンポーネントツリー全体で状態またはデータを共有する方法を提供します。 APIは、実験的な機能としてReactでしばらくの間利用可能でしたが、React16.3.0で安全に使用できるようになりました。 APIを使用すると、プロップの穴あけを排除しながら、コンポーネント間のデータ共有が簡単になります。

React Contextをアプリケーション全体に適用できますが、アプリケーションの一部に適用することもできます。

フックを使用するには、最初にReact.createContextを使用してコンテキストを作成する必要があります。その後、このコンテキストをフックに渡すことができます。

useContextフックの使用法を示すために、アプリケーション全体でフォントサイズを大きくする簡単なアプリを作成しましょう。

context.jsファイルにコンテキストを作成しましょう。

 import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;

ここでは、コンテキストを作成し、それに初期値16を渡してから、コンテキストをエクスポートしました。 次に、コンテキストをアプリケーションに接続しましょう。

 import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;

上記のコードでは、コンポーネントツリー全体をFontSizeContext.Providerでラップし、 sizeをその値propに渡しました。 ここで、 sizeは、 useStateフックを使用して作成された状態です。 これにより、 sizeの状態が変化するたびに値propを変更できます。 コンポーネント全体をProviderでラップすることにより、アプリケーションのどこからでもコンテキストにアクセスできます。

たとえば、 <PageOne /><PageTwo /> >のコンテキストにアクセスしました。 この結果、 App.jsファイルからフォントサイズを大きくすると、これら2つのコンポーネント間でフォントサイズが大きくなります。 上記のようにボタンからフォントサイズを増減できます。そうすると、アプリケーション全体でフォントサイズが変更されます。

 import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;

ここでは、 PageOneコンポーネントのuseContextフックを使用してコンテキストにアクセスしました。 次に、このコンテキストを使用してfont-sizeプロパティを設定しました。 同様の手順がPageTwo.jsファイルに適用されます。

テーマまたはその他の高次のアプリレベルの構成は、コンテキストの候補として適しています。

useContextuseReducer使用

useReducerフックと一緒に使用すると、 useContextを使用して独自の状態管理システムを作成できます。 グローバル状態を作成し、アプリケーションで簡単に管理できます。

コンテキストAPIを使用してToDoアプリケーションを改善しましょう。

いつものように、 todoContext.jsファイルにtodoContextを作成する必要があります。

 import { createContext } from "react"; const initialState = []; export default createContext(initialState);

ここでは、空の配列の初期値を渡して、コンテキストを作成しました。 次に、コンテキストをエクスポートしました。

やることリストと項目を分けて、 App.jsファイルをリファクタリングしましょう。

 import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }

ここでは、 App.jsファイルをTodoContext.Providerでラップしてから、 todoReducerの戻り値を渡しました。 これにより、レデューサーの状態とdispatch機能にアプリケーション全体でアクセスできるようになります。

次に、ToDo表示をコンポーネントTodoListに分割しました。 Context APIのおかげで、プロップドリルなしでこれを行いました。 TodoList.jsファイルを見てみましょう。

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;

配列の破棄を使用すると、 useContextフックを使用して、コンテキストから状態にアクセスできます(ディスパッチ関数を終了します)。 次に、状態をマッピングして、やること項目を表示できます。 これはまだTodoコンポーネントで抽出しました。 ES6 +マップ関数では、一意のキーを渡す必要があります。特定のTo Doが必要なため、それも一緒に渡します。

Todoコンポーネントを見てみましょう。

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;

再び配列の破棄を使用して、コンテキストからディスパッチ関数にアクセスしました。 これにより、 useReducerセクションですでに説明したように、 completeTodo関数とremoveTodo関数を定義できます。 todoから渡されたtodoList.jsを使用して、to-doアイテムを表示できます。 また、完了としてマークを付け、適切と思われるToDoを削除することもできます。

アプリケーションのルートに複数のコンテキストプロバイダーをネストすることもできます。 これは、複数のコンテキストを使用して、アプリケーションでさまざまな機能を実行できることを意味します。

これを示すために、to-doの例にテーマを追加しましょう。

これが私たちが構築するものです。

ここでも、 themeContextを作成する必要があります。 これを行うには、 themeContext.jsファイルを作成し、次のコードを追加します。

 import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);

ここでは、コンテキストを作成し、 colors.lightを初期値として渡しました。 colors.jsファイルでこのプロパティを使用して色を定義しましょう。

 const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;

上記のコードでは、明るいプロパティと暗いプロパティを含むcolorsオブジェクトを作成しました。 各プロパティには、 backgroundColorcolorオブジェクトがあります。

次に、テーマの状態を処理するためのthemeReducerを作成します。

 import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;

すべてのレデューサーと同様に、 themeReducerは状態とアクションを実行します。 次に、 switchステートメントを使用して現在のアクションを判別します。 タイプがLIGHTの場合は、 Colors.lightの小道具を割り当てるだけで、タイプがDARKの場合は、 Colors.darkの小道具を表示します。 useStateフックを使用してこれを簡単に行うことができますが、ポイントをホームにドライブするためにuseReducerを選択します。

themeReducerを設定したら、それをApp.jsファイルに統合できます。

 import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }

上記のコードでは、既存のToDoアプリケーションにいくつか追加しました。 まず、 ThemeContextthemeReducerThemeToggler 、およびColorsをインポートしました。 useReducerフックを使用してレデューサーを作成し、 themeReducerとColors.lightの初期値をColors.lightます。 This returned the themeState and themeDispatch to us.

We then nested our component with the provider function from the ThemeContext , passing the themeState and the dispatch functions to it. We also added theme styles to it by spreading out the themeStates . This works because the colors object already defined properties similar to what the JSX styles will accept.

However, the actual theme toggling happens in the ThemeToggler component. それを見てみましょう。

 import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;

In this component, we used the useContext hook to retrieve the values we passed to the ThemeContext.Provider from our App.js file. As shown above, these values include the ThemeState , dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight to determine the current theme. This allows us to easily change the button text depending on the current theme.

The useMemo Hook

The useMemo hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.

The hook can be used as follows:

 const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])

Let's consider three cases of the useMemo hook.

  1. When the dependency values, a and b remain the same.
    The useMemo hook will return the already computed memoized value without recomputation.
  2. When the dependency values, a and b change.
    The hook will recompute the value.
  3. When no dependency value is passed.
    The hook will recompute the value.

Let's take a look at an example to demonstrate this concept.

In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.

The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.

This is shown in the sandbox below.

First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).

const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);

最初のuseEffect axios async/awaitメソッドを使用し、2番目ではドットと構文を使用しました。 これらの2つのアプローチは同じように機能します。

次に、上記から取得した従業員データを使用して、救済変数を計算しましょう。

 const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);

これはかなり複雑な計算であるため、メモ化または最適化するためにuseMemoフックでラップする必要がありました。 このようにメモ化すると、同じ従業員に再度アクセスしようとした場合に計算が再計算されないようになります。

さらに、上記で得られた免税額を使用して、PAYEとPAYE後の収入を計算したいと思います。

 const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);

上記で計算した税変数を使用して税計算(かなり複雑な計算)を実行し、 useMemoフックでメモ化しました。

完全なコードはここから入手できます。

これは、ここに示す税計算手順に従います。 まず、所得、子供の数、扶養家族の数を考慮して免税額を計算しました。 次に、課税所得にPIT率を段階的に掛けました。 問題の計算はこのチュートリアルに完全に必要なわけではありませんが、 useMemoが必要な理由を示すために提供されています。 これもかなり複雑な計算なので、上記のようにuseMemoで記憶する必要があるかもしれません。

値を計算した後、結果を表示するだけです。

useMemoフックについては次の点に注意してください。

  • useMemoは、計算を最適化する必要がある場合にのみ使用してください。 言い換えれば、再計算に費用がかかる場合です。
  • 最初に計算を暗記せずに記述し、パフォーマンスの問題が発生している場合にのみ暗記することをお勧めします。
  • useMemoフックの不必要で無関係な使用は、パフォーマンスの問題を悪化させる可能性さえあります。
  • 場合によっては、メモ化が多すぎるとパフォーマンスの問題が発生することもあります。

useCallbackフック

useCallbackは、 useMemoと同じ目的を果たしますが、メモ化された値ではなく、メモ化されたコールバックを返します。 つまり、 useCallbackは、関数呼び出しなしでuseMemoを渡すことと同じです。

たとえば、以下のコードについて考えてみます。

 import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample

上記の例では、 memoResultcallbackResultの両方が同じ値の12を返します。 ここで、 useCallbackはメモ化された値を返します。 ただし、関数として渡すことで、メモ化されたコールバックを返すようにすることもできます。

以下のuseCallbackは、メモ化されたコールバックを返します。

 ... const callbackResult = useCallback(() => a + b, [a, b]) ...

その後、アクションが実行されたとき、またはuseEffectフックでコールバックをトリガーできます。

 import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample

上記のコードでは、 useCallbackフックを使用してコールバック関数を定義しました。 次に、コンポーネントがマウントされたとき、およびボタンがクリックされたときに、 useEffectフックでコールバックを呼び出しました。

useEffectとボタンクリックの両方で同じ結果が得られます。

useMemoフックに適用される概念、すべきこと、およびすべきでないことは、 useMemoフックにも適用されることに注意してuseCallbackuseCallbackを使用してuseMemoの例を再作成できます。

useRefフック

useRefは、アプリケーションで永続化できるオブジェクトを返します。 フックにはcurrentという1つのプロパティしかなく、引数を簡単に渡すことができます。

これは、クラスベースのコンポーネントで使用されるcreateRefと同じ目的を果たします。 このフックを使用して、次のように参照を作成できます。

 const newRef = useRef('')

ここでは、 newRefという新しい参照を作成し、それに空の文字列を渡しました。

このフックは主に2つの目的で使用されます。

  1. DOMへのアクセスまたは操作、および
  2. 可変状態の保存—これは、値が変更されたときにコンポーネントを再レンダリングしたくない場合に役立ちます。

DOMの操作

DOM要素に渡されると、refオブジェクトはその要素を指し、そのDOM属性とプロパティにアクセスするために使用できます。

これは、この概念を示す非常に簡単な例です。

 import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample

上記の例では、空の文字列を渡すuseRefフックを使用してheadingRefを定義しました。 次に、 ref = {headingRef}を渡して、 h1タグにrefを設定します。 このrefを設定することにより、 headingRefh1要素を指すように要求しました。 これは、refからh1要素のプロパティにアクセスできることを意味します。

これを確認するには、 console.log(headingRef)の値を確認すると、 {current: HTMLHeadingElement}または{current: h1}が取得され、要素のすべてのプロパティまたは属性を評価できます。 同様のことが他のHTML要素にも当てはまります。

たとえば、コンポーネントがマウントされるときにテキストを斜体にすることができます。

 useEffect(() => { headingRef.current.style.font; }, []);

テキストを別のものに変更することもできます。

 ... headingRef.current.innerHTML = "A Changed H1 Element"; ...

親コンテナの背景色を変更することもできます。

 ... headingRef.current.parentNode.style.backgroundColor = "red"; ...

ここでは、あらゆる種類のDOM操作を実行できます。 headingRef.currentは、 document.querySelector('.topheading')と同じ方法で読み取ることができることに注意してください。

DOM要素を操作する際のuseRefフックの興味深いユースケースの1つは、カーソルを入力要素にフォーカスすることです。 すぐに実行してみましょう。

 import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample

上記のコードでは、 useRefフックを使用してinputRefを作成し、input要素を指すように要求しました。 次に、コンポーネントがロードされたとき、およびinputRef.current.focus()を使用してボタンがクリックされたときに、カーソルを入力refにフォーカスさせました。 これが可能なのは、 focus()が入力要素の属性であり、refがメソッドを評価できるためです。

親コンポーネントで作成された参照は、 React.forwardRef()を使用して転送することにより、子コンポーネントで評価できます。 それを見てみましょう。

まず、別のコンポーネントNewInput.jsを作成し、それに次のコードを追加しましょう。

 import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;

このコンポーネントは、 propsrefを受け入れます。 refをそのrefpropに渡し、 props.valをそのプレースホルダーpropに渡しました。 通常のReactコンポーネントはref属性を取りません。 この属性は、上記のようにReact.forwardRefでラップした場合にのみ使用できます。

これを親コンポーネントで簡単に呼び出すことができます。

 ... <NewInput val="Just an example" ref={inputRef} /> ...

可変状態の保存

参照は、DOM要素を操作するために使用されるだけでなく、コンポーネント全体を再レンダリングせずに可変値を格納するためにも使用できます。

次の例では、コンポーネントを再レンダリングせずにボタンがクリックされた回数を検出します。

 import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }

上記のコードでは、ボタンがクリックされたときにcountRefをインクリメントし、コンソールに記録しました。 コンソールに表示されているように値は増加しますが、コンポーネントで直接評価しようとすると、変化を確認できません。 コンポーネントが再レンダリングされるときにのみ更新されます。

useStateは非同期ですが、 useRefは同期であることに注意してください。 つまり、値は更新された直後に使用可能になります。

useLayoutEffectフック

useEffectフックと同様に、 useLayoutEffectは、コンポーネントがマウントおよびレンダリングされた後に呼び出されます。 このフックはDOMミューテーションの後に起動し、同期的に起動します。 useLayoutEffectは、DOMミューテーションの後に同期的に呼び出されることを除けば、 useEffectと同じことを行います。

useLayoutEffectは、DOMミューテーションまたはDOM関連の測定を実行するためにのみ使用する必要があります。それ以外の場合は、 useEffectフックを使用する必要があります。 DOMミューテーション関数にuseEffectフックを使用すると、ちらつきなどのパフォーマンスの問題が発生する可能性がありますが、 useLayoutEffectは、ミューテーションが発生した後に実行されるため、それらを完全に処理します。

この概念を示すために、いくつかの例を見てみましょう。

  1. サイズ変更時にウィンドウの幅と高さを取得します。
 import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample

上記のコードでは、widthプロパティとheightプロパティを持つ状態windowSizeを作成しました。 次に、ウィンドウのサイズが変更されたときに、状態をそれぞれ現在のウィンドウの幅と高さに設定します。 また、アンマウント時にコードをクリーンアップしました。 useLayoutEffect 、DOM操作をクリーンアップして効率を向上させるために、クリーンアッププロセスが不可欠です。

  1. useLayoutEffectを使用してテキストをぼかしましょう。
 import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }

上記のコードでは、 useRefuseLayoutEffectを一緒に使用しました。 最初に、段落を指すref、 paragraphRefを作成しました。 次に、クリック時のイベントリスナーを作成して、段落がクリックされたときに監視し、定義したスタイルプロパティを使用して段落をぼかします。 最後に、 removeEventListenerを使用してイベントリスナーをクリーンアップしました。

useDispatchおよびuseSelectorフック

useDispatchは、アプリケーションでアクションをディスパッチ(トリガー)するためのReduxフックです。 アクションオブジェクトを引数として取り、アクションを呼び出します。 useDispatchは、フックがmapDispatchToPropsと同等です。

一方、 useSelectorはReduxの状態を評価するためのReduxフックです。 ストアから正確なReduxレデューサーを選択し、対応する状態を返す関数を取ります。

ReduxストアがReduxプロバイダーを介してReactアプリケーションに接続されると、useDispatchを使用してアクションを呼び出し、 useDispatchを使用して状態にアクセスできuseSelector 。 すべてのReduxアクションと状態は、これら2つのフックで評価できます。

これらの状態には、React Redux(ReactアプリケーションでReduxストアを簡単に評価できるパッケージ)が付属していることに注意してください。 コアReduxライブラリでは利用できません。

これらのフックは非常に簡単に使用できます。 まず、ディスパッチ関数を宣言してからトリガーする必要があります。

 import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample

上記のコードでは、 react-reduxからuseDispatchuseSelectorをインポートしました。 次に、 useEffectフックで、アクションをディスパッチしました。 別のファイルでアクションを定義してからここで呼び出すか、 useEffect呼び出しに示されているように直接定義することができます。

アクションをディスパッチすると、州が利用可能になります。 次に、図のようにuseSelectorフックを使用して状態を取得できます。 状態は、 useStateフックからの状態を使用するのと同じ方法で使用できます。

これらの2つのフックを示す例を見てみましょう。

この概念を示すために、Reduxストア、レデューサー、およびアクションを作成する必要があります。 ここでは簡単にするために、JSONPlaceholderの偽のデータベースでReduxToolkitライブラリを使用します。

開始するには、次のパッケージをインストールする必要があります。 次のbashコマンドを実行します。

 npm i redux @reduxjs/toolkit react-redux axios

まず、 employeesSlice.jsを作成して、従業員のAPIのレデューサーとアクションを処理しましょう。

 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;

これは、Reduxツールキットの標準セットアップです。 createAsyncThunkを使用してThunkミドルウェアにアクセスし、非同期アクションを実行しました。 これにより、APIから従業員のリストを取得できました。 次に、 employeesSliceを作成し、アクションタイプに応じて、「読み込み中」、「エラー」、および従業員のデータを返しました。

Reduxツールキットを使用すると、ストアのセットアップも簡単になります。 こちらがお店です。

 import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;

ここでは、 combineReducersを使用してレデューサーをバンドルし、Reduxツールキットが提供するconfigureStore関数を使用してストアをセットアップしました。

これをアプリケーションで使用してみましょう。

まず、ReduxをReactアプリケーションに接続する必要があります。 理想的には、これはアプリケーションのルートで実行する必要があります。 私はindex.jsファイルでそれを行うのが好きです。

 import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );

ここでは、上記で作成したストアと、 react-reduxからProviderをインポートしました。

次に、アプリケーション全体をProvider関数でラップし、ストアを渡します。 これにより、アプリケーション全体でストアにアクセスできるようになります。

次に、 useDispatchフックとuseSelectorフックを使用してデータをフェッチします。

App.jsファイルでこれを実行しましょう。

 import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }

上記のコードでは、 useDispatchフックを使用して、 employeesSlice.jsファイルに作成されたfetchEmployeesアクションを呼び出しました。 これにより、従業員はアプリケーションで利用できるようになります。 次に、 useSelectorフックを使用して状態を取得しました。 その後、 employeesを介してマッピングして結果を表示しました。

useHistoryフック

Reactアプリケーションではナビゲーションが非常に重要です。 これはいくつかの方法で実現できますが、React Routerは、Reactアプリケーションで動的ルーティングを実現するためのシンプルで効率的で一般的な方法を提供します。 さらに、React Routerには、ルーターの状態を評価し、ブラウザーでナビゲーションを実行するためのフックがいくつか用意されていますが、それらを使用するには、最初にアプリケーションを適切にセットアップする必要があります。

React Routerフックを使用するには、最初にアプリケーションをBrowserRouterでラップする必要があります。 次に、 SwitchRouteを使用してルートをネストできます。

ただし、最初に、次のコマンドを実行してパッケージをインストールする必要があります。

 npm install react-router-dom

次に、アプリケーションを次のように設定する必要があります。 App.jsファイルでこれを行うのが好きです。

 import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }

レンダリングするコンポーネントの数に応じて、できるだけ多くのルートを作成できます。 ここでは、 Employeesコンポーネントのみをレンダリングしました。 path属性は、React Router DOMにコンポーネントのパスを通知し、クエリ文字列またはその他のさまざまな方法で評価できます。

ここでは順序が重要です。 ルートルートは、子ルートの下などに配置する必要があります。 この順序を上書きするには、ルートルートにexactキーワードを含める必要があります。

 <Route path='/' exact > <Employees /> </Route>

ルーターを設定したので、アプリケーションでuseHistoryフックと他のReactルーターフックを使用できます。

useHistoryフックを使用するには、最初に次のように宣言する必要があります。

 import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }

履歴をコンソールに記録すると、それに関連付けられたいくつかのプロパティが表示されます。 これらには、 blockcreateHrefgogoBackgoForwardlengthlistenlocationpushreplace含まれます。 これらのプロパティはすべて便利ですが、 history.pushhistory.replaceを他のプロパティよりも頻繁に使用する可能性があります。

このプロパティを使用して、あるページから別のページに移動してみましょう。

特定の従業員の名前をクリックしたときに、その従業員に関するデータを取得したいとします。 useHistoryフックを使用して、従業員の情報が表示される新しいページに移動できます。

 function moveToPage = (id) =>{ history.push(`/employees/${id}`) }

以下を追加することで、これをEmployee.jsファイルに実装できます。

 import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }

pushToPage関数では、 useHistoryフックからのhistoryを使用して従業員のページに移動し、従業員IDを一緒に渡しました。

useLocationフック

このフックは、ReactRouterDOMにも付属しています。 これは、クエリ文字列パラメータを操作するために使用される非常に人気のあるフックです。 このフックは、ブラウザのwindow.locationに似ています。

 import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample

useLocationフックは、 pathnamesearchパラメーター、 hash 、およびstateを返します。 最も一般的に使用されるパラメーターには、 pathnamesearchが含まれますが、同様にhashを使用して、アプリケーションで多くのことをstateことができます。

location pathnameプロパティは、 Route設定で設定したパスを返します。 searchは、クエリ検索パラメータがあればそれを返します。 たとえば、クエリに'http://mywebsite.com/employee/?id=1'を渡すと、 pathname/employeeになり、 search?id=1になります。

次に、クエリ文字列などのパッケージを使用するか、コーディングすることで、さまざまな検索パラメータを取得できます。

useParamsフック

パス属性にURLパラメーターを指定してRouteを設定すると、 useParamsフックを使用してこれらのパラメーターをキーと値のペアとして評価できます。

たとえば、次のルートがあるとします。

 <Route path='/employees/:id' > <Employees /> </Route>

ルートは、 :idの代わりに動的IDを期待します。

useParamsフックを使用すると、ユーザーから渡されたIDがあればそれを評価できます。

たとえば、ユーザーがhistory.pushを使用して関数で以下を渡すと仮定すると、

 function goToPage = () => { history.push(`/employee/3`) }

次のように、 useParamsフックを使用してこのURLパラメーターにアクセスできます。

 import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample

paramsをコンソールに記録すると、次のオブジェクト{id: "3"}が得られます。

useRouteMatchフック

このフックは、一致オブジェクトへのアクセスを提供します。 引数が指定されていない場合、コンポーネントに最も近い一致を返します。

matchオブジェクトは、 path (Routeで指定されたパスと同じ)、 URLparamsオブジェクト、およびisExactを含むいくつかのパラメーターを返します。

たとえば、 useRouteMatchを使用して、ルートに基づいてコンポーネントを返すことができます。

 import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;

上記のコードでは、 useRouteMatchを使用してルートのパスを設定し、ユーザーが選択したルートに応じて<Employee />または<Admin />コンポーネントをレンダリングします。

これを機能させるには、 App.jsファイルにルートを追加する必要があります。

 ... <Route> <CustomRoute /> </Route> ...

カスタムフックの構築

Reactのドキュメントによると、カスタムフックを作成すると、ロジックを再利用可能な関数に抽出できます。 ただし、Reactフックに適用されるすべてのルールがカスタムフックに適用されることを確認する必要があります。 このチュートリアルの上部にあるReactフックのルールを確認し、カスタムフックがそれぞれに準拠していることを確認してください。

カスタムフックを使用すると、関数を1回記述して、必要なときにいつでも再利用できるため、DRYの原則に従います。

たとえば、次のように、ページ上のスクロール位置を取得するカスタムフックを作成できます。

 import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };

ここでは、ページ上のスクロール位置を決定するためのカスタムフックを定義しました。 これを実現するために、最初に、スクロール位置を格納するための状態scrollPosを作成しました。 これによりDOMが変更されるため、 useLayoutEffectの代わりにuseEffectを使用する必要があります。 xとyのスクロール位置をキャプチャするスクロールイベントリスナーを追加してから、イベントリスナーをクリーンアップしました。 最後に、スクロール位置に戻りました。

このカスタムフックは、他の状態を使用する場合と同じように呼び出して使用することで、アプリケーションのどこでも使用できます。

 import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App

ここでは、上記で作成したカスタムフックuseScrollPosをインポートしました。 次に、それを初期化してから、値をコンソールに記録しました。 ページをスクロールすると、フックはスクロールのすべてのステップでスクロール位置を表示します。

カスタムフックを作成して、アプリで想像できるほぼすべてのことを実行できます。 ご覧のとおり、いくつかの機能を実行するには、組み込みのReactフックを使用する必要があります。 サードパーティのライブラリを使用してカスタムフックを作成することもできますが、その場合、フックを使用できるようにするには、そのライブラリをインストールする必要があります。

結論

このチュートリアルでは、ほとんどのアプリケーションで使用するいくつかの便利なReactフックをよく見てきました。 それらが何を提示し、アプリケーションでどのように使用するかを調べました。 また、これらのフックを理解してアプリケーションに適用するのに役立ついくつかのコード例も確認しました。

これらのフックを自分のアプリケーションで試して、理解を深めることをお勧めします。

ReactDocsからのリソース

  • フックに関するFAQ
  • Reduxツールキット
  • ステートフックの使用
  • エフェクトフックの使用
  • フックAPIリファレンス
  • Reduxフックに反応する
  • Reactルーターフック