20230523----重返学习-Hooks组件useState()-Hooks组件useEffect()-Hook函数组件说明

99 阅读20分钟

day-076-seventy-six-20230523-Hooks组件useState()-Hooks组件useEffect()-Hook函数组件说明

Hooks组件useState()

React Hooks 组件化开发

  • React 组件分类

    • 函数组件

      • 不具备状态ref周期函数等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件!

      • 但是具备属性及插槽,父组件可以控制其重新渲染!

      • 渲染流程简单,渲染速度较快!

      • 基于FP函数式编程思想设计,提供更细粒度的逻辑组织和复用!

        • FP-Funtional Programming-函数式编程思想。
        • oop-面向对象编程思想。
        • pop-面向过程。
    • 类组件

      • 具备状态ref周期函数属性插槽等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情
      • 渲染流程繁琐,渲染速度相对较慢!
      • 基于OOP面向对象编程思想设计,更方便实现继承等!
  • React Hooks组件,就是基于React中新提供的Hook函数,可以让函数组件动态化!

Hook 函数概览

  • HookReact16.8的新增特性!并且只能运用到函数组件中!

    • Hook
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZtMC5FY-1684857369285)(./ReactHooks的所有HookAPI.jpg)]
  • React Hooks组件化开发:

    • 2023前端当下和未来的主流编程思想:函数式编程FP,也叫去面向对象化。

    • React开发中,目前最常用的组件类型,应该是函数式组件类组件偶尔使用。

    • React提供的各种HookAPI,就是让函数组件可以像类组件一样,具备状态周期函数Ref 等操作,让函数组件从静态化转为动态化

    • 所有的HookAPI特点:

      • 都是以useXxx的格式命名的。

      • 只能用在函数组件中,在类组件中用不了。

        • 类组件中也没必要用。
    • 在函数式组件内部中,thisundefined

  • HookAPI:

    1. useState():在函数组件中使用状态,并且提供修改状态让视图更新的办法。

      • let [xxx,setXxx] = useState(初始值)

        • 每一次执行useState()就创建一个状态。

          • 如果需要创建多个状态,则可以让其执行多次,或者使用useReducer()这个Hook函数
        • useState()执行的返回结果是一个数组:[当前状态值,修改状态并让视图更新的函数]。

          • 声明一个叫做xxx的变量,用于存储状态值。
          • setXxx(值):把状态值修改为传递的值,并且让组件更新。
  • Hook组件与类组件的对比:

    • 样式组件/src/views/VoteBoxStyle.jsx:

      import styled from "styled-components"
      
      const VoteBoxStyle = styled.div`
          box-sizing: border-box;
          margin: 20px auto;
          padding:10px 20px;
          width: 300px;
          border: 1px solid #DDD;
      
          .title{
              display: flex;
              justify-content: space-between;
              align-items: center;
              line-height: 50px;
              font-size: 18px;
              border-bottom:1px dashed #DDD;
      
              span{
                  color: #ff4d4f;
              }
          }
      
          .main-box{
              padding: 10px 0;
      
              p{
                  font-size: 14px;
                  line-height: 30px;
              }
          }
          
          .footer-box{
              .ant-btn{
                  margin-right: 10px;
              }
          }
      `
      export default VoteBoxStyle
      
    • Hook组件/src/views/VoteBox.jsx:

      import React, { useState } from "react";
      import { Button } from "antd";
      import VoteBoxStyle from "./VoteBoxStyle";
      
      export default function VoteBox() {
        // console.log("this", this);//undefined;
        // console.log(useState(6));
        let [supNum, setSupNum] = useState(6);
        let [oppNum, setOppNum] = useState(2);
        let total = supNum + oppNum;
        let ratio = `--`;
        if (total > 0) {
          ratio = `${((supNum / total) * 100).toFixed(2)}%`;
        }
      
        // 定义普通函数;
        const handle = (type) => {
          if (type === `sup`) {
            setSupNum(supNum + 1)
            return
          }
      
          setOppNum(oppNum + 1)
        }
      
        return (
          <VoteBoxStyle>
            <h2 className="title">
              React其实也不难!
              <span>{total}</span>
            </h2>
            <div className="main-box">
              <p>支持人数:{supNum} 人</p>
              <p>反对人数:{oppNum} 人</p>
              <p>支持比率:{ratio}</p>
            </div>
            <div className="footer-box">
              <Button type="primary" onClick={handle.bind(null, `sup`)}>
                支持
              </Button>
              <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
                反对
              </Button>
            </div>
          </VoteBoxStyle >
        );
      }
      
    • 类组件/src/views/VoteBox.jsx:

      import React from "react"
      import { Button } from 'antd'
      import VoteBoxStyle from "./VoteBoxStyle"
      console.log(React);
      // 基于类组件完成需求:
      export default class VoteBox extends React.Component {
        state = {
          supNum: 6,
          oppNum: 2,
        }
        handle = (type) => {
          let { supNum, oppNum } = this.state;
          if (type === `sup`) {
            this.setState({
              supNum: supNum + 1
            })
            return
          }
          this.setState({
            oppNum: oppNum + 1
          })
        }
        render() {
          let { supNum, oppNum } = this.state;
          let total = supNum + oppNum;
          let ratio = `--`;
          if (total > 0) {
            ratio = `${((supNum / total) * 100).toFixed(2)}%`
          }
          return <VoteBoxStyle>
            <h2 className="title">
              React其实也不难!
              <span>{total}</span>
            </h2>
            <div className="main-box">
              <p>支持人数:{supNum} 人</p>
              <p>反对人数:{oppNum} 人</p>
              <p>支持比率:{ratio}</p>
            </div>
            <div className="footer-box">
              <Button type="primary" onClick={this.handle.bind(null,`sup`)}>支持</Button>
              <Button type="primary" danger onClick={this.handle.bind(null,`opp`)}>反对</Button>
            </div>
          </VoteBoxStyle>
        }
      }
      
      • 主要用到面向对象,this指向,原型链。

