完全なUseEffectガイド

フックを使用しいくつかのコンポーネントを作成しました。 おそらく-彼らも小さなアプリケーションを作成しました。 一般に、結果に非常に満足しています。 あなたはAPIに慣れており、作業中にいくつかの非自明な有用なトリックを見つけました。 独自のフックをいくつか作成し、コードを300行に減らして、プログラムフラグメントの繰り返しによって以前に表されていたものをそれらに配置しました。 あなたがしたことは、同僚に見せました。 「よくやった」彼らはあなたのプロジェクトについて言った。


ただし、 useEffectを使用すると、ソフトウェアメカニズムのコンポーネントがうまく合わない場合があります。 あなたには何かが欠けているようです。 これらはすべて、クラスベースのコンポーネントライフイベントでの作業に似ていますが、本当にそうですか?
何が自分に合わないかを理解しようとすると、次の質問をしていることに気づきます。


私が最初にフックを使い始めたとき、これらの質問も私を苦しめました。 ドキュメントを準備していても、微妙な点を完全に知っているとは言えませんでした。 それ以来、突然、重要な何かに気付いた瞬間、「エウレカ!」と叫びたくなりました。 これらの瞬間に気づいたことについて、お話ししたいと思います。 useEffectについて学んだことにより、上記の質問に対する明白な回答を明確に見ることができるようになりました。

しかし、これらの質問に対する答えを見るためには、まず一歩後退する必要があります。 この記事の目的は、読者にuseEffectuseEffectするための段階的な指示を提供することではありません。 彼らが言うように、 useEffectを「 useEffect 」するのを助けることを目指しています。 そして、率直に言って、学ぶことはあまりありません。 実際、ほとんどの時間は以前の知識を忘れることに費やします。

コンポーネントベースのコンポーネントのライフサイクルのよく知られた方法のプリズムを通してuseEffectフックを見るのをやめてから、頭の中のすべてが一緒になりました。

「教えられたことを忘れなければならない」


habr.com/ru/company/ruvds/blog/445276/Yoda


この資料の読者はuseEffect APIにある程度精通していることを前提としてます。 これはかなり長い記事であり、小さな本と比較することができます。 実際、私は自分の考えをこのように表現することを好みます。 以下に、非常に簡潔に、上記で議論されたこれらの質問に対する答えが与えられます。 おそらく、それらはすべての資料を読む時間や欲求がない人々にとって有用です。

useEffectすべての説明と例が考慮されている形式があまり適していない場合は、これらの説明が他の無数のマニュアルに登場する瞬間まで少し待つことができます。 これは、Reactライブラリ自体と同じ話で、2013年にはまったく新しいものでした。 開発コミュニティが新しいメンタルモデルを認識し、このモデルに基づいた教材が表示されるまでには時間がかかります。

質問への回答


以下は、この資料全体の内容を読みたくない人を対象とした、この資料の冒頭で提示された質問に対する短い回答です。 これらの答えを読んでいるときに、読んでいるものの意味を本当に理解していないと感じたら、資料に目を通してください。 テキストに詳細な説明があります。 すべてを読む場合は、このセクションをスキップできます。

use useEffectを使用してcomponentDidMountを再生する方法


useEffect(fn, [])コンストラクトを使用してcomponentDidMount機能を再生できcomponentDidMount 、これはcomponentDidMountまったく同じではありません。 つまり、 componentDidMountとは異なり、プロパティと状態をキャプチャします。 したがって、コールバック内でも、元のプロパティと状態が表示されます。 何かの最新バージョンを見たい場合は、 refリンクにそれを書くことができます。 ただし、通常はコードを構造化するより簡単な方法があるため、これはオプションです。 メンタルエフェクトモデルは、 componentDidMountおよびその他のコンポーネントライフサイクルメソッドに適用されるcomponentDidMountとは異なることに注意してください。 したがって、正確な同等物を見つけようとすると、良いことよりも害になることがあります。 生産的に働くためには、いわば「効果を考える」必要があります。 メンタルモデルの基礎は、コンポーネントのライフサイクルのイベントへの応答よりも同期の実装に近いものです。

