React 基础 —— 各种 hooks 的使用场景

hooks

1. useRef

ref 属于组件实例的共享变量(相当于class 组件中的 this.xxx)。直接修改 ref.current 不会触发组件的重渲染。

Caveats

① 常用于事件处理函数中共享与读写 ref

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

② 不应在渲染期间读写 ref.current,除非渲染时直接初始化,且遵循下面的写法:

避免直接使用

function Video() {
  const playerRef = useRef(new VideoPlayer());
  // ...

而应使用

function Video() {
  const playerRef = useRef(null);
  if (playerRef.current === null) {
    playerRef.current = new VideoPlayer();
  }
  // ...

2. useEffect

产生副作用(与外部系统通信)。

Caveats

浏览器通常在运行 effect 之前更新画面,但由于 useEffect 是异步的,可能会导致页面闪烁(页面更新多次),此时可以使用 useLayoutEffect,因为它是同步的。

3. useMemo

重新渲染时,缓存计算结果。又称记忆化(Memoization)。有时会丢弃缓存,比如组件在初始化挂载期间。

import { useMemo, useState } from 'react';

function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState('');

  // 由于缓存了,除了[todos, filter] 两个属性发生变化之外(如 newTodo 发生变化),不会触发重新计算。
  const visibleTodos = useMemo(() => {
    return getFilteredTodos(todos, filter);
  }, [todos, filter]);
}

3.1 React.memo

React.memo 是一个高阶组件,用于优化函数组件的性能。它的作用是:只有在 props 发生变化时才重新渲染组件,否则会跳过渲染,复用上一次的结果。

适用场景:

  1. 组件纯粹依赖 props 进行渲染
  2. 组件渲染开销较大

不适用场景:

  1. 组件 props 总是在变化。
  2. 组件非常轻量、更新开销很小时,加上 memo 反而增加了浅比较的开销,不值得优化。
  3. 组件使用了大量 useContext,context 变化时会强制更新组件,React.memo 起不到作用。
const Button = React.memo(({ onClick, label }) => {
  console.log('Button render');
  return <button onClick={onClick}>{label}</button>;
});

4. useCallback

重新渲染时,缓存函数定义。

使用场景举例:

① 在使用 React.memo 缓存某个子组件时,若该子组件依赖一个父组件的函数。当父组件重渲染时,该组件的实例变量(函数)也会重新创建,故而子组件将重渲染。此时,可以使用 useCallback 包装这个函数,从而避免子组件跳过重渲染。

import React, { useCallback, useState } from "react"

function App() {
  const [text, updateText] = useState("")

  //由于缓存了 handleClick,从而 MyButton 可以跳过重渲染
  const handleClick = useCallback(() => {
    alert("2233")
  }, [])

  return (
    <>
      <input value={text} onChange={(e) => updateText(e.target.value)} />
      <MyButton onClick={handleClick} />
    </>
  )
}
export default App

const MyButton = React.memo(function MyButton({ onClick }) {
  console.log(233)
  return <button onClick={onClick}>click me</button>
})

② 当 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

此外,对于这种场景可以直接提取真正要依赖的变量,将函数的创建放在 useEffect 内部,故而直接消除父组件函数的重创建带来的影响。

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

  useEffect(() => {
    function createOptions() { // ✅ No need for useCallback or function dependencies!
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }

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

③ 如果自定义 hook 需要返回函数,通常可以使用 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,
  };
}

PS:useCallback 用于缓存函数,useRef 用于缓存变量,useMemo 用于缓存对象(不能主动修改)。

233

posted on 2023-12-23 23:57  Lemo_wd  阅读(79)  评论(0)    收藏  举报

导航