useEffct

作者: 未路过 | 来源:发表于2022-11-01 16:03 被阅读0次

    1. 认识useEffect

    image.png

    ◼ 目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
     Effect Hook 可以让你来完成一些类似于class中生命周期的功能;
     事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects);
     所以对于完成这些功能的Hook被称之为 Effect Hook;

    import React, { memo } from 'react'
    import { useState, useEffect } from 'react'
    
    const App = memo(() => {
      const [count, setCount] = useState(200)
    
    //
      document.title = count
    
    
      return (
        <div>
          <h2>当前计数: {count}</h2>
          <button onClick={e => setCount(count+1)}>+1</button>
        </div>
      )
    })
    
    export default App
    

    //每次点击按钮文档名字都会改变。
    //这个代码是放在整个函数组件的基本逻辑里面是不合适的,因为函数式组件的目的是要告诉react你要帮我渲染的是一个什么样的内容。上面的useState是为了return里面的jsx里面来服务的,来提供数据的。
    但是 document.title = count是属于当前组件在完成渲染任务之外,额外的副作用的东西,这里,要告诉react你要渲染的东西,还要额外的修改document的标题。
    这个东西是这个执行主逻辑之外的一个副作用的东西。放在主逻辑里面不是不可以,只是不太合适。希望把它放在一个独立的地方,去给他完成一个对应的操作。

    2.使用

    把副作用的东西放到useEffct里面。
    useEffct是一个函数。本身接受一个回调函数。
    一旦组件渲染完成,就会执行里面的回调函数。

    import React, { memo } from 'react'
    import { useState, useEffect } from 'react'
    
    const App = memo(() => {
      const [count, setCount] = useState(200)
    
      useEffect(() => {
        // 当前传入的回调函数会在组件被渲染完成后, 自动执行
        // 网络请求/DOM操作(修改标题)/事件监听
        document.title = count
      })
    
      return (
        <div>
          <h2>当前计数: {count}</h2>
          <button onClick={e => setCount(count+1)}>+1</button>
        </div>
      )
    })
    
    export default App
    

    这个组件的目的是告诉react你要渲染的内容, const [count, setCount] = useState(200)是直接给我渲染的内容来服务的,提供数据的。副作用的东西用useEffct包裹起来了。执行完return后面,渲染以后,再执行useEffect里面的回调函数(副作用的逻辑)。

    3.解析

    useEffect的解析:
     通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作;
     useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
     默认情况下(useEffct里面只有一个参数,就算一个回调函数),无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;
    相当于componentDidMount+componentDidUpdate

    4. 需要清除Effect

    image.png

    ◼ 在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:
     比如我们之前的事件总线或Redux中手动调用subscribe;
     都需要在componentWillUnmount有对应的取消订阅;
     Effect Hook通过什么方式来模拟componentWillUnmount呢?

    import React, { memo, useEffect } from 'react'
    import { useState } from 'react'
    
    const App = memo(() => {
      const [count, setCount] = useState(0)
    
      // 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
      useEffect(() => {
        // 1.监听事件
        // const unubscribe = store.subscribe(() => {
        // })
        // function foo() {
        // }
        // eventBus.on("why", foo)
        console.log("监听redux中数据变化, 监听eventBus中的why事件")
    
        // 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
        return () => {
          console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")
        }
      })
    
      return (
        <div>
          <button onClick={e => setCount(count+1)}>+1({count})</button>
        </div>
      )
    })
    
    export default App
    

    componentWillUnmount

    useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:

    type EffectCallback = () => (void | (() => void | undefined));

    ◼ 为什么要在 effect 中返回一个函数?
     这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;
     如此可以将添加和移除订阅的逻辑放在一起;
     它们都属于 effect 的一部分;
    ◼ React 何时清除 effect?
     React 会在组件更新和卸载的时候执行清除操作;
     正如之前学到的,effect 在每次渲染的时候都会执行;

    useEffect(fn)
    fn会在第一次渲染组件和每次更新渲染的时候被回调。
    里面的fn有回调的化,依然是和fn搭配执行,
    fn里面打印1,retun的函数打印2,
    首次组件渲染,只会打印1.
    每次渲染的时候两个都会执行。21 21 21 21 总是成对出现的.
    因为每次监听的时候我都需要把以前的监听取消掉。
    但是返回值的回调也会在组件卸载的时候被执行。
    返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
    组件一般只会mount一次,就是第一次渲染的时候,而且是不会unmount的,除非强制ReactDOM.unmountComponentAtNode(document.getElementById("test"))
    父组件重新渲染的时候,子组件不会重新mount,只是发生了更新,哪怕是里面retrun的东西什么都没变。每次父组件重新渲染的时候,子组件的componentDidUpdate就会被调用。

    以前类组件里面,监听放在didmount里面,取消componentWillUnmount里面,没放在一起。现在用了hook,都放在useEffect里面。提高了内聚性。方便对代码进行管理。

    多个useEffct的使用

    image.png

    会按照书写顺序,依次执行。

    import React, { memo, useEffect } from "react";
    import { useState } from "react";
    import EntireWrapper from "./style";
    const Entire = memo(() => {
      const [count, setCount] = useState(0);
      useEffect(() => {
        console.log("1");
        return () => {
          console.log("2");
        };
      });
      useEffect(() => {
        console.log("1-1");
        return () => {
          console.log("2-2");
        };
      });
      useEffect(() => {
        console.log("1-1-1");
        return () => {
          console.log("2-2-2");
        };
      });
    
      return (
        <EntireWrapper>
          <div>
            <button onClick={(e) => setCount(count + 1)}>+1({count})</button>
          </div>
        </EntireWrapper>
      );
    });
    
    export default Entire;
    

    首次渲染
    1
    1-1
    1-1-1
    点击按钮
    2
    2-2
    2-2-2
    1
    1-1
    1-1-1

    每个useEffect里面写不同的逻辑
    第一个写对dom的监听
    第二个写对redux的监听
    第三个写eventbus的监听
    好处:
    1.每个有单独的逻辑,好管理
    2.可以把每个逻辑抽取到单独的自定义hook里面。就可以进行复用。以前的类组件是没办法分离的。

    useEffect性能优化

    image.png

    我们有些副作用,只需要执行一次。每次更新数据,都去拿,并不需要。对服务器有压力。
    某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);如何让一个回调只能执行一次呢?

    需要传入第二个参数
    参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)
    如果传入空数组的化,就代表我第一个回调函数谁的影响都不收,只在第一次渲染的时候执行一次。
    里面return的函数也只有再组件被卸载的时候才会执行。
    这个时候就是 componentDidMount和componentWillUnmount这两个函数。

    第二次参数写了[count],就代表只有count改变的时候,这个回调函数才会执行。

    useEffect可以模拟以前的声明周期,但是比以前的声明周期更强大。

    总结

    useEffect里面的函数,
    只有一个参数
    1.第一次渲染和每次更新都会执行。
    2.return的函数,每次更新的时候执行,而且是先执行输出2,后执行监听(回调函数里面的)的东西输出1。不会再首次渲染的时候执行。
    3.return的函数会在组件被卸载的时候执行。
    有第二个参数
    1.参数是一个空数组,函数只有再第一次渲染的时候才会执行。里面return的函数也只有在卸载的时候才会执行。

    2.参数是一个state的化,就只有在state更新的时候会执行。

    总结:useEffect执行副作用,可以模拟生命周期,比生命周期更强大。

    相关文章

      网友评论

          本文标题:useEffct

          本文链接:https://www.haomeiwen.com/subject/moejtdtx.html