プロジェクトで使用できる便利なReactフック
公開: 2022-03-10フックは、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)
ここで、 mapStateToProps
とmapDispatchToProps
は定義する関数です。
一方、フックの時代では、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番目の例と比較していかに偽物であるかに注意してください。
フックのコンベンションとルール
さまざまなフックを詳しく調べる前に、それらに適用される規則と規則を確認すると役立つ場合があります。 フックに適用されるルールのいくつかを次に示します。
- フックの命名規則は、接頭辞
use
で始まる必要があります。 したがって、useState、useEffectなどを使用できますuseState
やuseEffect
などの最新のコードエディターを使用している場合、ESLintプラグインはReactフックにとって非常に便利な機能になる可能性があります。 プラグインは、ベストプラクティスに関する有用な警告とヒントを提供します。 - フックは、returnステートメントの前に、コンポーネントの最上位で呼び出す必要があります。 条件文、ループ、またはネストされた関数内で呼び出すことはできません。
- フックは、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
フックが配列(状態変数とも呼ばれる)を返すことは注目に値します。上記の例では、配列をstate
とupdater
関数に分解しました。
コンポーネントのレンダリング
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つの状態arr
とobj
を作成し、それらをそれぞれいくつかの配列値とオブジェクト値に初期化しました。 次に、 handleArrClick
およびhandleObjClick
というonClick
ハンドラーを作成して、それぞれ配列とオブジェクトの状態を設定しました。 handleArrClick
が起動すると、 setArr
を呼び出し、ES6 Spread演算子を使用して、既存の配列値を拡散し、それにnewArr
を追加します。
handleObjClick
ハンドラーに対しても同じことを行いました。 ここでは、 setObj
を呼び出し、ES6 Spread演算子を使用して既存のオブジェクト値を拡散し、 name
とage
の値を更新しました。
useState
の非同期の性質
すでに見てきたように、アップデーター関数に新しい値を渡すことにより、 useState
で状態を設定します。 アップデータが複数回呼び出された場合、新しい値がキューに追加され、 Object.is
比較を使用してそれに応じて再レンダリングが行われます。
状態は非同期で更新されます。 これは、新しい状態が最初に保留状態に追加され、その後、状態が更新されることを意味します。 したがって、設定された状態にすぐにアクセスすると、古い状態の値を取得できる場合があります。
この動作を観察するために、次の例を考えてみましょう。
上記のコードでは、 useState
フックを使用してcount
状態を作成しました。 次に、ボタンがクリックされるたびにcount
状態をインクリメントするonClick
ハンドラーを作成しました。 h2
タグに表示されているように、 count
状態は増加しましたが、以前の状態は引き続きコンソールに記録されていることに注意してください。 これは、フックの非同期性によるものです。
新しい状態を取得したい場合は、非同期関数を処理するのと同じ方法で処理できます。 これを行う1つの方法があります。
ここでは、作成されたnewCountValue
を保存して、更新されたカウント値を保存してから、更新された値でcount
状態を設定しました。 次に、更新されたカウント値をコンソールに記録しました。
useEffect
フック
useEffect
は、ほとんどのプロジェクトで使用されるもう1つの重要なReactフックです。 これは、クラスベースのコンポーネントのcomponentDidMount
、 componentWillUnmount
、およびcomponentDidUpdate
ライフサイクルメソッドと同様のことを行います。 useEffect
は、アプリケーションに副作用をもたらす可能性のある命令コードを作成する機会を提供します。 このような影響の例には、ロギング、サブスクリプション、ミューテーションなどが含まれます。
ユーザーはuseEffect
をいつ実行するかを決定できますが、設定されていない場合、副作用はすべてのレンダリングまたは再レンダリングで実行されます。
以下の例を考えてみましょう。
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
上記のコードでは、 useEffect
にcount
を記録しただけです。 これは、コンポーネントがレンダリングされるたびに実行されます。
コンポーネントでフックを(マウント上で)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つの条件のいずれかが満たされたときに実行されます。
- マウント時—コンポーネントがレンダリングされた後。
-
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
フックを実装する方法を見てみましょう。
まず、状態を保持するためのレデューサーを作成する必要があります。
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
ファイルに適用されます。
テーマまたはその他の高次のアプリレベルの構成は、コンテキストの候補として適しています。
useContext
とuseReducer
使用
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
オブジェクトを作成しました。 各プロパティには、 backgroundColor
とcolor
オブジェクトがあります。
次に、テーマの状態を処理するための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アプリケーションにいくつか追加しました。 まず、 ThemeContext
、 themeReducer
、 ThemeToggler
、および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.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - 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
上記の例では、 memoResult
とcallbackResult
の両方が同じ値の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
フックにも適用されることに注意してuseCallback
。 useCallback
を使用してuseMemo
の例を再作成できます。
useRef
フック
useRef
は、アプリケーションで永続化できるオブジェクトを返します。 フックにはcurrent
という1つのプロパティしかなく、引数を簡単に渡すことができます。
これは、クラスベースのコンポーネントで使用されるcreateRef
と同じ目的を果たします。 このフックを使用して、次のように参照を作成できます。
const newRef = useRef('')
ここでは、 newRef
という新しい参照を作成し、それに空の文字列を渡しました。
このフックは主に2つの目的で使用されます。
- DOMへのアクセスまたは操作、および
- 可変状態の保存—これは、値が変更されたときにコンポーネントを再レンダリングしたくない場合に役立ちます。
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を設定することにより、 headingRef
にh1
要素を指すように要求しました。 これは、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;
このコンポーネントは、 props
とref
を受け入れます。 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
は、ミューテーションが発生した後に実行されるため、それらを完全に処理します。
この概念を示すために、いくつかの例を見てみましょう。
- サイズ変更時にウィンドウの幅と高さを取得します。
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操作をクリーンアップして効率を向上させるために、クリーンアッププロセスが不可欠です。
-
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> ); }
上記のコードでは、 useRef
とuseLayoutEffect
を一緒に使用しました。 最初に、段落を指す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
からuseDispatch
とuseSelector
をインポートしました。 次に、 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
でラップする必要があります。 次に、 Switch
とRoute
を使用してルートをネストできます。
ただし、最初に、次のコマンドを実行してパッケージをインストールする必要があります。
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() ... }
履歴をコンソールに記録すると、それに関連付けられたいくつかのプロパティが表示されます。 これらには、 block
、 createHref
、 go
、 goBack
、 goForward
、 length
、 listen
、 location
、 push
、 replace
含まれます。 これらのプロパティはすべて便利ですが、 history.push
とhistory.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
フックは、 pathname
、 search
パラメーター、 hash
、およびstate
を返します。 最も一般的に使用されるパラメーターには、 pathname
とsearch
が含まれますが、同様に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で指定されたパスと同じ)、 URL
、 params
オブジェクト、および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ルーターフック