【每日一题NO.66】useEffect、useCallback、useMemo理解及仿写

前端印记

共 4726字,需浏览 10分钟

 ·

2021-10-25 04:57

useEffect

useEffect 一般用于处理状态更新导致的副作用

useEffect 可以看做是 componentDidMount/componentDidUpdate/componentWillUnmount 这三个生命周期函数的替代。

语法:

useEffect(didUpdate, deps);

didUpdate

是一个包含命令式并且可能会有副作用代码的函数,是组件渲染成功并且 deps 依赖参数发生变化时执行的函数。该函数可以没有返回值,只是执行内部的内容。

但是当 didUpdate 有返回值的时候,返回值必须是一个可执行的函数,目的是用于清除 didUpdate 执行过程中产生的订阅或者计数器ID等资源。

如果 didUpdate 多次触发,则在每次重新执行前都会先行返回的可执行函数,官方称之为清除effect

以下是官网提供的例子,可以全面展示 useEffect 的使用方式:

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

// 该组件定时从服务器获取好友的在线状态
function FriendStatus(props{
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status{
      setIsOnline(status.isOnline);
    }
    // 在浏览器渲染结束后执行
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    // 在每次渲染产生的 effect 执行之前执行
    return function cleanup({
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };

    // 只有 props.friend.id 这个依赖更新了才会重新执行这个 hook
  }, [props.friend.id]);

  if (isOnline === null) {
    //继续等待
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

仿写useEffect

let hookStates = []; // 保存状态的数组 [0,0]
let hookIndex = 0// 索引
function useEffect(callback,dependencies){
  if(hookStates[hookIndex]){
    // 说明不是第一次执行
    let lastDependencies = hookStates[hookIndex];
    let same = dependencies.every((item,index) => item === lastDependencies[index]);
    if(same){
      hookIndex++;
    }else{
      hookStates[hookIndex++] = dependencies;
      callback();
    }
  }else{
    // 说明是第一次渲染
    hookStates[hookIndex++] = dependencies; // 进行依赖缓存
    callback();
  }
}

useLayoutEffect

useEffect 是官方推荐拿来代替componentDidMount/componentDidUpdate/componentWillUnmount这三个生命周期函数的,但是它们并不是完全等价的:

  • useEffect 是在浏览器渲染结束之后才执行的,
  • 这三个生命周期函数是在浏览器渲染之前同步执行的。

而能够完美等价这三个生命周期函数的另一个官方hook就是 useLayoutEffect

useEffect 是在浏览器重绘之后才异步执行的,而 useLayoutEffect 是在浏览器重绘之前同步执行的。

因为 useEffect 不会阻塞浏览器重绘,而且平时业务中遇到的绝大多数场景都是时机不敏感的,比如取数、修改 dom、事件触发/监听…,所以首先推荐使用 useEffect 来处理 副作用,性能上表现的更好一些。

useEffect(() => {
  const timer = setInterval(() => {
    console.log("timer");
  }, 1000);
  return () => {
    // 清除定时器
    clearInterval(timer);
  };
});

useCallback

语法:

const memoizedCallback = useCallback(() => {
  doSomething(params);
}, deps);

deps 是依赖的参数列表,当依赖列表中的任一参数变化时,则重新执行前面的函数

返回一个 memoize回调函数,即返回一个函数的句柄,等同于函数的变量,因此 useCallback 的作用在于利用 memoize 减少无效的 re-render 来达到性能优化的作用。

有人会误以为 useCallback 可以用来解决创建函数造成的性能问题,其实恰恰相反。单个组件来看,useCallback 只会更慢,因为 inline 函数是无论如何都会创建的,还增加了 useCallback 内部对 inputs 变化的检测。

useCallback 的真正目的是在于缓存每次渲染时 inline callback 的实例,这样方便配合上子组件的 shouldComponentUpdate 或者 React.memo 起到减少不必要渲染的作用,需要注意的是 React.memoReact.useCallback 一定要配对使用。缺了一个可能导致性能不升反降。毕竟无意义的浅比较也是消耗那么一点点性能。

仿写

let hookStates = []; // 保存状态的数组 [0,0]
let hookIndex = 0// 索引
function useCallback(callback, dependencies{
  if (hookStates[hookIndex]) { // 说明不是第一次,
    let [lastCallback, lastDependencies] = hookStates[hookIndex];
    // 判断一下新的依赖数组中的每一项是否跟上次完全相等
    let same = dependencies.every((item, index) => item === lastDependencies[index]);
    if (same) {
      hookIndex++;
      return lastCallback;
    } else { // 只要有一个依赖变量不一样的话
      hookStates[hookIndex++] = [callback, dependencies]; // hookIndex=3 callback=()=>setNumber(number+1)  dependencies=[0]
      return callback;
    }
  } else { // 说明是第一次渲染
    hookStates[hookIndex++] = [callback, dependencies]; // hookIndex=3 callback=()=>setNumber(number+1)  dependencies=[0]
    return callback;
  }
}

useMemo

语法:

const memoizedValue = useMemo(() => computerExpensiveValue(params), deps);

返回一个 memoize useMemo 函数每当 deps 发生变化时都会调用 componentExpensiveValue 的内容,这是和 useCallback 最大的不同,useCallback 不执行 dosomething 的内容,只是重新刷新函数句柄。

官方文档上有这样的一个等式:

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

deps 发生变化时,useCallback 返回的值是一个可执行的 fn 的句柄,而 useMemo 则是执行()=>fn

useMemo 是拿来保持一个对象引用不变的。useMemouseCallback 都是 React 提供来做性能优化的。比起 classHooks 给开发者更高的灵活度和自由,但是对开发者要求也更高了,因为 Hooks 使用不当很容易导致性能问题。

仿写

let hookStates = []; // 保存状态的数组 [0,0]
let hookIndex = 0// 索引
function useMemo(factory, dependencies{
  if (hookStates[hookIndex]) { // 说明不是第一次,
    let [lastMemo, lastDependencies] = hookStates[hookIndex];
    // 判断一下新的依赖数组中的每一项是否跟上次完全相等
    let same = dependencies.every((item, index) => item === lastDependencies[index]);
    if (same) {
      hookIndex++;
      return lastMemo;
    } else { // 只要有一个依赖变量不一样的话
      let newMemo = factory();
      hookStates[hookIndex++] = [newMemo, dependencies];
      return newMemo;
    }
  } else { // 说明是第一次渲染
    let newMemo = factory();
    hookStates[hookIndex++] = [newMemo, dependencies];
    return newMemo;
  }
}

所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转


END
愿你历尽千帆,归来仍是少年。


浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报