useuseEffect内にデータを適切にロードする方法 []とは何ですか?


useEffectを使用したデータのロードに関する優れたガイドをuseEffectます。 全体を読んでみてください! それほど大きくはありません。 空の配列を表す角かっこ[]は、エフェクトがReactデータストリームに含まれる値を使用しないことを意味します。このため、その単一の使用は安全と見なすことができます。 さらに、依存関係の空の配列の使用は、特定の値が実際にエフェクトで使用される場合のエラーの一般的な原因です。 この依存関係を不当に破棄するのではなく、依存関係の必要性を排除するのに役立ついくつかの戦略(主にuseReduceruseCallback形式で提示)を習得する必要があります。

functions効果の依存関係として関数を指定する必要がありますか?


プロパティや状態を必要としない関数は、コンポーネントの外部で使用することをお勧めします。また、エフェクトでのみ使用される関数は、エフェクト内に配置することをお勧めします。 その後、エフェクトがレンダーのスコープ内にある関数(プロパティからの関数を含む)をuseCallback使用する場合は、それらを宣言されているuseCallbackでラップし、再度使用してみてください。 なぜこれが重要なのですか? 関数は、プロパティと状態から値を「見る」ことができるため、データストリームに参加します。 これについての詳細は、FAQをご覧ください。

▍プログラムがデータのリロードの無限ループに陥ることがあるのはなぜですか?


これは、依存関係を表す2番目の引数を持たないエフェクトでデータの読み込みが実行されるときに発生する可能性があります。 これがない場合、各レンダリング操作の後にエフェクトが実行されます。つまり、状態を設定すると、そのようなエフェクトが呼び出されます。 常に変化する値が依存関係配列に示されている場合も、無限ループが発生する可能性があります。 依存関係を一度に1つずつ削除することで、どのような値が可能なのかを調べてください。 ただし、依存関係を削除する(または[]を使用して突発的に行う)ことは、通常、問題を解決するための間違ったアプローチです。 代わりに、問題の原因を見つけて実際に解決する必要があります。 たとえば、関数は同様の問題を引き起こす可能性があります。 それらをエフェクトに入れたり、コンポーネントの外側に移動したり、 useCallbackラップしたりすることでuseCallbackます。 複数のオブジェクトを作成しないようにするには、 useMemoを使用できます。

▍古い状態がエフェクト内に表示されたり、古いプロパティが見つかったりするのはなぜですか?


エフェクトは、宣言されたレンダーから常にプロパティと状態を「参照」します。 これはエラーの防止に役立ちますが、場合によってはコンポーネントの通常の動作を妨げる可能性があります。 このような場合、可変refリンクを明示的に使用して、そのような値を操作できます(これについては、前述の記事の最後で参照できます)。 古いレンダリングからプロパティまたは状態が表示されると思っているが、これを期待していない場合は、いくつかの依存関係を見逃している可能性があります。 それらを見ることを学ぶために、リンターのこのルールを使用してください。 数日で、それはあなたの第二の自然のようなものになります。 また、よくある質問でこの回答をご覧ください。

質問に対するこれらの回答が、それらを読んだ人に役立つことを願っています。 ではuseEffectについてuseEffectましょう。

各レンダーには、独自のプロパティと状態があります。


効果について説明する前に、レンダリングについて説明する必要があります。

