完整的使用效果指南

您已经使用hooks编写了几个组件。 也许-他们甚至创建了一个小应用程序。 通常,您对结果感到满意。 您已经习惯了API,并在此过程中发现了一些显而易见的有用技巧。 您甚至创建了一些自己的钩子 ,并将代码减少到300行,在其中放置了以前由重复程序片段表示的内容。 您做了什么,向同事展示了。 “做得好,”他们谈到您的项目时说。


但是有时,当您使用useEffect ,软件机制的组件无法很好地组合在一起。 在您看来,您缺少什么。 所有这一切都类似于处理基于类的组件生命周期事件……但是真的是这样吗?
试图了解到底什么不适合您时,您注意到您在问以下问题:

  • 如何使用useEffect播放componentDidMount
  • 如何在useEffect加载数据? 什么是[]
  • 是否需要将功能指定为效果依赖项?
  • 为什么程序有时会陷入无休止的重新加载数据循环中?
  • 为什么有时会在效果内看到旧状态,还是找到旧属性?

当我第一次开始使用钩子时,这些问题也折磨了我。 即使在准备文档时,也不能说我完全掌握了一些细节。 从那时起,我经历了片刻,突然意识到重要的事情,我真的想大声喊道:“尤里卡!” 关于这些时刻我意识到的事情,我想告诉你。 现在,您对useEffect了解将使您清楚地看到上述问题的明显答案。

但是,为了查看这些问题的答案,我们首先需要退后一步。 本文的目的不是向读者提供使用useEffect的逐步指导。 可以useEffect它旨在帮助您“ useEffectuseEffect 。 坦率地说,没有太多的东西要学习。 实际上,大多数时候我们会花时间忘记以前知道的东西。

仅当我停止通过基于组件的组件生命周期的熟悉方法的棱镜来查看useEffect挂钩后,我的头脑中的所有东西才聚集在一起。

“您必须忘记所学的内容”


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


假定该材料的读者对useEffect API有点熟悉。 这是一篇相当长的文章,可以与一本小书相比。 事实是我更喜欢用这种方式表达自己的想法。 下面非常简短地给出了上面讨论的那些问题的答案。 也许它们对那些没有时间或不想阅读所有材料的人很有用。

如果考虑使用useEffect的格式及其所有说明和示例都不适合您,则可以稍等片刻-直到这些说明出现在无数其他手册中。 这与React库本身的故事相同,2013年它是全新的。 开发社区需要花费一些时间来认识新的心理模型,并需要基于该模型的教育材料。

问题答案


以下是本材料开头提出的问题的简短答案,供那些不想阅读全文的读者使用。 如果在阅读这些答案时感到没有真正理解所读内容的含义,请仔细阅读材料。 您将在文本中找到详细的说明。 如果您要阅读所有内容,则可以跳过本节。

▍如何使用useEffect播放componentDidMount?


尽管您可以使用useEffect(fn, [])构造来播放componentDidMount功能,但这并不完全等同于componentDidMount 。 即,它与componentDidMount不同,它捕获属性和状态。 因此,即使在回调内部,您也会看到初始属性和状态。 如果要查看某些内容的最新版本,可以在ref链接中编写它。 但是通常有一种更简单的方法来构造代码,因此执行此操作是可选的。 请记住,心理效应模型不同于可应用于componentDidMount和其他组件生命周期方法的模型。 因此,试图找到精确的等效物弊大于利。 为了高效地工作,可以这么说,您需要“思考效果”。 他们的心理模型的基础比对组件生命周期中的事件的响应更接近同步的实现。

▍如何在useEffect内正确加载数据? 什么是[]?


这是有关使用useEffect加载数据良好指南。 尝试完整阅读它! 它不那么大。 方括号[]代表一个空数组,表示该效果未使用参与React数据流的值,因此,单次使用可以认为是安全的。 此外,在效果中实际使用某个值的情况下,使用空的依赖关系数组是错误的常见来源。 您将需要掌握几种策略(主要以useReduceruseCallback的形式useCallback ),这些策略可以帮助消除对依赖的需求,而不是不合理地丢弃此依赖。

functions是否需要将功能指定为效果依赖项?


