Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day383:说下 React 的 useEffect、useCallback、useMemo #1218

Open
Genzhen opened this issue Sep 8, 2021 · 2 comments
Open

Day383:说下 React 的 useEffect、useCallback、useMemo #1218

Genzhen opened this issue Sep 8, 2021 · 2 comments
Labels
React teach_tag

Comments

@Genzhen
Copy link
Collaborator

Genzhen commented Sep 8, 2021

每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案
欢迎大家在下方发表自己的优质见解

二维码加载失败可点击 小程序二维码

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁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>;
};

效果如下:

img

如果换成 useLayoutEffect,得到的效果是:

img

这个例子可以很明显看出 useEffect 和 useLayoutEffect 之间的区别,useEffect 是在浏览器重绘之后才异步执行的,所以点击按钮上的数字会先变成 0,再变成一个随机数;而 useLayoutEffect 是在浏览器重绘之前同步执行的,所以两次 setCount 合并到 300 毫秒后的重绘里了。

img

因为 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.memoReact.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 函数内容。

@Genzhen Genzhen added the React teach_tag label Sep 8, 2021
@Wangjian1211
Copy link

你好,扫描不了二维码

@Ming0053
Copy link

componentWillReceiveProps 这个react的生命周期现在在函数式组件中 哪些地方将会用到

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
React teach_tag
Projects
None yet
Development

No branches or pull requests

3 participants