模拟useState的源码

// 模拟useState的源码
let state //这个是用于存储当前状态值的。
const useState = function useState(initialValue){
  // 只有第一次执行useState时,才会给状态赋值初始值。
  if(typeof state ==='undefined'){
    state = initialValue
  }
  // 修改状态的函数
  const change = (value)=>{
    state = value
    // 让视图更新
    // ...
  }

  return [state,change]
}
  • 也就是更新时的useState(initialValue),不会再走初始值了。

    • 但是在更新执行那个函数时,依旧会执行Hook函数,只是这个Hook函数返回的值变化了。

      • 也就是说:调用的Hook方法该重新执行还是重新执行。
      import React, { useState } from "react";
      import { Button } from "antd";
      import VoteBoxStyle from "./VoteBoxStyle";
      export default function VoteBox() {
        let [supNum, setSupNum] = useState(6);//第一次渲染时,会执行这个函数`useState(6)`,返回的值为[6,修改状态值的函数]。而到了非第一次渲染的更新时,依旧还是会执行`let [supNum, setSupNum] = useState(6)`,只是此时`useState(6)`返回的值变成了`[上一次传递给函数的状态值,修改当前状态值的函数]`
        return (
          <h2 className="title">
            React其实也不难!
            <span>{supNum}</span>
          </h2>
        );
      }
      

函数组件的闭包

  • 函数组件的每一次渲染和更新,都是把函数重新执行,产生一个新的闭包,多次调用组件并多次渲染,都是在自己的闭包中,进行相关的处理,相互之间不会有影响。

    • 每一次函数执行,props始终都在,都是父组件传的属性值。

    • 每一次函数执行,函数中的代码都会重新自上而下执行一遍。

      • 调用的Hook方法该重新执行还是重新执行。

      • 如果遇到的是创建函数:则每个闭包中都会重新创建一个新的函数,而此函数的作用域就是当前这个闭包。

        • 新创建的函数依旧还是会被重新创建,得到一个新的堆内存地址。
      • 如果是创建私有变量:则每个闭包中都有这个变量,用于存储闭包作用域中属于自己的值。

      • 如果是useState:则第一次渲染会让状态等于初始值,后期再更新的时候,状态等于最新状态值。

    • 如果要判断某个变量是什么值,需要缕清楚其作用域链(或者是其属于那个闭包),才能知道值具体是多少!