機能的なカウンターコンポーネントは次のとおりです。

 function Counter() { const [count, setCount] = useState(0); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

<p>You clicked {count} times</p> 。 彼女はどういう意味ですか? 定数定数は何らかの形で状態の変化を「観察」し、自動的に更新されますか? この結論は、Reactを研究している人の一種の貴重な最初のアイデアと考えることができますが何が起こっているかを正確に理解する精神的なモデルではありません。

この例では、 countは単なる数字です。 これは、ある種の魔法の「データバインディング」ではなく、ある種の「オブザーバーオブジェクト」や「プロキシ」などではありません。 私たちの前に、このような古い良い数字があります:

 const count = 42; // ... <p>You clicked {count} times</p> // ... 

最初のコンポーネントの出力中、 useState()から取得したcountは0ですsetCount(1)を呼び出すと、Reactはコンポーネントを再度呼び出します。 この時間countは1になります。

 //     function Counter() { const count = 0; //  useState() // ... <p>You clicked {count} times</p> // ... } //       function Counter() { const count = 1; //  useState() // ... <p>You clicked {count} times</p> // ... } //        function Counter() { const count = 2; //  useState() // ... <p>You clicked {count} times</p> // ... } 

Reactは、状態を更新するたびにコンポーネントを呼び出します。 結果として、各レンダリング操作は、関数内で定数であるcounter状態の独自の値を「認識」します。

その結果、この行は特別なデータバインディング操作を実行しません。

 <p>You clicked {count} times</p> 

レンダリング中に生成されたコードに数値のみを埋め込みます。 この番号はReactによって提供されます。 setCountを呼び出すと、Reactは異なるcount値でコンポーネントを再度呼び出します。 次に、ReactはDOMを更新して、コンポーネントレンダリング中にドキュメントオブジェクトモデルが最新のデータ出力と一致するようにします。

これから引き出すことができる最も重要な結論は、 countは特定のレンダー内では定数であり、時間とともに変化しないということです。 何度も呼び出されるコンポーネントが変更されます。 各レンダリングは独自のcount値を「認識」します。このcount値は、レンダリング操作ごとに分離されます。

この資料には、このプロセスに関する詳細が記載されています。

各レンダーには、独自のイベントハンドラがあります。


すべてはまだ明確です。 イベントハンドラはどうですか?
この例を見てください。 ここでは、ボタンをクリックしてから3秒後に、 count保存されている値に関する情報を含むメッセージボックスが表示されcount

 function Counter() { const [count, setCount] = useState(0); function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>     <button onClick={handleAlertClick}>       Show alert     </button>   </div> ); } 

次の一連のアクションを実行するとします。



[アラートを表示]ボタンをクリックした後、カウント値を増やす

メッセージボックスに何が表示されると思いますか? タイマーがトリガーされたときのcount値に対応する5がそこに表示されますか、3-ボタンが押された瞬間のcount値ですか?

これで、この質問に対する答えが見つかりますが、すべてを自分で調べたい場合は、この例の作業バージョンがあります。

あなたが見たものがあなたにとって理解できないように思えるなら、現実に近い例がここにあります。 状態で、メッセージの現在の受信者のIDが保存され、 Sendボタンがあるチャットアプリケーションを想像してSendこの資料では、何が起こっているのかを詳細に検討します。 実際、メッセージボックスに表示される内容の質問に対する正しい答えは3です。

メッセージボックスを表示するメカニズムは、ボタンがクリックされた瞬間の状態を「キャプチャ」しました。

動作の別のバリアントを実装する方法がありますが、ここではシステムの標準的な動作を扱います。 テクノロジーのメンタルモデルを構築する場合、「最小の抵抗のパス」をあらゆる種類の「緊急出口」と区別することが重要です。

これはどのように機能しますか?

countの値は、関数の特定の呼び出しごとに定数であると既に述べました。 これについて詳しく説明する価値があると思います。 ポイントは、関数が何度も呼び出されることです(レンダリング操作ごとに1回)が、これらの各呼び出しでは、その内部のcountは定数です。 この定数は、特定の値(特定のレンダリング操作の状態を表す)に設定されます。

