关于memo、useMemo、useCallback

memo参考文档

 memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。

参数 :memo(Component, arePropsEqual)

  • Component:要进行记忆化的组件。memo 不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。

  • 可选参数 arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回 true。否则返回 false。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。

返回值 

memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化。

用法 :

如下,在App组件中定义的2个state,但是只有count传递给了DemoChild,如果DemoChild不用memo包裹,那么count和count1改变时,DemoChild都会重新渲染。

import React from 'react'
import DemoChild from './page/DemoChild';
const App = () => {
  const [count, setCount] = React.useState(0)
  const [count1, setCount1] = React.useState(0)
  return (
    <>
      <button onClick={() => setCount(count + 1)}>count:{count}</button>
      <button onClick={() => setCount1(count1 + 2)}>count:{count1}</button>
      <DemoChild count={count} />
    </>
  );
};
export default App
import React from 'react'
const DemoChild = ({ count }) => {
    console.log("Greeting was rendered at", new Date().toLocaleTimeString());
    return (
        <>
            Child: {count}
        </>
    )
}
export default DemoChild

 如下用memo包裹后,只有count改变时,DemoChild会重新渲染。

import React, { memo } from 'react'
const DemoChild = ({ count }) => {
    console.log("Greeting was rendered at", new Date().toLocaleTimeString());
    return (
        <>
            Child: {count}
        </>
    )
}
export default memo(DemoChild)

useMemo参考文档

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果

在什么情况下是有用的?

        默认情况下,React 会在每次重新渲染时重新运行整个组件。如果计算速度很快,这将不会产生问题。但是,当正在过滤转换一个大型数组,或者进行一些昂贵的计算,而数据没有改变,那么使用useMemo就可以跳过这些重复计算可以大大提高性能。

用法 :

先定一个一个filterTodos方法:

  export function filterTodos(todos, tab) {
    console.log( todos.length + ' todos for "' + tab + '" tab.');
    let startTime = performance.now();
    while (performance.now() - startTime < 500) {
      // 在 500 毫秒内不执行任何操作以模拟极慢的代码
      // console.log("500ms还没执行, 太慢啦");
    }

用useMemo包裹filterTodos,当我们切换isDark时,会跳过重复计算,背景色切换速度很快。反之,则会始终重新计算 ,这是因为 此版本没有调用 useMemo,因此每次重新渲染都会调用人为减速的 filterTodos

import React,{useMemo} from 'react';
import { filterTodos } from '../utils'

const TodoList = ({ todos, tab }) => {
  const [isDark, setIsDark] = React.useState(false);

    const visibleTodos = useMemo(
        () => filterTodos(todos, tab),
        [todos, tab]
    );
    // const visibleTodos = filterTodos(todos, tab);
    return (
        <div style={{ background: isDark  ? 'green' : 'pink' }}>
        <label>
                <input
                type="checkbox"
                checked={isDark}
                onChange={e => setIsDark(e.target.checked)}
                />
            Dark mode
        </label>
            <ul>
                {visibleTodos.map(todo => (
                    <li key={todo.id}>
                        {todo.completed ?
                            <s>{todo.text}</s> :
                            todo.text
                        }
                    </li>
                ))}
            </ul>
        </div >
    );
}
export default TodoList

useCallback参考文档

用法:

如下:父组件定义主题state,传递给<ProductPage/>,点击checkbox时,可以切换主题。

import React from 'react'
import ProductPage from './page/ProductPage';

const App = () => {
  const [isDark, setIsDark] = React.useState(false);

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Dark mode
      </label>
     
      <ProductPage
        referrerId="wizard_of_oz"
        productId={123}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
};
export default App

 如下时ProductPage组件具体函数,

import React from 'react'
import ShippingForm from './ShippingForm';

export default function ProductPage({ productId, referrer, theme }) {
      function handleSubmit(orderDetails) {
        post('/product/' + productId + '/buy', {
          referrer,
          orderDetails,
        });
      }
    // const handleSubmit = React.useCallback((orderDetails) => {
    //     post('/product/' + productId + '/buy', {
    //         referrer,
    //         orderDetails,
    //     });
    // }, [productId, referrer]);

    return (
        <div style={{ background: theme === "dark" ? 'green' : 'pink' }}>
            <ShippingForm onSubmit={handleSubmit} />
        </div>
    );
}

function post(url, data) {
    //想象这发送了一个请求
    console.log('POST /' + url);
    console.log(data);
}

如下时ShippingForm组件的详细代码:

import { memo, useState } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  const [count, setCount] = useState(1);

  console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />');
  let startTime = performance.now();
  while (performance.now() - startTime < 500) {
    // 500 毫秒内不执行任何操作来模拟极慢的代码
  }

  function handleSubmit(e) {
    e.preventDefault();
    const formData = new FormData(e.target);
    const orderDetails = {
      ...Object.fromEntries(formData),
      count
    };
    onSubmit(orderDetails);
  }

  return (
    <form onSubmit={handleSubmit}>
      <p><b>Note: <code>ShippingForm</code> is artificially slowed down!</b></p>
      <label>
        Number of items:
        <button type="button" onClick={() => setCount(count - 1)}>–</button>
        {count}
        <button type="button" onClick={() => setCount(count + 1)}>+</button>
      </label>
      <label>
        Street:
        <input name="street" />
      </label>
      <label>
        City:
        <input name="city" />
      </label>
      <label>
        Postal code:
        <input name="zipCode" />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
});

export default ShippingForm;

由此例看出,尝试更改主题。将 useCallback 和memo结合使用后,由于 useCallback 依赖 productIdreferrer 自上次渲染后始终没有发生改变,因此 handleSubmit 也没有改变。由于 handleSubmit 没有发生改变,ShippingForm 就跳过了重新渲染。

如果不是有useCallback,切换主题会很慢!因为 handleSubmit 总是一个新的函数,并且被减速的 ShippingForm 组件不能跳过重新渲染。

为了解决频繁触发 Effect ,可以在 Effect 中将要调用的函数包裹在 useCallback 中:

const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); 

useEffect(() => {
    createOptions();
  }, [createOptions]);

如果你正在编写一个 自定义hooks,建议将它返回的任何函数包裹在 useCallback 中!

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值