【React】hooks之useEffect

useEffect是react官方提供的hook之一,对于初学者来说,理解好这个hook对于更好掌握react是很重要的。小伙伴们可以先看下官方文档中对useEffect的说明:useEffect官方文档传送门
注意:useEffect是在每次函数组件render完成之后再判断是否要执行的

useEffect的参数

setup函数

useEffect(setup, dependencies)
第一个参数:一个回调函数(将其内部的代码称之为Effect中),会在组件渲染完之后执行
● 在开发中,会将那些产生副作用(即会影响到组件的该次渲染)的代码,编写到useEffect传入的函数的函数体中,这样就可以避免这些代码影响到组件的渲染
● 在这个回调函数中,可以指定一个函数作为返回值,我们可以称之为清理函数
● 在这个清理函数中可以做一些工作清除上一次Effect执行所带来的影响,即让旧的Effect不影响新的Effect执行
● 这个清理函数会在下一次执行Effect的时候,比函数体中的代码先执行
● 这个清理函数只会在组件再次渲染后,要再次执行副作用之前执行,也即是在上一个组件销毁前执行。
● 另外再提一点,如果想要副作用在组件unmount之后执行,比如在组件卸载后,移除Effect中监听的事件,就可以返回一个清理函数,在该清理函数中移除对应的事件。useEffect(() => { return () => {} }, [])

依赖项数组

第二个参数(可选):依赖项组成的数组
● 即可以指定useEffect的依赖项,只有当依赖项改变的时候,Effect才会再次触发
● 如果不指定依赖项数组,即每次组件渲染完成后,都会执行对应回调(第一个参数)
● 如果指定了,那么只有在依赖项发生变化的时候,才会执行对应回调(第一个参数)
● 如果指定的是一个空数组,那么,其只会在组件初次渲染的时候执行
● 这个比较是浅比较(也就是比较的是依赖指向的内存空间),官方文档里说的是通过Object.is()进行比较,那么,如果依赖项是一个对象类型的数据的时候,就要特别注意了,即使存储的值不变,但是对象指向的地址发生了变化,即依赖项改变。
● 通常会将Effect中使用的所有变量都设置为依赖项(如果依赖项中的变量是不在回调函数中用到的,那么其实是没有意义的),这样就可以确保当这些值发生变化后,会触发Effect的执行,像setState是由钩子函数useState()生成的,useState会确保组件的每次渲染都取到相同的setState,所以类似于setState可以不写到useEffect的依赖项中

useEffect的返回值

返回值:undefined

useEffect执行流程理解

不过我们最好再来看看官方文档中的描述:
setup:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。在将组件首次添加到 DOM 之前,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。

可选 dependencies:setup 代码中引用的所有响应式值的列表。响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数。如果你的代码检查工具 配置了 React,那么它将验证是否每个响应式值都被正确地指定为一个依赖项。依赖项列表的元素数量必须是固定的,并且必须像 [dep1, dep2, dep3] 这样内联编写。React 将使用 Object.is 来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行 Effect 函数。如果你想了解更多,请参见 传递依赖数组、空数组和不传递依赖项之间的区别。

可以看出,这里的useEffect能够对应类组件的ComponentDidMount、ComponentDidUpdate、ComponentDidWillUnmounted三个生命周期函数,不过我们并不需要去对应

下面我们通过几个例子,理解一下useEffect的执行机制。

import { useState, useEffect } from "react";x	

const App = () => {
  console.log('组件被渲染了');
  const [count, setCount] = useState(0);

  // 这样会触发多次的重渲染,会报错,因为其是在渲染阶段执行的,那么,第一次useCount触发渲染,再次渲染时,count基于的值还是0(上一次渲染还没有完成),然后又会触发渲染,如此往复,成了一个死循环。
  ```js
  // useCount(1);

  // 下面这样可以使setState正常
  // setTimeout(() => {
  //   setCount(1);
  // }, 0);

  // 又或者是这样,可以实现我们上述的需求
  useEffect(() => {
    console.log('useEffect的回调被执行了');
    setCount(1);
  }, [count]);

  return <div>{count}</div>;
};

export default App;

如果这样,那么控制台输出的是:在这里插入图片描述

● 当第一次组件被渲染后(打印:组件被渲染了)
● 然后会执行一次useEffect的回调(打印:useEffect的回调被执行了)
● 其中setCount更改了组件中的count这个state,又会触发组件的一次渲染(打印:组件被渲染了)
● 而这时,又因为count变成了1(useEffect的依赖项更改),因此useEffect中的回调再一次执行(打印:useEffect的回调被执行了)
● 然后肯定是App函数又被执行了(这里本人还有点疑问),然后检查count没有发生变化,因此不再会执行Effect

如果我们给Effect加上清理函数(即该Effect的返回值,返回一个函数):

useEffect(() => {
    setCount(1);
    console.log("Effect执行了");

    return () => {
      console.log("Effect返回的函数执行了");
    };
  }, [count]);

执行结果:,说明依赖发生变化
再次要执行Effect的时候,Effect的返回值会先于Effect函数体中的代码执行。

如果我们的依赖项没有发生变化:

useEffect(() => {
    // setCount(1);
    console.log("Effect执行了");

    return () => {
      console.log("Effect返回的函数执行了");
    };
  }, [count]);

控制台:在这里插入图片描述
也就是说清理函数只会在依赖项发生变化的时候执行(准确来说只有依赖项发生变化后,才会再次执行Effect,对应的也就是在此Effect执行之前执行对应的返回值)

清理函数用法举例

清理函数用法举例:比如在在Effect中执行的操作,需要对其做一个防抖操作(新手忘了就去查!)的时候(需要用定时器将这次的Effect包裹起来),然后我们可以在Effect的清理函数中执行clearTimeout清除上一个定时器
再举一个例子,比如说我们这时有一个用来显示文章内容的组件,该组件接收一个id prop,当id变化的时候,执行副作用,发送新的网络请求,更新文章内容。

import React, { useState, useEffect } from 'react'

function getArticle ({ id }) {
  const [ articleContent, setArticleContent ] = useState(null)

  useEffect(() => {
    const fetchArticalContentData = async () => {
      setArticleContent(null)
      const res = fetch(`/.../${id}`)
      setArticleContent(res)
    }
    fetchArticleContent()
  }, [id])

  const idLoading = ! articleContent
  return (
    <div>
      { isLoading ? "加载中" : articleContent }
    </div>
  )
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值