import React, { useState } from "react";
import { Button } from "antd";
import VoteBoxStyle from "./VoteBoxStyle";

export default function VoteBox() {
  // console.log("this", this);//undefined;
  // console.log(useState(6));
  let [supNum, setSupNum] = useState(6);
  let [oppNum, setOppNum] = useState(2);
  let total = supNum + oppNum;
  let ratio = `--`;
  if (total > 0) {
    ratio = `${((supNum / total) * 100).toFixed(2)}%`;
  }

  // 定义普通函数;
  const handle = (type) => {
    if (type === `sup`) {
      setSupNum(supNum + 1)

      

      setTimeout(()=>{
        console.log(supNum);//依旧是当前闭包中的值。
      })

      return
    }

    setOppNum(oppNum + 1)
  }

  return (
    <VoteBoxStyle>
      <h2 className="title">
        React其实也不难!
        <span>{total}</span>
      </h2>
      <div className="main-box">
        <p>支持人数:{supNum} 人</p>
        <p>反对人数:{oppNum} 人</p>
        <p>支持比率:{ratio}</p>
      </div>
      <div className="footer-box">
        <Button type="primary" onClick={handle.bind(null, `sup`)}>
          支持
        </Button>
        <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
          反对
        </Button>
      </div>
    </VoteBoxStyle >
  );
}

组件中多个状态和处理

  • useState中多个状态的处理:

    • 官方推荐:需要多个状态,则执行useState,创建多个状态及修改状态的方法,让每个状态单独管理。

      // 多状态的处理-多次useState()。
      import React, { useState } from "react";
      import { Button } from "antd";
      import VoteBoxStyle from "./VoteBoxStyle";
      export default function VoteBox() {
        let [supNum, setSupNum] = useState(6);
        let [oppNum, setOppNum] = useState(2);
      
        // 定义普通函数;
        const handle = (type) => {
          if (type === `sup`) {
            setSupNum(supNum + 1)
      
            return
          }
      
          setOppNum(oppNum + 1)
        }
        return (
          <VoteBoxStyle>
            <h2 className="title">
              React其实也不难!
              <span>{supNum + oppNum}</span>
            </h2>
            <div className="main-box">
              <p>支持人数:{supNum} 人</p>
              <p>反对人数:{oppNum} 人</p>
            </div>
            <div className="footer-box">
              <Button type="primary" onClick={handle.bind(null, `sup`)}>
                支持
              </Button>
              <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
                反对
              </Button>
            </div>
          </VoteBoxStyle >
        );
      }
      
      • 好处:

        • 状态分离,增删都方便处理。
    • 当前也可以执行一次useState,只不过创建的这个状态,需要是一个对象,对象中包含了需要的其它状态值。

      // 单状态的处理-单次调用useState()-传递的为对象。
      import React, { useState } from "react";
      import { Button } from "antd";
      import VoteBoxStyle from "./VoteBoxStyle";
      export default function VoteBox() {
        let [state, setState] = useState({
          supNum: 6,
          oppNum: 2
        });
        const handle = (type) => {
          if (type === `sup`) {
            setState({ ...state, supNum: state.supNum + 1 })
            return
          }
          setState({ ...state, oppNum: state.oppNum + 1 })
        }
        return (
          <VoteBoxStyle>
            <h2 className="title">
              React其实也不难!
              <span>{state.supNum + state.oppNum}</span>
            </h2>
            <div className="main-box">
              <p>支持人数:{state.supNum} 人</p>
              <p>反对人数:{state.oppNum} 人</p>
            </div>
            <div className="footer-box">
              <Button type="primary" onClick={handle.bind(null, `sup`)}>
                支持
              </Button>
              <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
                反对
              </Button>
            </div>
          </VoteBoxStyle >
        );
      }
      
      • 但是这种方式我们不推荐:

        • 基于useState获取的修改状态方法,setXxx,在修改状态值的时候,不支持部分状态的更改。

          setState({ supNum: state.supNum + 1 })
          
          • 只有React.prototype.setState才支持部分状态更改。
        • setXxx()中传递什么值,就把状态值整体改为什么。

          setState({ supNum: state.supNum + 1 })//会直接用这个`{ supNum: state.supNum + 1 }的内存地址`把旧的state对象的内存地址改了。也就是说,旧state对象被完全修改了。
          
          • 如何解决?

            • 把要修改的值浅拷贝一份。

              setState({ ...state,supNum: state.supNum + 1})
              
              • 在修改成为新的状态对象之前,先把现有的状态信息浅拷贝一份,在修改的时候,没改的用原始值,改变的地方用用新改的值。

                setState({ 
                  ...state,//把要修改的值浅拷贝一份。
                  supNum: state.supNum + 1
                })
                

