自定义钩子内部的useEffect没有在React中的正确oreder中调用



我在一个名为useCustomHook的自定义挂钩中使用useEffect,我在两个组件中使用该useCustomHook,即(First,Second),但只有当FirstSecond组件被渲染时,才会调用useEffect

例如

我有一个第一组件

import React,{useState} from 'react'
import useCustomHook from './customHook'
function First(){
console.log("component First rendering")
const [count,setCount]=useState(0)
useCustomHook(count)
return (<div>First component</div>)
}

这是我的第二个组件

import React,{useState} from 'react'
import useCustomHook from './customHook'
function Second(){
console.log("component Second rendering")
const [count,setCount]=useState(0)
useCustomHook(count)
return (<div>Second component</div>)
}

这是我的客户Hook

import {useEffect} from 'react'
function useCustomHook(count){
console.log("useCustomHook getting called")
useEffect(()=>{
console.log("useEffect gets called") //this function is running after both component rendered
},[count])
}

我的主要应用程序组件

import First from './first'
import Second from './second'
function App(){
return (
<div>
<First/>
<Second/>
</div>
)
}

我的控制台输出为:

1) 组件首次呈现

2) useCustomHook被称为

3) 组件二次渲染

4) useCustomHook被称为

5) (2)useEffect被称为

我想知道

为什么行5输出不在行2之后,为什么Second组件日志发生在行2之后,因为useEffect应该在First组件调用useCustomHook之后但在调用Second组件日志之前调用。为什么CCD_ 16内部的CCD_ 15在CCD_ 17组件日志之前没有被调用。

您的输出是应该的。

我认为您对输出感到困惑,因为您认为useEffectcomponentDidMount相同,但这是不正确的。它们都是不同的,下面提到了它们之间的几个重要区别:

他们在不同的时间跑步

(与您的问题相关)

它们都是在组件的初始渲染之后调用的,但useEffect在浏览器绘制屏幕之后被称为,而componentDidMount在浏览器绘制之前被称为

捕捉道具和状态

(与您的问题无关,请随意跳到答案的末尾)

CCD_ 22捕获状态和道具,而CCD_ 23不执行此操作。

考虑以下代码片段来理解useEffect捕获状态和道具的含义。

class App extends React.Component {
constructor() {
super();
this.state = {
count: 0
};
}
componentDidMount() {
setTimeout(() => {
console.log('count value = ' + this.state.count);
}, 4000);
}
render() {
return (
<div>
<p>You clicked the button { this.state.count } times</p>
<button
onClick={ () => this.setState(prev => ({ count: prev.count + 1 })) }>
Increment Counter
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
console.log('count value = ' + count);
}, 4000);
}, [])

return (
<div>
<p>You clicked the button { count } times</p>
<button
onClick={ () => setCount(count + 1) }>
Increment Counter
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

这两个代码段是相同的,只是第一个代码段具有基于类的组件,而第二个代码段则具有函数组件。

这两个代码段的状态中都有一个名为count的变量,并且它们都在4秒后将count变量的值记录到控制台。它们还包括一个按钮,可用于增加count的值。

在控制台上记录count的值之前,尝试单击按钮(4或5次)。

如果您认为componentDidMountuseEffect是相同的,那么您可能会惊讶地发现,这两个代码片段在4秒后记录了count变量的不同值。

基于类的代码片段记录最新值,而基于功能组件的代码片段则记录count变量的初始值。

他们记录count变量不同值的原因是:

类组件内部的this.state总是指向最新状态,因此它会在4秒后记录count的最新值。
  • useEffect捕获count变量的初始值,并记录所捕获的值,而不是最新值。

  • 要深入解释useEffectcomponentDidMount之间的差异,我建议您阅读以下文章

    • useEffect(fn,[])不是新组件DidMount()

    • 使用Effect 的完整指南


    回到您的问题

    如果您注意了我回答的与您的问题相关的第一部分,那么您现在可能已经理解了为什么useEffect在安装了FirstSecond组件之后运行回调。

    如果没有,那就让我解释一下。

    在执行从First组件内部调用的useCustomHook函数后,将安装First组件,如果它是基于类的组件,则此时将调用其componentDidMount生命周期函数。

    在安装了First组件之后,就会安装Second组件,如果这也是一个基于类的组件,那么此时就会调用其componentDidMount生命周期函数。

    安装完两个组件后,浏览器会绘制屏幕,因此,您可以在屏幕上看到输出。浏览器绘制屏幕后,将对FirstSecond组件执行useEffect的回调函数。

    简而言之,useEffect允许浏览器在运行其效果/回调之前绘制屏幕。这就是为什么useEffect gets called记录在输出的末尾。

    你可以在官方文档上看到更多关于这方面的细节:效果的时机

    如果将FirstSecond组件转换为类组件,则输出将为:

    1. component First rendering
    2. component Second rendering
    3. component First mounted.      // console.log statement inside componentDidMount
    4. component Second mounted.     // console.log statement inside componentDidMount
    

    您可能希望第3行位于第2位,第2行位于第3位,但事实并非如此,因为react在所有子组件插入DOM之前首先执行它们的渲染函数,并且只有在它们插入DOM之后,每个组件的componentDidMount才会执行。

    如果您创建ThirdFourth组件,并创建以下类组件层次结构:

    App
    |__ First
    |     |__ Third
    |          |__ Fourth
    | 
    |__ Second
    

    然后您将看到以下输出:

    1.  First component constructor
    2.  component First rendering
    3.  Third component constructor
    4.  component Third rendering
    5.  Fourth component constructor
    6.  component Fourth rendering
    7.  Second component constructor
    8.  component Second rendering
    9.  component Fourth mounted
    10. component Third mounted
    11. component First mounted
    12. component Second mounted
    

    您提到的顺序非常合理,这就是钩子的工作方式。

    流量:

    • First组件开始执行
    • First组件中,useCustomHook(count)行代码之后,将执行useCustomHook
    • useCustomHook中,console.log被打印出来,useEffect被执行,而useEffect发生的CALLBACK被REGISTED和NOT执行
    • First组件返回JSX。即组件被安装/呈现
    • 一旦安装了First组件,就会调用useCustomHook中useEffect的回调
    • 基本上,First组件内部的useCustomHook的作用域是该组件

    第二个组件也是如此。。。

    相关内容

    • 没有找到相关文章

    最新更新