この関数の振る舞いはReactにとって特別なものではありません-通常の関数は同様に振る舞います:

 function sayHi(person) { const name = person.name; setTimeout(() => {   alert('Hello, ' + name); }, 3000); } let someone = {name: 'Dan'}; sayHi(someone); someone = {name: 'Yuzhi'}; sayHi(someone); someone = {name: 'Dominic'}; sayHi(someone); 

この例では、外部変数someone数回再割り当てされます。 同じことがReact内のどこかで発生する可能性があり、コンポーネントの現在の状態が変わる場合があります。 ただし、 sayHi関数の内部には、特定の呼び出しのperson関連付けられたローカル定数nameがあります。 この定数はローカルであるため、異なる関数呼び出しの値は互いに分離されています! その結果、タイムアウト後、表示された各メッセージウィンドウは独自のname値を「記憶」します。

これは、ボタンがクリックされたときにイベントハンドラーがcount値をキャプチャする方法を説明しています。 コンポーネントを使用して同じ原則を適用すると、各レンダリングが独自のcount値を「見る」ことがわかります。

 //     function Counter() { const count = 0; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //       function Counter() { const count = 1; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } //        function Counter() { const count = 2; //  useState() // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + count);   }, 3000); } // ... } 

その結果、各レンダーは実際に独自の「バージョン」 handleAlertClick返します。 これらの各バージョンは、独自のcount値を「記憶」しています。

 //     function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 0);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   0 // ... } //       function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 1);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   1 // ... } //        function Counter() { // ... function handleAlertClick() {   setTimeout(() => {     alert('You clicked on: ' + 2);   }, 3000); } // ... <button onClick={handleAlertClick} /> // ,   2 // ... } 

そのため、 この例では、イベントハンドラーは特定のレンダリングに「属し」、ボタンをクリックすると、コンポーネントはこれらのレンダリングのcount状態を使用しcount

特定の各レンダリング内で、プロパティと状態は常に同じままです。 ただし、異なるレンダリング操作が独自のプロパティと状態を使用する場合、それらを使用するメカニズム(イベントハンドラーを含む)でも同じことが起こります。 また、特定のレンダリングに「所属」します。 したがって、イベントハンドラー内の非同期関数でさえ、同じcount値を「参照」します。

上記の例では、特定のcount値をhandleAlertClick関数に直接埋め込みました。 count定数は特定のレンダリング内で変更できないため、この「メンタル」置換は私たちを傷つけません。 第一に、それは定数であり、第二に、それは数値です。 オブジェクトなどの他の意味について考えることもできますが、これは、状態の変更(突然変異)を行わないことを規則として受け入れる場合に限ります。 同時に、既存のオブジェクトを変更するのではなく、新しいオブジェクトでsetSomething(newObj)を呼び出すことに満足しています。このアプローチでは、前のレンダリングに属する状態は変更されないためです。

各レンダリングには独自の効果があります。


ご存知のように、この資料はエフェクトに専念していますが、それらについてはまだ話していません。 今それを修正します。 結局のところ、エフェクトの操作は、既にわかっているものと特に違いはありません。

ドキュメントのを考えてみましょう。これは、すでに分析したものと非常に似ています。

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   document.title = `You clicked ${count} times`; }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

今、あなたに質問があります。 エフェクトは最新のcount値をどのように読み取りますか?

おそらく、ある種の「データバインディング」がここで使用されているのでしょうか、それともエフェクト関数内のcount値を更新する「オブザーバーオブジェクト」ですか? 多分countは、コンポーネント内でReactが値を設定する可変変数であり、その結果、エフェクトは常に最新バージョンを認識しますか?

いや

特定のコンポーネントのレンダリングでは、 countが定数であることをすでに知っています。 countは特定のスコープにある定数であるため、イベントハンドラーでさえ、「属する」レンダーからcount値を「参照」しcount 。 同じことがエフェクトにも当てはまります!

また、これは変数countはなく、「変更されていない」エフェクト内で何らかの形で変化することに注意してください。 エフェクト関数自体は、レンダリング操作ごとに異なります。