隋性初始state值

  • 如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用,不想在后续更新时再把逻辑走一遍。

    // 隋性初始化初始状态值-正常的用非函数的值来初始化。
    import React, { useState } from "react";
    import { Button } from "antd";
    import VoteBoxStyle from "./VoteBoxStyle";
    export default function VoteBox() {
      // 如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用。
      let ran = Math.round(Math.random() * 9 + 1)//每次更新时都会执行一遍。
      console.log('ran',ran);//每次更新时都会执行一遍。
      let [supNum, setSupNum] = useState(ran);
      let [oppNum, setOppNum] = useState(2);
      // 定义普通函数;
      const handle = (type) => {
        if (type === `sup`) {
          setSupNum(supNum + 1)
          return
        }
        setOppNum(oppNum + 1)
      }
      return (
        <VoteBoxStyle>
          <h2 className="title">
            React其实也不难!
            <span>{supNum + oppNum}</span>
          </h2>
          <div className="main-box">
            <p>支持人数:{supNum} 人</p>
            <p>反对人数:{oppNum} 人</p>
          </div>
          <div className="footer-box">
            <Button type="primary" onClick={handle.bind(null, `sup`)}>
              支持
            </Button>
            <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
              反对
            </Button>
          </div>
        </VoteBoxStyle >
      );
    }
    
    • 可以发现,初始化状态值所用的代码每次更新时都会执行一遍。

      • 但是执行后的结果,在更新时都不再被需要了。造成性能浪费。
    • 解决方案:执行useState()初始化状态时,传递给useState()的是一个函数。

      • 此函数只对第一次执行useState(),并把返回值,作为状态的初始值。后续更新时,该函数都不再执行。

        // 隋性初始化初始状态值-基于函数的方式处理。
        import React, { useState } from "react";
        import { Button } from "antd";
        import VoteBoxStyle from "./VoteBoxStyle";
        export default function VoteBox() {
          let [supNum, setSupNum] = useState(() => {
            // 如果状态的初始值需要计算,而且是经过复杂计算、消耗很多性能算出来的。而且只需要第一次使用,我们基于函数的方式处理。
            //此函数只对第一次执行useState(),并把返回值,作为状态的初始值。
            let ran = Math.round(Math.random() * 9 + 1)
            console.log('ran', ran);
            //...
            return ran
          });
          let [oppNum, setOppNum] = useState(2);
          // 定义普通函数;
          const handle = (type) => {
            if (type === `sup`) {
              setSupNum(supNum + 1)
              return
            }
            setOppNum(oppNum + 1)
          }
          return (
            <VoteBoxStyle>
              <h2 className="title">
                React其实也不难!
                <span>{supNum + oppNum}</span>
              </h2>
              <div className="main-box">
                <p>支持人数:{supNum} 人</p>
                <p>反对人数:{oppNum} 人</p>
              </div>
              <div className="footer-box">
                <Button type="primary" onClick={handle.bind(null, `sup`)}>
                  支持
                </Button>
                <Button type="primary" danger onClick={handle.bind(null, `opp`)}>
                  反对
                </Button>
              </div>
            </VoteBoxStyle >
          );
        }
        

useState()返回的数组中的修改值函数setXxx()的异步更新机制

import { useState } from "react";
import { Button } from "antd";

