新しいReact 16.6.0のリリースにより、ドキュメントにフック(提案)が登場しました。 それらは現在、react 17.0.0-alphaで利用可能であり、オープンRFC:React Hooksで議論されています 。 それが何であり、なぜそれがカットの下で必要であるかを見てみましょう。
はい、これはRFCです。このアプローチを選んだ理由をリアクションの作成者と話し合うことで、最終的な実装に影響を与えることができます。
標準のフックがどのように見えるか見てみましょう:
import { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
このコードについて考えてみてください。これはティーザーであり、記事の終わりまでに、それが何を意味するかをすでに理解できます。 最初に知っておくべきことは、これによって後方互換性が損なわれることはなく、RFCでフィードバックと提案を収集した後、16.7で追加される可能性があることです。
みんなが保証するように、これはクラスを試薬から切り落とす計画ではありません。
また、フックは反応の現在の概念を置き換えるものではなく、すべてが小道具/状態/コンテキスト/参照の代わりになります。 これは、彼らの力を使用する別の方法です。
やる気
フックは、Facebookで5年間にわたって何万ものコンポーネントをサポートすることで発生した非接続問題を一見して解決します。
最も難しいのは、ステートフルコンポーネントでロジックを再利用することです。リアクションには、コンポーネントに再利用可能な動作を付加する方法がありません(たとえば、リポジトリに接続する)。 Reactを使用したことがあれば、HOC(高次コンポーネント)またはレンダープロップの概念を理解しています。 これらは十分なパターンですが、過度に使用される場合があり、使用できるようにコンポーネントを再構築する必要があり、通常はコードが面倒になります。 典型的な反応アプリケーションを見る価値があり、何が危険なのかが明らかになります。
これは、 wrapped-hell -wrapper hellと呼ばれます。
HOCのみからのアプリケーションは、現在の現実では普通であり、コンポーネントをストア/テーマ/ローカライズ/カスタムホックに接続します。これは誰もが知っていると思います。
反応がロジックを分離するために別の原始的なメカニズムを必要とすることが明らかになります。
フックを使用して、コンポーネントの状態を取得し、テストして再利用できるようにします。 フックを使用すると、コンポーネントの階層を変更せずに状態ロジックを再利用できます。 これにより、多くのコンポーネント間またはシステム全体間のリンクの交換が容易になります。 また、クラスコンポーネントは非常に恐ろしく見えます。ライフサイクルメソッドcomponentDidMount
/ shouldComponentUpdate
/ componentDidUpdate
、 componentDidUpdate
の状態、state / storを操作するメソッドの作成、コンポーネントインスタンスのbindimメソッドなどについて説明します。 通常、そのようなコンポーネントはx行を超えます。xは理解するのに十分困難です。
フックを使用すると、コンポーネント間のロジックを小さな関数に分割し、コンポーネント内で使用することで同じことができます。
クラスは人にとっても車にとっても難しい
Facebookのクラスを観察することは、Reactを学習する際の大きな障害です。 this
がどのthis
機能するかを理解する必要があり、他のプログラミング言語のように機能しないため、イベントハンドラーのバインドについても覚えておく必要があります。 安定した構文文がないと、コードは非常に冗長に見えます。 人々は小道具/状態パターンといわゆるトップダウンデータフローを非常によく理解していますが、クラスを理解するのは困難です。
特に、テンプレートに限定されない場合、それほど前のことではないが、反応の男はPrepack を使用してコンポーネントのレイアウトを実験し、有望な結果を見たが、それにもかかわらず、クラスコンポーネントを使用すると、これらの最適化が失われるような意図しない悪いパターンを作成でき、クラスも非常にうまく移行しないホットリロードクラスは信頼性を低下させます。 まず、すべての最適化をサポートし、ホットリブートで正常に動作するAPIを提供したかったのです。
フックを見てください
状態フック
以下のコードは段落とボタンをレンダリングします。ボタンをクリックすると、段落の値が増加します。
import { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
このことから、このフックはstate
などの概念でも同様に機能すると結論付けることができます。
もう少し詳細なuseState
メソッドは1つの引数を取ります。これはデフォルト値であり、値自体とそれを変更するメソッドがあるタプルを返します。setStateとは異なり、setCountは値をマージせず、単に更新します。 複数の状態宣言を使用することもできます。たとえば、
function ExampleWithManyStates() { // Declare multiple state variables! const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); // ... }
したがって、一度に複数の状態を作成し、それらをどうにか分解する方法を考える必要はありません。 したがって、フックはクラスコンポーネントのチップに「接続」できる関数であり、クラス内でフックが機能しないのと同様に、これを覚えておくことが重要です。
エフェクトフック
多くの場合、クラスコンポーネントでは、副作用関数を作成します。たとえば、イベントにサブスクライブしたり、データを要求したりします。通常、このためにcomponentDidMount
/ componentDidUpdate
メソッドを使用しcomponentDidMount
import { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
useEffect
を呼び出すとき、DOMツリーの変更を更新した後、「副作用」を実行するようにリアクションに伝えます。 効果はコンポーネント内で宣言されるため、小道具/状態にアクセスできます。 そして、あなたが好きなだけ同じ方法でそれらを作成することができます。
function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); function handleStatusChange(status) { setIsOnline(status.isOnline); } // ...
すぐに、2番目の副作用に注意する価値があります。関数を返します。コンポーネントがアンマウントを実行した後、アクションを実行するためにこれを行います。新しいAPIでは、これはクリーニング付きの効果と呼ばれます。 他の効果は何でも返すことができます。
フックルール
フックは単なるJavaScript関数ですが、必要なルールは2つだけです。
- フックは関数階層の最上部で実行する必要があります(これは、条件およびループでフックを呼び出さないことを意味します。そうしないと、リアクションはフックの実行順序を保証できません)
- React関数または機能コンポーネントでのみフックを呼び出すか、カスタムフックからフックを呼び出します(以下を参照)。
これらのルールに従うために、反応チームのメンバーは、クラスコンポーネントまたはループと条件でフックを呼び出すとエラーをスローするリンタープラグインを作成しました。
カスタムフック
同時に、ステートフルコンポーネントのロジックを再利用したいと思います。通常は、HOCまたはレンダリングの小道具パターンのいずれかを使用しますが、アプリケーションの追加ボリュームを作成します。
たとえば、次の関数について説明します。
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
このコードを実現します。さまざまなコンポーネントで呼び出すことができるカスタムフックになります。 たとえば、次のように:
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
かそこら
function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
いずれの場合も、コンポーネントの状態を再利用し、 useFriendStatus
関数の呼び出しごとに分離状態を作成します。 また、この関数の先頭はuseという単語で始まっていることに注意してください。これはフックであることを意味します。 この形式に従うことをお勧めします。 何でも、アニメーション/サブスクリプション/タイマーなどのためのカスタムフックを書くことができます。
さらにいくつかのフックがあります。
useContext
useContext
を使用すると、renderPropsの代わりに通常の戻り値を使用できます。抽出するコンテキストを渡す必要があります。これを返すと、コンテキストをpropに渡したすべてのHOCを取り除くことができます。
function Example() { const locale = useContext(LocaleContext); const theme = useContext(ThemeContext); // ... }
これで、戻り値でコンテキストオブジェクトを使用できます。
useCallback
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
メソッドへの参照を保存するためだけに、クラスのコンポーネントを作成しなければならなかった頻度はどれくらいですか? これはもう行う必要はありません。useCallbackを使用できます。onClickへの新しいリンクが到着したため、コンポーネントは再描画されません。
useMemo
memized値を返します。memized値は、引数の1つが変更された場合にのみ計算され、2回目は同じものが計算されないことを意味します。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
はい、ここでは、フックが変更されていないことを理解できるように、配列内の値を複製する必要があります。
useRef
useRef
は変更された値を返します。ここで、 .current
フィールドは最初の引数で初期化され、コンポーネントが存在する限りオブジェクトは存在します。
入力に注目した場合の最も一般的な例
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
useImperativeMethods
useImperativeMethods
は、親から渡されるインスタンスの値をカスタマイズし、refを直接使用します。 いつものように、直接リンクは避け、 forwardRef
を使用する必要があります
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeMethods(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
この例では、 FancyInput
をFancyInput
コンポーネントFancyInput
fancyInputRef.current.focus()
呼び出すことができます。
useMutationEffect
useMutationEffect
非常に似ていますが、reactが隣接するコンポーネントが更新される前にDOM値を変更する段階で同期的に開始されることを除き、このフックはDOMミューuseEffect
を実行するために使用されます。
UseEffectは、視覚的な変更のブロックを防ぐために最適です。
useLayoutEffect
useLayoutEffect
はuseLayoutEffect
と似ていますが、すべてのDOMおよび同期再レンダリングの更新後に同期的に開始する点がuseEffect
ます 。 useLayoutEffect
計画された更新は、ブラウザーが要素を描画する前に同期的に適用されます。 また、視覚的な変更を妨げないように、標準のuseEffect
使用を試みる必要があります。
useReducer
useReducer
は、状態と変更をディスパッチする機能を返すレデューサーを作成するためのフックです。
const [state, dispatch] = useReducer(reducer, initialState);
Reduxの仕組みを理解していれば、 useReducer
仕組みを理解できます。 useReducer
介してのみ上記のカウンターを使用したのと同じ例:
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return initialState; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
UseReducerも3つの引数を取ります。これは、レデューサーが初期化されたときに実行されるaction
です。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return {count: action.payload}; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; } } function Counter({initialCount}) { const [state, dispatch] = useReducer( reducer, initialState, {type: 'reset', payload: initialCount}, ); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
このレデューサーでコンテキストを作成し、 useContext
フックを使用してアプリケーション全体で使用することもできます。これは宿題用のままです。
まとめると
フックは、wrapper-hellを解決し、いくつかの問題を解決するための非常に強力なアプローチですが、それらすべてをリンク転送の 1つの定義で使用できます。 現在、使用するフックのコレクションまたはこのコレクションが表示され始めています。 フックの詳細については、 ドキュメントをご覧ください。