各バージョンは、「属する」レンダーのcount値を「認識」しcount

 //     function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${0} times`;   } ); // ... } //       function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${1} times`;   } ); // ... } //        function Counter() { // ... useEffect(   //        () => {     document.title = `You clicked ${2} times`;   } ); // .. } 

React , DOM .

, ( ), , , , «» , «».

, , .

, ( , ). , , , .

, , :

React:


:


React:


ブラウザ:


React:


, . , , - :

:


React:


:


React:


ブラウザ:


React:



, , , , «» .

. 次のコードを検討してください。

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   setTimeout(() => {     console.log(`You clicked ${count} times`);   }, 3000); }); return (   <div>     <p>You clicked {count} times</p>     <button onClick={() => setCount(count + 1)}>       Click me     </button>   </div> ); } 

, ?

, . , , , . しかし、これはそうではありません! , , , , , count . .




: «, ! ?».

, , this.setState , , . , , , , , :

   componentDidUpdate() {   setTimeout(() => {     console.log(`You clicked ${this.state.count} times`);   }, 3000); } 

, this.state.count count , , . , , , 5 , 5 .




, JavaScript-, , , , , setTimeout , . , (React this.state , ), .

— , , «» , . , , , . , , . , , , , , , .


, ( , , - API ) , .

:

 function Example(props) { useEffect(() => {   setTimeout(() => {     console.log(props.counter);   }, 1000); }); // ... } function Example(props) { const counter = props.counter; useEffect(() => {   setTimeout(() => {     console.log(counter);   }, 1000); }); // ... } 

, «» . ! . , .

, , - , , , , . , ref , .

, , , , , . , ( ), «» React-. , , . , .

, , , :

 function Example() { const [count, setCount] = useState(0); const latestCount = useRef(count); useEffect(() => {   //        count   latestCount.current = count;   setTimeout(() => {     //            console.log(`You clicked ${latestCount.current} times`);   }, 3000); }); // ... 




- React . React this.state . , , latestCount.current . , . , , , .

?


, . , , «» .

次のコードを検討してください。

 useEffect(() => {   ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);   return () => {     ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);   }; }); 

, props{id: 10} , {id: 20} — . , :


( , , .)

, «» - , , «» - , . — , , , . .

React , . , . . . :


, «» props , {id: 10} , , props {id: 20} .

, …


— ?

: « ( , , - API ) , ».

! « » , . , , :

 //  ,  props  {id: 10} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(10, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange);     };   } ); // ... } //  ,  props  {id: 20} function Example() { // ... useEffect(   //       () => {     ChatAPI.subscribeToFriendStatus(20, handleStatusChange);     //           return () => {       ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange);     };   } ); // ... } 

, , … , «» , -, {id: 10} .

React . , , . props , .

,


React , . .

, :

 function Greeting({ name }) { return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

, <Greeting name="Dan" /> , — <Greeting name="Yuzhi" /> , <Greeting name="Yuzhi" /> . Hello, Yuzhi .

, , . React, . , , . $.addClass $.removeClass jQuery- ( — , «»), , CSS- React ( — , «»).

React DOM , . «» «».

. useEffect , React, .

 function Greeting({ name }) { useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}   </h1> ); } 

useEffect , , . , - , ! , «», «».

, A , B , — C , , C . (, - ), .

, , , . ( ).

これに対処する方法は?

React


React DOM. DOM , React DOM, - .

, :

 <h1 className="Greeting"> Hello, Dan </h1> 

:

 <h1 className="Greeting"> Hello, Yuzhi </h1> 

React :

 const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'}; 

React , children , DOM. , className . :

 domNode.innerText = 'Hello, Yuzhi'; // domNode.className    

- ? , , .

, , - :

 function Greeting({ name }) { const [counter, setCounter] = useState(0); useEffect(() => {   document.title = 'Hello, ' + name; }); return (   <h1 className="Greeting">     Hello, {name}     <button onClick={() => setCounter(counter + 1)}>       Increment     </button>   </h1> ); } 

counter . document.title name , name . document.title counter , .

React … ?

 let oldEffect = () => { document.title = 'Hello, Dan'; }; let newEffect = () => { document.title = 'Hello, Dan'; }; //   React  ,        ? 

そうでもない。 React , , . ( . name .)

, , ( deps ), useEffect :

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); //   