export default function Demo() {
  console.log('render')
  let [x, setX] = useState(10);
  let [y, setY] = useState(20);
  let [z, setZ] = useState(30);

  const handle = () => {
    setX(x + 1);
    setY(y + 1);
    setZ(z + 1);
  };

  return (
    <div className="demo" style={{ padding: "50px" }}>
      <p>
        x:{x}-y:{y}-z:{z}
      </p>
      <Button type="primary" size="small" onClick={handle}>
        按钮
      </Button>
    </div>
  );
}
  • useState()返回的数组中的修改值函数setXxx()的异步更新机制.jpg

  • 基于useState创建状态,会拿到修改状态的方法setXxx()。

    • 在React18中,执行setXxx()是异步去修改状态和让视图更新的。

      • 底层机制:updater更新队列机制:

        • 当然也可以基于flushSync让其变为类似于同步的效果(立即刷新渲染队列)!
      • 不论修改状态是同步还是异步,此处获取的x的值,永远都不会是最新修改的。用的都是现在闭包中的值。最新修改的状态值,只能在下一个闭包中获取!

        1. 即便把修改状态的操作,基于flushSync()处理了,也仅仅是让其立即更新渲染一次,在它的下面,依然无法获取最新修改的状态值。

        • 而在类组件中:

          1. 我们还可以基于this.setState()中的callback()函数,通过this.state.xxx获取最新状态值。

            this.setState({x:x+1},()=>{
              //this.state可以拿到最新的x。
              console.log(this.state.x);
            })
            
          2. 或者基于flushSync()把状态修改变为类似于同步效果,然后在flushSync()下面,就可以获取最新状态值。

            this.setState({x:x+1},()=>{
              //this.state可以拿到最新的x。
              console.log(this.state.x);
            })
            
        import { useState } from "react";
        import { Button } from "antd";
        import { flushSync } from "react-dom";
        
        export default function Demo() {
          console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。
          let [x, setX] = useState(10);
          let [y, setY] = useState(20);
          let [z, setZ] = useState(30);
        
          console.log("新一次渲染的一次闭包中的x", x);
        
          const handle = () => {
            flushSync(() => {
              setX(x + 1);//会先更新,但下方的x依旧是旧的,只有新的闭包中的x才是本次闭包操作中的x+1;
            })
        
            setY(y + 1);
            setZ(z + 1);
            console.log("点击后x", x);//不论修改状态是同步还是异步,此处获取的x的值,永远都不会是最新修改的。用的都是现在闭包中的值。最新修改的状态值,只能在下一个闭包中获取!即便把修改状态的操作,基于flushSync处理了,也仅仅是让其立即更新渲染一次,在它的下面,依然无法获取最新修改的状态值。
            // 而在类组件中,我们还可以基于this.setState中的callback函数,通过this.state.xxx获取最新状态值。或者基于flushSync()处理等。
          };
        
          return (
            <div className="demo" style={{ padding: "50px" }}>
              <p>
                x:{x}-y:{y}-z:{z}
              </p>
              <Button type="primary" size="small" onClick={handle}>
                按钮
              </Button>
            </div>
          );
        }
        
    • 在React16中,setXxx有时候是异步操作,有时候是同步操作。

      • 在其它异步操作中,它本身是同步的。
      • 在非异步操作中,它本身是异步操作的!
  • 在同步异步性的处理上,和类组件中的this.setState()/this.forceUpdate()是保持一致的!

