React 性能优化

useCallback

场景一: 避免子组件的重复渲染

当我们需要从子组件传递消息时,通常使用的是由父组件传递方法给子组件,子组件调用方法时将消息当做参数传递给这个方法。 但在有些情况我们想在重新渲染父组件时并不需要重新渲染这个子组件(子组件渲染很耗时),就可以用到useCallback+memo的组合了,(memo是一个包裹组件的的api,在组件接收的prop不变时,父组件重新渲染不会触发该组件的重新渲染)

import { useCallback } from 'react';

function ProductPage({ productId, referrer, theme }) {
  // 将handleSubmit方法用useCallback缓存起来,传递给子组件时该方法就不会被重新初始化,
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}
import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

场景二: 当方法需要在useEffect中调用时

因为需要添加到dependency list中,如果每次都重新初始化的话会导致useEffect每次都重新调用,因此可以将方法用useCallback包裹起来

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

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

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ Only changes when createOptions changes
  // ...

场景三: 优化自定义hooks

React建议在自定义的钩子中,所有返回的函数都用useCallback包裹

function useRouter() {
  const { dispatch } = useContext(RouterStateContext);

  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);

  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);

  return {
    navigate,
    goBack,
  };
}

这能确保在使用这个自定义hook的时候,可以做一些性能上的优化,参照场景一二

useDeferredValue vs Suspense

useDeferredValue是一个推迟渲染的钩子,他是使React先用useDeferredValue传入的更新之前的值先渲染一次,如果值有更新的话再用更新后的值再渲染一次。通常和useState共同使用

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

首次渲染时,deferred value就是传入的默认值
更新渲染时,react会先不更新deferred value用旧的值先渲染一次,再用更新后的值再渲染一次。注意这个再次更新是可打断的,如果再次更新deferred value,react会打断这个渲染,而使用新的数据重新渲染,用户看到的始终都是原始的数据渲染的结果

另外再使用useDeferredValue,做性能优化时,需要和memo配合使用。 memo的作用是传入的prop没变化则不更新组件。


Posted

in

by

Tags: