【问题标题】:useEffect inside custom hook not getting called in correct oreder in React自定义钩子中的useEffect没有在React中以正确的顺序调用
【发布时间】:2020-06-11 14:27:20
【问题描述】:

我在一个名为useCustomHook 的自定义钩子中使用useEffect,我在两个组件(第一、第二)中使用这个useCustomHook,但useEffect 仅在First 和@987654326 时被调用@component 得到渲染。

例如

我有一个第一个组件

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

}

这是我的自定义钩子

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应该在useCustomHook被调用之后被调用First 组件,但在此之前调用 Second 组件日志。为什么useEffectuseCustomHook 内没有在Second 组件日志之前被调用。

【问题讨论】:

  • 使用useLayoutEffect 来获得想要的结果。 useEffect 首次渲染后触发
  • 不,我只是想知道为什么 useEffect 会发生这种行为

标签: javascript reactjs react-hooks


【解决方案1】:

你的输出是应该的。

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

它们在不同的时间运行

(与您的问题有关)

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

捕获道具和状态

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

useEffect 捕获状态和道具,而 componentDidMount 不这样做。

考虑以下代码 sn-ps 以了解 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>

两个代码 sn-ps 是相同的,除了第一个具有基于类的组件,第二个具有功能组件。

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

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

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

基于类的代码 sn-p 记录最新值,而基于功能组件的代码 sn-p 记录 count 变量的初始值。

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

  • 类组件内部的this.state总是指向最新状态,因此它会在4秒后记录count的最新值。

  • useEffect捕获count变量的初始值并记录捕获的值而不是最新值。

要深入了解useEffectcomponentDidMount的区别,建议你阅读以下文章


回到你的问题

如果您注意了与您的问题相关的我的回答的第一部分,您现在可能明白为什么useEffectFirstSecond 组件都安装后运行它的回调。

如果不是,那么让我解释一下。

在执行从First 组件中调用的useCustomHook 函数后,会挂载First 组件,如果它是基于类的组件,则此时将调用其componentDidMount 生命周期函数.

First 组件挂载后,Second 组件挂载,如果这也是基于类的组件,则此时将调用其componentDidMount 生命周期函数。

安装两个组件后,浏览器会绘制屏幕,​​结果,您会在屏幕上看到输出。浏览器绘制完屏幕后,FirstSecond 组件都会执行 useEffect 的回调函数。

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

您可以在官方文档中查看更多详细信息:Timing of effects

如果将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

【讨论】:

【解决方案2】:

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

流程:

  • First 组件开始执行。
  • First组件中,useCustomHook(count)这行代码之后,useCustomHook会被执行
  • useCustomHook 中打印console.log 并执行useEffect 并注册和不执行使用效果的CALLBACK。
  • First 组件返回 JSX。即组件已安装/渲染。
  • 一旦First 组件被挂载,就会调用useCustomHook 中的useEffect 回调。
  • 基本上First 组件内的useCustomHook 的作用域是该组件。

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

【讨论】:

  • 但是第一个组件应该先渲染,然后调用useEffect回调,但是为什么第二个组件在第一个组件的useEffect回调之前被调用
猜你喜欢
  • 2021-09-10
  • 2020-03-23
  • 1970-01-01
  • 2020-09-15
  • 2020-08-08
  • 2022-10-09
  • 1970-01-01
  • 2020-02-19
  • 1970-01-01
相关资源
最近更新 更多