useState()返回的数组中的修改值函数setXxx()的累计更新

  • 直接调用setXxx(),不能直接修改值。

    • 并且在同步的for循环过程中,x依旧都是旧闭包作用域中的x。

      import { useState } from "react";
      import { Button } from "antd";
      import { flushSync } from "react-dom";
      export default function Demo() {
        console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。
        let [x, setX] = useState(10);
        let [y, setY] = useState(20);
        let [z, setZ] = useState(30);
        console.log("新一次渲染的一次闭包中的x", x);
        const handle = () => {
          for (let i = 0; i < 10; i++) {
            setX(x +1);
          }
        };
        return (
          <div className="demo" style={{ padding: "50px" }}>
            <p>
              x:{x}-y:{y}-z:{z}
            </p>
            <Button type="primary" size="small" onClick={handle}>
              按钮
            </Button>
          </div>
        );
      }
      
  • 基于函数来做到更新:

    import { useState } from "react";
    import { Button } from "antd";
    import { flushSync } from "react-dom";
    export default function Demo() {
      console.log("render"); //只打印一次,证明setXxx()是异步修改状态的。
      let [x, setX] = useState(10);
      let [y, setY] = useState(20);
      let [z, setZ] = useState(30);
      console.log("新一次渲染的一次闭包中的x", x);
      const handle = () => {
        for (let i = 0; i < 10; i++) {
          setX(prev => {
            return prev + 1
          });
        }
      };
      return (
        <div className="demo" style={{ padding: "50px" }}>
          <p>
            x:{x}-y:{y}-z:{z}
          </p>
          <Button type="primary" size="small" onClick={handle}>
            按钮
          </Button>
        </div>
      );
    }
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4Sg3kmD-1684857369287)(./useState()]返回的数组中的修改值函数setXxx()的累计更新.jpg)

useState()返回的数组中的修改值函数setXxx()的新旧值比对优化

  • 基于setXxx修改状态,函数组件内部做了优化机制:

    • 基于Object.is()方法,对新老状态值做对比,如果发现是一致的,则视图不更新。

      • 类似于设置了优化处理的shouldComponentUpdate()钩子函数。
      import { useState } from "react";
      import { Button } from "antd";
      export default function Demo() {
        console.log("render");
        let [x, setX] = useState(10);
        let [y, setY] = useState(20);
        let [z, setZ] = useState(30);
        const handle = () => {
          console.log("click");
          setX(10);//执行了该函数,但不会更新视图。可以看作内部用了Object.is()做了比对,如果是true,就不更新视图。
        };
        return (
          <div className="demo" style={{ padding: "50px" }}>
            <p>
              x:{x}-y:{y}-z:{z}
            </p>
            <Button type="primary" size="small" onClick={handle}>
              按钮
            </Button>
          </div>
        );
      }
      

Hooks组件useEffect()

在函数组件中使用生命周期函数

  • useEffect()/useLayoutEffect() 在函数组件中使用生命周期函数

    • useEffect(callback)

      • 在第一次渲染完毕和每一次组件更新完毕后,触发callback执行。

        • 等价于componentDidMount()和componentDidUpdate()。

          import { useState, useEffect, useLayoutEffect } from "react";
          import { Button } from "antd";
          export default function Demo() {
            console.log('render');
            let [x, setX] = useState(10);
            let [y, setY] = useState(20);
            let [z, setZ] = useState(30);
            const handle = () => {
              setX(x + 1);
              setY(y + 1);
              setZ(z + 1);
              console.log('click');
            };
          
            useEffect(() => {
              console.log('AA');
            })
            return (
              <div className="demo" style={{ padding: "50px" }}>
                <p>
                  x:{x}-y:{y}-z:{z}
                </p>
                <Button type="primary" size="small" onClick={handle}>
                  按钮
                </Button>
              </div>
            );
          }
          
      • useEffect(callback,[])

        • 只在第一次渲染完毕后触发执行。

          • 等价于componentDidMount()。

            useEffect(() => {
              console.log('componentDidMount');
            }, [])
            
      • useEffect(callback,[依赖的状态])

        • 在第一次渲染完毕后触发执行,以及依赖的状态发生改变后触发执行。

          • 类似于Vue2中watch监听器。

            // 类似于监听器,监听了x与y。
            useEffect(() => {
              console.log('@3 第一次渲染完毕 与 x或y状态改变');
            }, [x, y])
            
      • useEffect(返回一个函数的callback,[])组件销毁之前做些事情。

        • useEffect()传递的callback函数,其内部可以不写返回值

          • 如果设置返回值,则必须返回一个函数
          useEffect(() => {
            return () => {
              console.log('@4 组件销毁之前');
            }
          }, [])
          
            useEffect(() => {
              //第一次渲染完毕做的事情;
              return () => {
                //组件销毁之前做的事情;
              }
            }, [])
          
      • 不写数组或者设置依赖项:

        • useEffect(返回一个函数A的callback) 但凡有状态更新,会先把上一次闭包中callback返回的函数A执行,之后才执行本次闭包中的返回一个函数A的callback

        • useEffect(返回一个函数A的callback,[依赖的状态]) 依赖的状态发生改变,会先把上一次闭包中callback返回的函数A执行,之后才执行本次闭包中的返回一个函数A的callback

            useEffect(() => {
              return ()=>{
                //但凡有状态更新,或者依赖的状态发生改变,在组件更新之前,会先把此函数执行。-->通俗理解:产生新的闭包之前,处理的事情。
              }
            },不写数组或者设置依赖项)
          

