我在一个名为useCustomHook
的自定义挂钩中使用useEffect
,我在两个组件中使用该useCustomHook
,即(First,Second),但只有当First
和Second
组件被渲染时,才会调用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组件日志之前没有被调用。
您的输出是应该的。
我认为您对输出感到困惑,因为您认为useEffect
和componentDidMount
相同,但这是不正确的。它们都是不同的,下面提到了它们之间的几个重要区别:
他们在不同的时间跑步
(与您的问题相关)
它们都是在组件的初始渲染之后调用的,但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次)。
如果您认为componentDidMount
和useEffect
是相同的,那么您可能会惊讶地发现,这两个代码片段在4秒后记录了count
变量的不同值。
基于类的代码片段记录最新值,而基于功能组件的代码片段则记录count
变量的初始值。
他们记录count
变量不同值的原因是:
this.state
总是指向最新状态,因此它会在4秒后记录count
的最新值。
useEffect
捕获count
变量的初始值,并记录所捕获的值,而不是最新值。
要深入解释useEffect
和componentDidMount
之间的差异,我建议您阅读以下文章
useEffect(fn,[])不是新组件DidMount()
使用Effect 的完整指南
回到您的问题
如果您注意了我回答的与您的问题相关的第一部分,那么您现在可能已经理解了为什么useEffect
在安装了First
和Second
组件之后运行回调。
如果没有,那就让我解释一下。
在执行从First
组件内部调用的useCustomHook
函数后,将安装First
组件,如果它是基于类的组件,则此时将调用其componentDidMount
生命周期函数。
在安装了First
组件之后,就会安装Second
组件,如果这也是一个基于类的组件,那么此时就会调用其componentDidMount
生命周期函数。
安装完两个组件后,浏览器会绘制屏幕,因此,您可以在屏幕上看到输出。浏览器绘制屏幕后,将对First
和Second
组件执行useEffect的回调函数。
简而言之,useEffect
允许浏览器在运行其效果/回调之前绘制屏幕。这就是为什么useEffect gets called
记录在输出的末尾。
你可以在官方文档上看到更多关于这方面的细节:效果的时机
如果将First
和Second
组件转换为类组件,则输出将为:
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
才会执行。
如果您创建Third
和Fourth
组件,并创建以下类组件层次结构:
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
的作用域是该组件
第二个组件也是如此。。。