Day383:说下 React 的 useEffect、useCallback、useMemo
Genzhen opened this issue · 2 comments
每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案
欢迎大家在下方发表自己的优质见解二维码加载失败可点击 小程序二维码
扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。
一、定义
useEffect(didUpdate, deps);
const memoizedCallback = useCallback(() => {
doSomething(params);
}, deps);
const memoizedValue = useMemo(() => computerExpensiveValue(params), deps);
- deps 是依赖的参数列表,当依赖列表中的任一参数变化时,则重新执行前面的函数。
1.1 useEffect
useEffect 一般用于处理状态更新导致的 side effects。虽然说不提倡面向生命周期函数编程,但是在没有熟练掌握 useEffect 的时候,类比 Class Component 的生命周期函数最能帮助我们快速上手。
useEffect 可以看做是 componentDidMount/componentDidUpdate/componentWillUnmount
这三个生命周期函数的替代。
看下官网提供的例子,可以非常全面的展示 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";
}
1.2 useLayoutEffect
useEffect 是官方推荐拿来代替 componentDidMount/componentDidUpdate/componentWillUnmount
这三个生命周期函数的,但是它们并不是完全等价的,useEffect 是在浏览器渲染结束之后才执行的,而这三个生命周期函数是在浏览器渲染之前同步执行的,React 还有一个官方的 hook 是完美等价于这三个生命周期函数的,叫 useLayoutEffect。
useEffect 和 useLayoutEffect 的区别来看一个例子:
const App = () => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
// 耗时 300 毫秒的计算
const start = +new Date();
while (+new Date() - start <= 300) {
continue;
}
if (count === 0) {
setCount(Math.random());
}
}, [count]);
const handleClick = React.useCallback(() => setCount(0), []);
return <button onClick={handleClick}>{count}</button>;
};
效果如下:
如果换成 useLayoutEffect,得到的效果是:
这个例子可以很明显看出 useEffect 和 useLayoutEffect 之间的区别,useEffect 是在浏览器重绘之后才异步执行的,所以点击按钮上的数字会先变成 0,再变成一个随机数;而 useLayoutEffect 是在浏览器重绘之前同步执行的,所以两次 setCount 合并到 300 毫秒后的重绘里了。
因为 useEffect 不会阻塞浏览器重绘,而且平时业务中我们遇到的绝大多数场景都是时机不敏感的,比如取数、修改 dom、事件触发/监听...,所以首推使用 useEffect 来处理 side effects,性能上表现的更好一些。
didUpdate 是一个包含命令式,且可能有副作用代码的函数
didUpdate 是组件渲染成功且 deps 依赖参数发生变化时执行的函数。
didUpdate 可以没有返回值,只是执行 didUpdate 的内容。
但是当 didUpdate 有返回值的时候,返回值必须是一个可执行的函数,目的是用于清除 didUpdate 执行过程中产生的订阅或者计时器等资源。同时如果 didUpdate 多次触发,则在每次重新执行前都会先执行返回的可执行函数。(官方称之为清除 Effect)
useEffect(() => {
const timer = setInterval(() => {
console.log("effect");
}, 1000);
return () => {
// 清除定时器
clearInterval(timer);
};
});
1.2 useCallback
有人可能误认为 useCallback 可以用来解决创建函数造成的性能问题,其实恰恰相反。单个组件来看,useCallback 只会更慢,因为 inline 函数是无论如何都会创建的,还增加了 useCallback 内部对 inputs 变化的检测。
function A() {
const cb = () => {}; /* 创建了 */
}
function B() {
const cb = React.useCallback(() => {} /* 还是创建了 */, [a, b]);
}
useCallback 的真正目的是在于缓存每次渲染时 inline callback 的实例,这样方便配合上子组件的 shouldComponentUpdate 或者 React.memo
起到减少不必要的渲染的作用。需要注意的是 React.memo
和 React.useCallback
一定要配对使用。缺了一个可能导致性能不升反降。毕竟无意义的浅比较也是消耗那么一点点的性能。
返回一个 memoized 的回调函数,即返回一个函数的句柄,等同于函数的变量,因此 useCallback 的作用在于利用 memoize 减少无效的 re-render,来达到性能优化的作用。
1.3 useMemo
useMemo 是拿来保持一个对象引用不变的。useMemo 和 useCallback 都是 React 提供来做性能优化的。比起 classes,Hooks 给了开发者更高的灵活度和自由,但是对开发者要求也更高了,因为 Hooks 使用不恰当很容易导致性能问题。
返回一个 memoized 值,useMemo 函数每当 deps 发生变化时都会调用 computeExpensiveValue 的内容,这是与 useCallback 最大的不同,useCallback 不执行 doSomething 的内容,只是重新刷新函数句柄。
句柄
官方上有这样一个等式:useCallback(fn,deps)
相当于useMemo(()=>fn,deps)
。就是 deps 发生变化时,useCallback 返回的是一个可执行的 fn 的句柄,而 useMemo 则是执行()=>fn
,但是因为返回的是 fn 函数,因此当调用这种时,其实执行的是相同的 fn 函数内容。
你好,扫描不了二维码
componentWillReceiveProps 这个react的生命周期现在在函数式组件中 哪些地方将会用到