useEffect()内部回调处理机制

  • 每一次函数组件更新/渲染,除了产生一个新的闭包外,还会生成一个新的effect链表!

    1. effect链表:存储基于useEffect创建的callback(),或者是callback返回的函数,以及依赖的状态。

      • 组件渲染中:

        • 每次遇到useEffect(callback,依赖项/空数组/没有),基于MountEffect方法,把依赖项和callback放在effect链表中。
      • 组件渲染完毕:会基于UpdateEffect方法通知effect链表中的callback,按照各自的特征,去逐一执行!

    2. callback执行的时候,其宿主上下文,就是本次视图渲染产生的闭包!

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IQTvnLlp-1684857369288)(./useEffect()]内部回调处理机制.jpg)

Hook函数组件说明

  • Hook函数组件的每一次更新和渲染,都是让函数重新执行。

    • 产生新的私有上下文,代码会自上而下执行一遍,最后返回新的VirtualDOM。

      • 如果是第一次渲染:

        • 当前函数组件当前执行时产生了私有上下文1

        • 当前函数组件当前执行时产生的私有上下文1中的this一般是undefined。

          • 因为经过React处理过了。
        • 通过当前函数组件第一个形参得到父组件传递的props

        • 通过执行useState(),得到初始状态值修改该初始状态值的函数

          • 返回的初始状态值第一次传入useState()函数内的值

          • 虽然看起来初始状态值没什么变化,但React内部已经帮你做了很多操作。

            • 比如会返回一个修改状态值的函数,以及记录了当前的作用域、当前所在函数、当前父节点之类的操作。

              • 同时,在调用修改状态值的函数时,还会重新执行一遍当前函数,产生的虚拟DOM也可以在父组件中被更新。
        • 自上而下执行一遍,中间可能会创建一些局部函数,会返回一个虚拟DOM,而父组件会将它缓存下来,并渲染到页面上

          • 这些创建的局部函数中,上级作用域链是当前函数组件当前执行时产生的私有上下文1,也就意味着,如果它内部找不到值,会在当前函数组件当前执行时产生的私有上下文1中查找。
          • 同时父组件还会缓存当前返回出去的虚拟DOM,以便下次更新时进行比较。
        • 该组件中执行的方法和使用的值,基本上都是当前函数组件当前执行时产生的私有上下文1中的值或得到的函数或新创建的函数。

      • 如果是后续更新,重新自上而下执行一遍,但Hook组件中提供的Hook函数,可以让返回值变动,进而让函数返回虚拟DOM产生新的变化。

        • 当前函数组件当前执行时产生了私有上下文2

        • 当前函数组件当前执行时产生的私有上下文2中的this一般是undefined。

          • 因为经过React处理过了。
        • 通过第一个形参得到父组件传递的props。

          • 函数组件的每次更新,都会把第一次渲染时获取的属性,在后期每一次执行中,传递进来。
        • 通过执行useState(),得到最新状态值和修改该最新状态值的函数。

          • 返回的最新状态值是上一次传入函数内的值。

          • 虽然看起来初始状态值没什么变化,但React内部已经帮你做了很多操作。

            • 比如会返回一个修改状态值的函数,以及记录了当前的作用域、当前所在函数、当前父节点、当前props之类的操作,同时,在调用修改状态值的函数时,还会重新执行一遍当前函数,产生的虚拟DOM也可以在父组件中被更新。
        • 自上而下执行一遍,中间可能会创建一些局部函数,会返回一个虚拟DOM,而父组件会将它缓存下来,并渲染到页面上。

          • 这些创建的局部函数中,上级作用域链是当前函数组件当前执行时产生的私有上下文1,也就意味着,如果它内部找不到值,会在当前函数组件当前执行时产生的私有上下文1中查找。
          • 同时父组件还会缓存当前返回出去的虚拟DOM,以便下次更新时进行比较。
        • 该组件中执行的方法和使用的值,基本上都是当前函数组件当前执行时产生的私有上下文1中的值或得到的函数或新创建的函数。

          import { useState, useEffect, useLayoutEffect } from 'react'
          import { Button } from 'antd'
          
          export default function Demo() {
              let [x, setX] = useState(10),
                  [y, setY] = useState(20),
                  [z, setZ] = useState(30)
          
              useEffect(() => {
                  console.log('@1 第一次渲染完毕/更新完毕', x, y, z)
                  return () => {
                      console.log('@5 组件更新之前', x, y, z);
                  }
              })
          
              useEffect(() => {
                  console.log('@2 第一次渲染完毕', x, y, z)
                  setTimeout(() => {
                      console.log('哈哈哈', x, y, z) //获取的永远是闭包1中的状态值 10/20/30
                  }, 5000)
                  return () => {
                      console.log('@4 组件销毁之前', x, y, z);
                  }
              }, [])
          
              useEffect(() => {
                  console.log('@3 第一次渲染完毕 & x/y状态改变', x, y, z)
              }, [x, y])
          
              return <div className="demo" style={{ padding: '50px' }}>
                  <p>{x} - {y} - {z}</p>
                  <Button type='primary' size='small' onClick={() => setX(x + 1)}>修改X</Button>
                  <Button type='primary' size='small' onClick={() => setY(y + 1)}>修改Y</Button>
                  <Button type='primary' size='small' onClick={() => setZ(z + 1)}>修改Z</Button>
              </div>
          }
          
    • Hook组件中关于useState()中的Xxx与setXxx在更新和渲染时内存变动.jpg