建议将不需要属性或状态的功能带到组件外部,建议将仅由效果使用的功能放置在效果内部。 如果在那之后您的效果仍然使用渲染范围内的函数(包括属性中的函数), useCallback它们包装在声明它们的useCallback中,然后尝试再次使用它们。 为什么这很重要? 函数可以从属性和状态中“查看”值,因此它们可以参与数据流。 这是我们的常见问题解答中有关此内容的更多详细信息。

▍为什么程序有时会陷入无休止的重新加载数据循环中?


当执行数据加载时没有第二个参数表示依赖项时,就会发生这种情况。 没有它,效果将在每个渲染操作之后执行-这意味着设置状态将导致此类效果被调用。 如果在依赖项数组中指示一个始终变化的值,则也可能发生无限循环。 一次消除一个依赖关系,找出可能产生的价值。 但是,删除依赖项(或轻率地使用[] )通常是解决问题的错误方法。 相反,您应该找到问题的根源并真正解决它。 例如,功能可能会导致类似的问题。 您可以通过将它们置于效果中,将它们移到组件之外或包装在useCallback来帮助解决该useCallback 。 为了避免创建多个对象,可以使用useMemo

▍为什么有时在效果中可以看到旧状态或找到旧属性?


效果始终从声明它们的渲染器中“查看”属性和状态。 这有助于防止错误 ,但是在某些情况下,它可能会干扰组件的正常运行。 在这种情况下,您可以显式使用可变ref链接来处理此类值(您可以在上述文章的末尾阅读有关此内容的信息)。 如果您认为您从旧的渲染器中看到了属性或状态,但是不希望如此,则可能是错过了一些依赖关系。 为了学会看他们,请使用短绒的这条规则。 再过几天,它将变成您的第二天性。 另外,请在我们的常见问题解答中查看答案。

我希望这些问题的答案对阅读它们的人有用。 现在让我们更多地讨论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存储的值的信息:

 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> ); } 

假设我执行以下操作序列:

  • 点击“ Click me按钮,将count的值设为3。
  • 单击Show alert按钮。
  • 在超时之前将其值增加到5。


单击“显示警报”按钮后增加计数值

您认为该消息框中出现了什么? 是否会在此处显示5(对应于触发计时器时的count数值),还是3(即按下按钮时的count数值)?

现在,您将找到此问题的答案,但是,如果您想自己查找所有内容, 这是此示例有效版本。

如果您所看到的似乎对您来说令人难以理解-这是一个更接近现实的例子。 想象一下一个聊天应用程序,在该应用程序的状态下,存储消息ID当前收件人的ID ,并且有一个“ Send按钮。 在材料中,将详细考虑正在发生的事情。 实际上,该消息框中出现的问题的正确答案是3。

显示消息框的机制“捕获”了单击按钮时的状态。

有一些方法可以实现行为的另一个版本,但是现在,我们将处理系统的标准行为。 在建立技术思维模型时,重要的是将“阻力最小的路径”与各种“紧急出口”区分开。

这一切如何运作?

我们已经说过,对于函数的每个特定调用, count的值都是一个常数。 我认为值得对此进行详细介绍。 关键是我们的函数被调用了多次(每次渲染操作一次),但是对于这些调用中的每一个,内部的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函数内部有一个本地常量name ,该name与特定调用中的person相关联。 这个常量是局部的,因此在不同的函数调用中它的值是相互隔离的! 结果,在超时后,每个显示的消息窗口都会“记住”自己的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数值直接嵌入到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数值:

 //     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:

  • , 0.

:

  • : <p>You clicked 0 times</p> .
  • , , : () => { document.title = 'You clicked 0 times' } .

React:

  • . . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 0 times' } .

, . , , - :

:

  • , React, 1.

React:

  • , 1.

:

  • : <p>You clicked 1 times</p> .
  • , , : () => { document.title = 'You clicked 1 times' } .

React:

  • . . , , - DOM.

:

  • , .

React:

  • , , .
  • () => { document.title = 'You clicked 1 times' } .


, , , , «» .

. :

 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 {id: 10} .
  • React {id: 20} .
  • React {id: 20} .

( , , .)

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

React , . , . . . :

  • React {id: 20} .
  • . {id: 20} .
  • React {id: 10} .
  • React {id: 20} .

, «» 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/zh-CN445276/


All Articles