React Hooks ——useEffect及其底层链表机制

useEffect

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

语法:具备多种情况

useEffect([callback], [dependencies])

用法

useEffect:在函数组件中,使用生命周期函数
  useEffect(callback):没设置依赖
    + 第一次渲染完毕后,执行callback,等价于 componentDidMount
    + 在组件每一次更新完毕后,也会执行callback,等价于 componentDidUpdate

  useEffect(callback,[]):设置了,但是无依赖
    + 只有第一次渲染完毕后,才会执行callback,每一次视图更新完毕后,callback不再执行
    + 类似于 componentDidMount

  useEffect(callback,[依赖的状态(多个状态)])+ 第一次渲染完毕会执行callback
    + 当依赖的状态值(或者多个依赖状态中的一个)发生改变,也会触发callback执行
    + 但是依赖的状态如果没有变化,在组件更新的时候,callback是不会执行的

  useEffect(()=>{
    return ()=>{
      // 返回的小函数,会在组件释放的时候执行
      // 相当于componentWillUnmount
      // 如果组件更新,会把上一次返回的小函数执行「可以“理解为”上一次渲染的组件释放了」
    };
  });
  
  /* 
  返回的函数将在 组件卸载后 被调用
  等同于 componentWillUnmount
  */
    useEffect(() => {
      return () => {
          console.log('@4');
      };
    }, []);

    /* 
    以及若干种排列组合
    初次渲染和即将卸载时调用
    等同于 componentWillUnmount
    */
    useEffect(() => {
      console.log('@5')
      return () => {
          console.log('@6');
      };
    }, []);

底层原理——effect链表

  • 视图渲染开始时,遇到useEffect函数,会调用MountEffect方法将callback/依赖项加入到链表
  • 视图渲染完毕时,基于UpdateEffect函数,监听时机执行链表中的effect callback
  • 重新render时,首先销毁上一次的视图,因此会在上一次释放时首先调用componentWillUnmount时机的callback,即返回内容
  • useEffect执行部分获取的是最新的状态值;(挂载时)返回的callback中获取的是卸载前的状态值

函数组件在渲染(或更新)期间,遇到useEffect操作,会基于MountEffect方法把callback(和依赖项)加入到effect链表中!

在视图渲染完毕后,基于UpdateEffect方法,通知链表中的方法执行:

1、按照顺序执行期间,首先会检测依赖项的值是否有更新「有容器专门记录上一次依赖项的值」;有更新则把对应的callback执行,没有则继续处理下一项

2、遇到依赖项是空数组的,则只在第一次渲染完毕时,执行相应的callback

3、遇到没有设置依赖项的,则每一次渲染完毕时都执行相应的callback

在这里插入图片描述

import React from 'react';
import { useEffect, useState } from 'react';
import { Button } from 'antd';
export default function UseEffectDemo() {
  let [num, setNum] = useState(0);
  /* 
    在组件 第一次渲染完 && 每一次更新完 调用 
    等同于 componentDidMount && componentDidUpdate
  */
  useEffect(() => {
    // 获取最新的状态值
    console.log('@1', num);
  });
  /* 
    只在组件 第一次渲染完 调用 
    等同于 componentDidMount
  */
  useEffect(() => {
    console.log('@2', num);
  }, []);
  /* 
    第一次渲染完 以及 依赖的状态发生改变 时调用
  */
  useEffect(() => {
    console.log('@3', num);
  }, [num]);
  /* 
    返回的函数将在 组件卸载后 被调用
    等同于 componentWillUnmount
  */
  useEffect(() => {
    return () => {
        // 获取的是上一次的状态值
        console.log('@4', num);
    };
  });
  /* 
    以及若干种排列组合
    初次渲染和即将卸载时调用
    等同于 componentWillUnmount
  */
  useEffect(() => {
    console.log('@5')
    return () => {
        console.log('@6');
    };
  }, []);
  const handle = () => {
    setNum(num + 1);
  };
  return <div className="demo">
    <span className="num">{num}</span>
    <Button type="primary"
        size="small"
        onClick={handle}>
        新增
    </Button>
  </div>
}

只在函数最外层使用

只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用。

import React, { useState, useEffect } from "react";
export default function Demo() {
    let [num, setNum] = useState(10);
    if (num >= 10) {
        // Error:React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render  react-hooks/rules-of-hooks
        useEffect(() => {
            console.log('@1', num);
        });
    }
    // 应该这样写
    useEffect(() => {
       if(num >= 10) console.log('@1', num);
    });    
    return <div>
        <span>{num}</span>
        <button onClick={() => { setNum(num + 1); }}>处理</button>
    </div>;
};

异步获取数据

问题:useEffect的callback不能用async修饰

不能直接对[callback]设置async,因为useEffect的callback只能返回一个函数(或者不设置返回值),而用async修饰函数之后,返回值总是Promise对象

无论async函数有无await操作,其总是返回一个Promise。

  1. 没有显式return,相当于return Promise.resolve(undefined);
  2. return非Promise的数据data,相当于return Promise.resolve(data);
  3. return Promise, 会得到Promise对象本身
import React, { useState, useEffect } from "react";
const queryData = () => {
    return fetch('/api/subscriptions/recommended_collections')
        .then(response => {
            return response.json();
        });
};
export default function Demo() {
    let [data, setData] = useState([]);
    /* // Warning: useEffect must not return anything besides a function, which is used for clean-up.
    useEffect(async () => {
        let result = await queryData();
        setData(result);
        console.log(result);
    }, []); */
    useEffect(() => {
        const next = async () => {
            let result = await queryData();
            setData(result);
            console.log(result);
        };
        next();
    }, []);
    return <div>
        ...
    </div>;
};
解决方案一:不用async和await,使用Promise.then
useEffect(() => {
  queryData()
  .then(data => console.log(data))
}, [])
解决方案二:不用async修饰useEffect的callback,而是更深一层的函数,保证不会默认有Promise返回值
useEffect(() => {
  const next = async () => {
    let data = await queryData();
    console.log(data);
  };
  next()
}, [])

注意事项

import React, { useState, useEffect } from "react";
export default function Demo() {
    let [num, setNum] = useState(0);
    useEffect(() => {
        const timer = setTimeout(() => {
            // ...
            console.log(num); //不管如何更新都是0
        }, 2000);
        return () => {
            clearTimeout(timer);
        };
    }, []);
    return <div>
        <span>{num}</span>
        <button onClick={() => { setNum(num + 1); }}>处理</button>
    </div>;
};
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值