import { useState, useEffect, useLayoutEffect } from "react";
import { Button } from "antd";
export default function Demo() {
  console.log('render');
  let [x, setX] = useState(10);
  let [y, setY] = useState(20);
  let [z, setZ] = useState(30);

  // useEffect(() => {
  //   console.log('@1 第一次渲染完毕 或 更新完毕');
  // })


  // useEffect(() => {
  //   return ()=>{
  //     console.log('@5 组件更新之前');
  //   }
  // })


  useEffect(() => {
    console.log('@1 第一次渲染完毕 或 更新完毕', x, y, z);
    return () => {
      console.log('@5 组件更新之前', x, y, z);
    }
  })


  // useEffect(() => {
  //   console.log('@2 第一次渲染完毕');
  // }, [])

  // useEffect(() => {
  //   return () => {
  //     console.log('@4 组件销毁之前');
  //   }
  // }, [])

  // 类似于监听器,监听了x与y。
  useEffect(() => {
    console.log('@3 第一次渲染完毕 与 x或y状态改变', x, y, z);
  }, [x, y])



  useEffect(() => {
    console.log('@2 第一次渲染完毕', x, y, z);

    setTimeout(() => {
      console.log(`延时`, x, z, y);//因为该useEffect只在第一次执行,所以依旧是第一次的值。x-10,y-20,z-30;
    }, 5000)
    return () => {
      console.log('@4 组件销毁之前', x, y, z);
    }
  }, [])
  return (
    <div className="demo" style={{ padding: "50px" }}>
      <p>
        x:{x}-y:{y}-z:{z}
      </p>
      <Button type="primary" size="small" onClick={() => setX(x + 1)}>
        修改x
      </Button>
      <Button type="primary" size="small" onClick={() => setY(y + 1)}>
        修改y
      </Button>
      <Button type="primary" size="small" onClick={() => setZ(z + 1)}>
        修改z
      </Button>
    </div>
  );
}

进阶参考