, React: «, , , , name ».

, , React :

 const oldEffect = () => { document.title = 'Hello, Dan'; }; const oldDeps = ['Dan']; const newEffect = () => { document.title = 'Hello, Dan'; }; const newDeps = ['Dan']; // React     ,     . //      ,     . 

, , ! - - .

React


React — . , , , , useEffect , , , . ( !)

 function SearchResults() { async function fetchData() {   // ... } useEffect(() => {   fetchData(); }, []); //   ?  .      . // ... } 

FAQ , . .

« !», — . : , , . , , , — , .

, , . , , , , . , . .

, , .

, React


, , React , .

   useEffect(() => {   document.title = 'Hello, ' + name; }, [name]); 




, , , [] , , , , :

   useEffect(() => {   document.title = 'Hello, ' + name; }, []); // :    name 




. , «» , , .

, , , . , : « setInterval clearInterval ». . , , , useEffect , , , [] . - , ?

 function Counter() { const [count, setCount] = useState(0); useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); return <h1>{count}</h1>; } 

, , .

, « , », . , , , setInterval , . , ?

, — React , , . , count , React , , , . — .

count 0. setCount(count + 1) setCount(0 + 1) . , — [] , setCount(0 + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); //  setCount(1)     }, 1000);     return () => clearInterval(id);   },   [] //    ); // ... } //       1 function Counter() { // ... useEffect(   //     - ,    //   React  ,   .   () => {     const id = setInterval(() => {       setCount(1 + 1);     }, 1000);     return () => clearInterval(id);   },   [] ); // ... } 

React, , , — .

count — , ( ):

   const count = // ... useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, []); 

. React .


,

. , , React , , . — - .


React , . , , , .

, , , . count :

 useEffect(() => { const id = setInterval(() => {   setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]); 

. , , — , . count , count , setCount(count + 1) :

 //  ,   0 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(0 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [0] // [count] ); // ... } //  ,   1 function Counter() { // ... useEffect(   //       () => {     const id = setInterval(() => {       setCount(1 + 1); // setCount(count + 1)     }, 1000);     return () => clearInterval(id);   },   [1] // [count] ); // ... } 

, setInterval , count , . , .


,

, , , . — , .

.


, count .

 useEffect(() => {   const id = setInterval(() => {     setCount(count + 1);   }, 1000);   return () => clearInterval(id); }, [count]); 

, , count . , count setCount . , , count . , , setState :

   useEffect(() => {   const id = setInterval(() => {     setCount(c => c + 1);   }, 1000);   return () => clearInterval(id); }, []); 

« ». , count - , setCount(count + 1) . count - , count + 1 «» React. React count . , React — , , , .

setCount(c => c + 1) . « React », , . « » , , .

, , , . React. count :


,

.

, setInterval , , c => c + 1 . count . React .

Google Docs


, , — ? , , «», , . , Google Docs . . , .

, . . , setCount(c => c + 1) , , setCount(count + 1) , «» count . , ( — «»). « React» — . .

( ) , Google Docs . — , React . , , ( , , ) .

, setCount(c => c + 1) , . , . , , , , , . setCount(c => c + 1) . useReducer .


, : count step . setInterval , step :

 function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); useEffect(() => {   const id = setInterval(() => {     setCount(c => c + step);   }, 1000);   return () => clearInterval(id); }, [step]); return (   <>     <h1>{count}</h1>     <input value={step} onChange={e => setStep(Number(e.target.value))} />   </> ); } 

.

, React . step , . .

: step setIntervalstep . , , , ! , , , , , .

, , , setInterval , step . step ?

, , useReducer .

, setSomething(something => ...) , , . «», , , .

step dispatch :

 const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; useEffect(() => { const id = setInterval(() => {   dispatch({ type: 'tick' }); //  setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [dispatch]); 

.

: « , ?». , React , dispatch . .

!

( dispatch setstate useRef , React , . — .)

, , , , . step . , . , . :

 const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') {   return { count: count + step, step }; } else if (action.type === 'step') {   return { count, step: action.step }; } else {   throw new Error(); } } 

, , , .

useReducer — -


, , , . , , ? , , API <Counter step={1} /> . , props.step ?

, ! , :

 function Counter({ step }) { const [count, dispatch] = useReducer(reducer, 0); function reducer(state, action) {   if (action.type === 'tick') {     return state + step;   } else {     throw new Error();   } } useEffect(() => {   const id = setInterval(() => {     dispatch({ type: 'tick' });   }, 1000);   return () => clearInterval(id); }, [dispatch]); return <h1>{count}</h1>; } 

, . , , , , . .

dispatch . , , . .

, , . «» , , ? , dispatch , React . . .

useReducer «-» . , . , , , , .


, - , .

, , , :

 function SearchResults() { const [data, setData] = useState({ hits: [] }); async function fetchData() {   const result = await axios(     'https://hn.algolia.com/api/v1/search?query=react',   );   setData(result.data); } useEffect(() => {   fetchData(); }, []); //   ? // ... 

, .

, , . , , , , , , , , .

, , , , :

 function SearchResults() { // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=react'; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, , :

 function SearchResults() { const [query, setQuery] = useState('react'); // ,       function getFetchUrl() {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } // ,        async function fetchData() {   const result = await axios(getFetchUrl());   setData(result.data); } useEffect(() => {   fetchData(); }, []); // ... } 

, (, ), . .

, . , :

 function SearchResults() { // ... useEffect(() => {   //      !   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=react';   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, []); //    . // ... } 

.

? , « ». React, - .

getFetchUrl , query , , , , . — , query :

 function SearchResults() { const [query, setQuery] = useState('react'); useEffect(() => {   function getFetchUrl() {     return 'https://hn.algolia.com/api/v1/search?query=' + query;   }   async function fetchData() {     const result = await axios(getFetchUrl());     setData(result.data);   }   fetchData(); }, [query]); //    . // ... } 

.

, « React». query . , , , , . , , .

exhaustive-deps eslint-plugin-react-hooks , . , , .




.

, ?


. , , . , , .

? , . : React . . , « ». , , . , , , !

, , . , getFetchUrl :

 function SearchResults() { function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //  : getFetchUrl useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //  : getFetchUrl // ... } 

getFetchUrl — , .

, «» , . getFetchUrl (, , ), :

 function SearchResults() { //       function getFetchUrl(query) {   return 'https://hn.algolia.com/api/v1/search?query=' + query; } useEffect(() => {   const url = getFetchUrl('react');   // ...    -    ... }, [getFetchUrl]); //   ,     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //   ,     . // ... } 

, getFetchUrl . , — . - , , . , , , .

— .

, , :

 //        function getFetchUrl(query) { return 'https://hn.algolia.com/api/v1/search?query=' + query; } function SearchResults() { useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, []); //     . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, []); //     . // ... } 

, . , , .

. , useCallback :

 function SearchResults() { //    ,   const getFetchUrl = useCallback((query) => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []);  //      . useEffect(() => {   const url = getFetchUrl('react');   // ...    -   ... }, [getFetchUrl]); //      . useEffect(() => {   const url = getFetchUrl('redux');   // ...    -   ... }, [getFetchUrl]); //       // ... } 

useCallback . : , -, , , .

, . ( 'react' 'redux' ). , , , query . , , query , getFetchUrl .

, query useCallback :

 function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(() => { //   query   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, []); //  : query // ... } 

useCallback query , , getFetchUrl , query :

 function SearchResults() { const [query, setQuery] = useState('react'); //      query const getFetchUrl = useCallback(() => {   return 'https://hn.algolia.com/api/v1/search?query=' + query; }, [query]);  //    . useEffect(() => {   const url = getFetchUrl();   // ...    -   ... }, [getFetchUrl]); //    . // ... } 

useCallback , query , getFetchUrl , , . query , getFetchUrl , . Excel: - , , , .

— , . , :

 function Parent() { const [query, setQuery] = useState('react'); //      query const fetchData = useCallback(() => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + query;   // ...      ... }, [query]);  //       return <Child fetchData={fetchData} /> } function Child({ fetchData }) { let [data, setData] = useState(null); useEffect(() => {   fetchData().then(setData); }, [fetchData]); //       // ... } 

fetchData Parent query , Child , .

?


, , , , . , , , , :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -   ... }; render() {   return <Child fetchData={this.fetchData} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } render() {   // ... } } 

, : « , , , useEffectcomponentDidMount componentDidUpdate . !». componentDidUpdate :

 class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   //         if (this.props.fetchData !== prevProps.fetchData) {     this.props.fetchData();   } } render() {   // ... } } 

, fetchData — ! (, , , .) - , . this.props.fetchData prevProps.fetchData . , , ?

   componentDidUpdate(prevProps) {   this.props.fetchData(); } 

. . ( .) , fetchData this.state.query ?

   render() {   return <Child fetchData={this.fetchData.bind(this, this.state.query)} />; } 

this.props.fetchData !== prevProps.fetchData true , , query ! .

, , , query Child . , , query , query :

 class Parent extends Component { state = {   query: 'react' }; fetchData = () => {   const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;   // ...    -    ... }; render() {   return <Child fetchData={this.fetchData} query={this.state.query} />; } } class Child extends Component { state = {   data: null }; componentDidMount() {   this.props.fetchData(); } componentDidUpdate(prevProps) {   if (this.props.query !== prevProps.query) {     this.props.fetchData();   } } render() {   // ... } } 

, , - , , .

, , . this , . , , , , - . , this.props.fetchData , , , , , .

- useCallback . , , , . , . useCallback props.fetchData .

, useMemo :

 function ColorPicker() { //         Child, //       . const [color, setColor] = useState('pink'); const style = useMemo(() => ({ color }), [color]); return <Child style={style} />; } 

, useCallback , - . « », , , . , . , .

, fetchData ( ), . , , . (« props.onComplete , ?») , .


, :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . . — , :

 class Article extends Component { state = {   article: null }; componentDidMount() {   this.fetchData(this.props.id); } componentDidUpdate(prevProps) {   if (prevProps.id !== this.props.id) {     this.fetchData(this.props.id);   } } async fetchData(id) {   const article = await API.fetchArticle(id);   this.setState({ article }); } // ... } 

, , , . , . , {id: 10} , {id: 20} , , . , , , . .

, , . — , , async/await ( , - ) , ( , ).

, async -. (, , , , .)

, , ! .

, :

 function Article({ id }) { const [article, setArticle] = useState(null); useEffect(() => {   let didCancel = false;   async function fetchData() {     const article = await API.fetchArticle(id);     if (!didCancel) {       setArticle(article);     }   }   fetchData();   return () => {     didCancel = true;   }; }, [id]); // ... } 

, , , . , .


, , , , , . , , , . . — .

useEffect , , , . React. , useEffect .

, , « », . . , , , , «» , .

, useEffect , . — . — , , — , . , , , , API.

, , useFetch , , useTheme , . , , useEffect . , , , .

, , useEffect . — , . , . ?

Suspense React , , - ( : , , ) .

Suspense , , useEffect , , , - . , , , . , , , , .

まとめ


, . , , - , , , .

Source: https://habr.com/ru/post/J445276/


All Articles