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

发布时间 2023-12-24 00:02:52作者: Lemo_wd

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)。有时会丢弃缓存,比如组件在初始化挂载期间。

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,
  };
}