【每日一题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.memo
和 React.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
是拿来保持一个对象引用不变的。useMemo
和 useCallback
都是 React 提供来做性能优化的。比起 class
,Hooks
给开发者更高的灵活度和自由,但是对开发者要求也更高了,因为 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
你也可以点击文末的 “阅读原文” 快速跳转