Skip to content

React Hooks 使用笔记 ​

按用途整理 React Hooks:数据更新驱动、生命周期、状态保存、状态获取与传递、工具类,每个 Hook 都包含基础签名、参数说明与使用场景。

一、数据更新驱动 ​

1. useState ​

1. 基础介绍 ​

useState 主要用于声明和操作状态变量,可以使函数组件像类组件一样拥有 state。

tsx
const [state, setState] = useState(initialState);
  • state:状态,作为渲染视图的数据源
  • setState:改变 state 的函数。可以直接传递新状态,也可以传递一个根据先前状态计算新状态的函数(函数式更新)
  • initialState:初始化值。如果是函数,则将函数的返回值作为初始值

2. 直接传递新状态 ​

点击按钮,count 变为 1:

tsx
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(1);
  };

  return (
    <div>
      {count}
      <button onClick={handleClick}>按钮</button>
    </div>
  );
}

3. 函数式更新 ​

根据先前的 state 更新 state,确保每次更新都能获取到最新的 count 值。

将按钮中的 setCount 调用方式改为:

tsx
setCount((prev) => prev + 1);

函数式更新作用示例:

tsx
import { useState } from "react";

const Login = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setTimeout(() => {
      setCount(count + 1);
    }, 1000);
  };
  const handleReset = () => {
    setCount(0);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
};

export default Login;

点击 Increment,然后在 1 秒内点击 Reset,页面显示为 1。再次点击 Increment,然后在 1 秒内点击 Reset,页面先显示 0 再显示 2。这是因为函数组件的更新基于渲染快照机制:第二次点击 Increment 时 count 为 1,被像照片一样记住,setTimeout 回调在 1 秒后把结果 2 渲染到页面上。

可以使用函数式更新避免这一问题,将 setCount(count + 1) 改为 setCount((prev) => prev + 1)。

4. 使用场景:使用 key 重置状态 ​

业务场景:在一系列筛选或输入后面增加重置按钮。

如果是受控组件,可以把值置空;如果是非受控组件,可以使用 key 重置组件的状态。

tsx
import { useState } from "react";

export default function App() {
  const [version, setVersion] = useState(0);

  const handleReset = () => {
    setVersion(version + 1);
  };

  return (
    <>
      <button onClick={handleReset}>重置</button>
      <Form key={version} />
    </>
  );
}

const Form = () => {
  const hanlderSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log(e.target);
  };

  return (
    <>
      <form onSubmit={hanlderSubmit}>
        <input type="text" name="user" />
        <input type="password" name="password" />
        <button type="submit">提交</button>
      </form>
    </>
  );
};

在表单中输入信息,然后点击重置按钮,会发现输入框内容被清空。这是因为当 key 改变时,React 会从头重新创建 Form 组件(以及它的所有子组件)。

5. 注意事项 ​

(1)set 函数是异步的

调用 set 函数后,不能立即获取最新的值:

tsx
const handleClick = () => {
  setCount(count + 1); // setCount(0 + 1)
  console.log(count); // 0
};

(2)获取的是渲染时的值

即使 2s 后打印,读取 count 时值仍为 0,所以打印结果也是 0。可以理解为渲染快照:

tsx
const handleClick = () => {
  setCount(count + 1); // setCount(0 + 1)
  setTimeout(() => {
    console.log(count); // 还是 0!
  }, 2000);
};

如果要获取最新的值,可以使用 useRef:

tsx
import { useRef } from "react";

export default function App() {
  const countRef = useRef(0);

  const handleClick = () => {
    countRef.current += 1;
    setTimeout(() => {
      console.log(countRef.current); // 1
    }, 2000);
  };

  return (
    <>
      <button onClick={handleClick}>按钮</button>
    </>
  );
}

(3)新值与当前 state 相同时会跳过渲染

如果新值与当前 state 相同(由 Object.is 比较确定),将跳过重新渲染。点击按钮,因为对象 info 的引用地址还是指向同一个,因此不会再重新渲染:

tsx
import { useState } from "react";

export default function App() {
  const [info, setInfo] = useState({
    name: "张三",
    age: 20,
  });

  const handleClick = () => {
    setInfo(Object.assign(info, { age: info.age + 1 }));
  };

  console.log("渲染");

  return (
    <>
      {info.name}--{info.age}
      <button onClick={handleClick}>按钮</button>
    </>
  );
}

要想触发渲染,需传递一个新对象:

tsx
setInfo(Object.assign({}, info, { age: info.age + 1 }));
// 或使用扩展运算符
setInfo({ ...info, age: info.age + 1 });

(4)setState 自动批量处理

React 18 之前,setState 只在合成事件与钩子函数中自动批量处理,在 promise、setTimeout 或 JS 原生事件中都不会批处理。React 18 中默认所有的更新都将自动批量处理:

tsx
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setTimeout(() => {
      setCount(1);
      setCount(2);
      setCount(3);
      setCount(2);
    });
  };

  console.log("渲染");

  return (
    <>
      {count}
      <button onClick={handleClick}>按钮</button>
    </>
  );
}

点击按钮,组件只会更新一次,并且值为最后一次调用 set 传入的值 2。

2. useReducer ​

1. 基础介绍 ​

useReducer 是 React Hooks 提供的、能够在无状态组件中运行类似 Redux 功能的 API。

tsx
const [state, dispatch] = useReducer(reducer, initialState, init?)
  • state:状态
  • dispatch:改变 state 的函数
  • reducer:与 Redux 中的 reducer 相同,一个接收 state 与 action 并返回新 state 的函数
  • initialState:初始值
  • init:将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialState)

2. 使用场景:状态管理 ​

tsx
import { useReducer } from "react";

const initialCount = {
  count: 0,
};

type InitialCount = typeof initialCount;

type ACTIONTYPE =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset"; payload: InitialCount };

/* 对初始值进行处理 */
function init(initialCount: InitialCount) {
  /* 如果传入的 count 初始值小于 0,则置为 0 */
  if (initialCount.count < 0) {
    return { count: 0 };
  } else {
    return initialCount;
  }
}

function reducer(state: InitialCount, action: ACTIONTYPE) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return init(action.payload || initialCount);
    default:
      throw new Error();
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({ type: "reset", payload: initialCount })}
      >
        Reset
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
};

export default App;

useReducer 与 Context 配合使用,可以形成一个小范围的状态管理功能。

3. useSyncExternalStore ​

1. 基础介绍 ​

useSyncExternalStore 可以在外部数据源变化时自动更新视图,一般由第三方状态管理库使用。

ts
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
  • subscribe:订阅 store 的变化
  • getSnapshot:返回 store 当前值
  • getServerSnapshot:用于服务端渲染

2. 使用场景:订阅浏览器 API ​

订阅外部数据源,当外部数据源更新时自动更新视图:

tsx
import { useSyncExternalStore } from "react";

export default function ChatIndicator() {
  // 监听浏览器网络连接状态
  const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine);
  return <h1>{isOnline ? "Online" : "Disconnected"}</h1>;
}

function subscribe(callback: any) {
  window.addEventListener("online", callback);
  window.addEventListener("offline", callback);
  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);
  };
}

4. useTransition ​

useTransition 是一个帮助你在不阻塞 UI 的情况下更新状态的 Hook。

1. 基础介绍 ​

ts
const [isPending, startTransition] = useTransition();
  • isPending:布尔值,表示是否正在等待
  • startTransition:接收一个函数,可以把里面的更新任务变成过渡任务

2. 使用场景:将状态更新标记为低优先级 ​

先执行其他高优先级任务,页面会先显示 list2 的内容,之后再显示 list1 的内容:

tsx
import { useState, useEffect, useTransition } from "react";

const App = () => {
  const [list1, setList1] = useState<null[]>([]);
  const [list2, setList2] = useState<null[]>([]);
  const [isPending, startTransition] = useTransition();

  useEffect(() => {
    startTransition(() => {
      // 将状态更新标记为 transition
      setList1(new Array(10000).fill(null));
    });
  }, []);

  useEffect(() => {
    setList2(new Array(10000).fill(null));
  }, []);

  return (
    <>
      {isPending ? "pending" : "nopending"}
      {list1.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
      -----------------list2
      {list2.map((_, i) => (
        <div key={i}>6666</div>
      ))}
    </>
  );
};

export default App;

注释掉 list1 相关代码的执行堆栈图:可以看到 layout 之后一直在函数调用,页面会堵塞大约 0.3 秒。

list1 未使用过渡更新时的执行堆栈,长任务堵塞约 0.3 秒

注释掉 list2、只看 list1 相关代码的执行堆栈图:可以看到任务被拆分到每一帧不同的 task 中,这样浏览器就有剩余时间执行样式布局和样式绘制,减少掉帧的可能性。

使用 useTransition 后更新被拆分成多个小任务,分散到每一帧

5. useDeferredValue ​

1. 基础介绍 ​

可以延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间。

ts
const deferredValue = useDeferredValue(value, initialValue);
  • value:想延迟的值
  • initialValue:初次渲染的值(React 19)
  • deferredValue:延迟值。只有当前没有紧急更新任务时才会更新为最新值,否则返回旧值

2. useDeferredValue 和 useTransition 的区别 ​

相同点:useDeferredValue 本质上和内部实现与 useTransition 一样,都是把更新标记成过渡任务。

不同点:useTransition 处理一段逻辑,useDeferredValue 是产生一个新的状态。

3. 使用场景:受控输入框与长列表 ​

将 input 更新作为紧急的部分优先处理,长列表更新作为不紧急的部分延迟处理:

tsx
import { useState, useDeferredValue, memo } from "react";

export default function App() {
  const [value, setValue] = useState("");
  const deferredValue = useDeferredValue(value);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  return (
    <div>
      <input value={value} onChange={handleChange} />
      <LongList deferredValue={deferredValue} />
    </div>
  );
}

const LongList = memo(({ deferredValue }: { deferredValue: string }) => {
  return (
    <div className="container">
      <div className="list">
        {Array(10000)
          .fill(null)
          .map((_, i) => (
            <div key={i}>{deferredValue}</div>
          ))}
      </div>
    </div>
  );
});

注意点:

如果直接在父组件中展示 1 万个长列表节点,value 更新会触发组件渲染并处理长列表节点,导致卡顿。将长列表拆分成子组件,把延迟的值传给子组件并用 memo 包裹,这样只有 deferredValue 更新时才会重新处理长列表节点。

6. useOptimistic ​

乐观更新:允许先设定接口返回的预期值显示,如果返回失败则恢复更新前的值。

tsx
import { useOptimistic, useState, useTransition } from "react";

/* 模拟接口 */
const getData = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("收藏成功");
    }, 1000);
  });
};

const App = () => {
  const [isPending, startTransition] = useTransition();

  const [state, setState] = useState({ data: "未收藏" });
  const [optimisticState, addOptimistic] = useOptimistic(
    state,
    (currentState, optimisticValue) => {
      // 合并返回
      return { ...currentState, ...optimisticValue };
    },
  );

  const handleClick = () => {
    startTransition(async () => {
      // 在实际数据提交前,使用乐观更新
      addOptimistic({ data: "收藏成功" });
      try {
        const realData = await getData();
        setState({ data: realData });
      } catch {
        console.error("接口错误");
      }
    });
  };

  return (
    <>
      {optimisticState.data}
      <button onClick={handleClick}>{isPending ? "加载中" : "按钮"}</button>
    </>
  );
};

export default App;

7. useActionState ​

旨在简化表单提交的异步状态管理、减少重复代码,常用于服务端渲染中处理表单提交的异步操作。

tsx
import { useActionState } from "react";

/* 模拟接口 */
const getData = (prevState, queryData) => {
  const name = queryData.get("name");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${name}注册成功`);
    }, 1000);
  });
};

const App = () => {
  const [state, formAction, isPending] = useActionState(getData, "", "/1");
  return (
    <>
      {state}
      <form action={formAction}>
        <input type="text" name="name" />
        <button type="submit" disabled={isPending}>
          注册
        </button>
      </form>
    </>
  );
};

export default App;

二、生命周期 ​

1. useEffect ​

1. 基础介绍 ​

useEffect 实现了 componentDidMount、componentDidUpdate 和 componentWillUnmount 三个 API 的功能。

ts
useEffect(fn, deps?)
  • fn:回调函数,会在初始化或依赖项变化时运行
  • deps:依赖项,是一个数组

2. 实现 componentDidMount ​

第二个参数传空数组,只会在初始化时触发一次:

ts
useEffect(() => {
  // 请求接口数据
}, []);

3. 实现 componentDidUpdate ​

在第二个参数传入依赖状态,当依赖状态改变时会重新渲染:

ts
useEffect(() => {
  // 请求接口数据
}, [props.name]);

内部是浅比较,源码中用 for 循环配合 Object.is 实现。

4. 实现 componentWillUnmount ​

返回一个回调函数,用来清除副作用:

tsx
import { useCallback, useEffect, useState } from "react";

const App = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouse = useCallback((e: MouseEvent) => {
    setPosition({
      x: e.pageX,
      y: e.pageY,
    });
  }, []);

  useEffect(() => {
    window.addEventListener("mousemove", handleMouse);
    return () => {
      // 取消监听
      window.removeEventListener("mousemove", handleMouse);
    };
  }, [handleMouse]);

  return (
    <div>
      x:{position.x} y:{position.y}
    </div>
  );
};

export default App;

2. useLayoutEffect ​

1. 基础介绍 ​

在浏览器 layout 之后、painting 之前执行。回调函数中执行的代码可能会堵塞浏览器绘制,常用来在绘制之前获取 DOM 节点信息、修改 DOM 结构,这样浏览器只需要绘制一次。

ts
useLayoutEffect(setup, deps?)
  • fn:回调函数,会在初始化或依赖项变化时运行
  • deps:依赖项

2. 使用场景:在浏览器绘制之前获取 DOM 节点信息 ​

tsx
import { useState, useRef, useLayoutEffect } from "react";

function App() {
  const ref = useRef<HTMLDivElement>(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current?.getBoundingClientRect() || { height: 0 };
    setTooltipHeight(height);
  }, []);

  return (
    <div ref={ref} style={{ height: 300 }}>
      容器的高:{tooltipHeight}
    </div>
  );
}

export default App;

3. useInsertionEffect ​

1. 基础介绍 ​

useInsertionEffect 是一个专为 CSS-in-JS 库的开发者打造的钩子,在 DOM 更新之前执行(比 useLayoutEffect 早)。

ts
useInsertionEffect(setup, deps?)
  • fn:回调函数,会在初始化或依赖项变化时运行
  • deps:依赖项

2. 使用场景:提前注入 style 标签 ​

tsx
import { useInsertionEffect } from "react";

function App() {
  useInsertionEffect(() => {
    /* 动态创建 style 标签插入到 head 中 */
    const style = document.createElement("style");
    style.innerHTML = `
      .css-in-js {
        color: red;
        font-size: 20px;
      }
    `;
    document.head.appendChild(style);
  }, []);

  return <div className="css-in-js">useInsertionEffect</div>;
}

export default App;

三、状态保存 ​

1. useMemo ​

1. 基础介绍 ​

在每次重新渲染的时候能够缓存计算的结果。

ts
const cachedValue = useMemo(calculateValue, deps);
  • calculateValue:一个函数,函数的返回值作为缓存值
  • deps:一个数组,存放当前 useMemo 的依赖项。依赖项改变时会运行 calculateValue 重新计算
  • cachedValue:返回值,如果 deps 中有依赖项改变,返回重新执行 calculateValue 产生的值,否则取上一次缓存值

2. 使用场景 ​

(1)缓存计算结果

tsx
import { useState, useMemo } from "react";

function App() {
  const [count, setCount] = useState(0);

  const memoizedValue = useMemo(() => {
    // 创建 1000 位数组
    const list = new Array(1000).fill(null).map((_, i) => i);

    // 对数组求和
    const total = list.reduce((res, cur) => (res += cur), 0);

    // 返回计算的结果
    return count + total;

    // 添加依赖项,只有 count 改变时才会重新计算
  }, [count]);

  return (
    <div>
      {memoizedValue}
      <button onClick={() => setCount((prev) => prev + 1)}>按钮</button>
    </div>
  );
}

export default App;

(2)缓存渲染列表

tsx
import { useState, useMemo } from "react";

function App() {
  const [list] = useState(["张三", "李四"]);

  const renderList = useMemo(
    () => (
      <div>
        {list.map((i, v) => (
          <span key={v}>{i}</span>
        ))}
      </div>
    ),
    [list],
  );

  return <div>{renderList}</div>;
}

export default App;

3. React.memo 与 useMemo 的区别 ​

  • React.memo:对外部传入的 props 进行浅比较,避免不必要的重复渲染,相当于 shouldComponentUpdate
  • useMemo:对依赖项做浅比较决定是否重新计算,缓存的是计算结果

2. useCallback ​

1. 基础介绍 ​

缓存函数的引用地址,仅在依赖项改变时才会更新。

ts
const cachedFn = useCallback(fn, deps);
  • fn:想要缓存的函数
  • deps:是否更新 fn 的所有响应式值的一个列表

2. 使用场景:避免子组件重复渲染 ​

默认情况下,当一个组件重新渲染时,React 将递归渲染它的所有子组件。通常对于有 props 的子组件会使用 React.memo 包裹:

tsx
import { useState, memo } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <div>
      {count}
      <MyButton handleClick={handleClick} />
    </div>
  );
};

interface Props {
  handleClick: () => void;
}

const MyButton = memo(({ handleClick }: Props) => {
  console.log("子组件渲染");
  return <button onClick={handleClick}>按钮</button>;
});

export default App;

点击按钮,可以发现即使子组件使用 memo 包裹了,还是更新了,控制台打印出「子组件渲染」。这是因为父组件 App 每次更新时,函数 handleClick 都返回了新的引用地址,对子组件来说每次传入的都是不一样的值,从而触发重渲染。

使用 useCallback 可以缓存函数的引用地址,将 handleClick 改为:

tsx
const handleClick = useCallback(() => {
  setCount((prev) => prev + 1);
}, []);

再点击按钮,会发现子组件不会再重新渲染。

提示:不要滥用 useCallback。如果函数不需要传递给子组件,那么函数本身就不需要使用 useCallback 缓存引用地址。

3. useMemo 与 useCallback 的区别 ​

useMemo 常用来缓存计算结果,useCallback 常用来缓存函数的引用地址。

useMemo 如果返回一个函数,同样能够做到缓存函数的引用地址,与 useCallback 等效。

3. useEffectEvent ​

React 19.2 版本引入了 useEffectEvent 钩子,它允许开发者在不触发副作用重新执行的情况下,始终访问最新的 props 和 state。

传统方式:theme 变化导致重连。

tsx
useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.on("connected", () => showNotification("Connected!", theme));
  connection.connect();
  return () => connection.disconnect();
}, [roomId, theme]);

使用 useEffectEvent,实现只依赖 roomId(变化时才重连),但能获取最新 theme:

tsx
const onConnected = useEffectEvent(() => {
  showNotification("Connected!", theme); // theme 取最新值
});

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.on("connected", onConnected);
  connection.connect();
  return () => connection.disconnect();
}, [roomId]); // 仅依赖 roomId

注意:useEffectEvent 应该只在 Effect 中调用,不要将它传递给其他组件或 hooks。

四、状态获取与传递 ​

1. useContext ​

1. 基础介绍 ​

向上查找最近的使用 Context Provider 提供的 value 值。

ts
const value = useContext(SomeContext);
  • SomeContext:由 React.createContext 创建的 context
  • value:获取 Provider 提供的 value 值

2. 使用场景:向组件树深层传递数据 ​

tsx
import {
  useState,
  useCallback,
  createContext,
  useMemo,
  useContext,
} from "react";

const defaultValue = { count: 0, handleClick: () => {} };

/* 1. 创建 Context */
const MyContext = createContext(defaultValue);

const App = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  // 传递值和改变该值的方法给子组件
  const contextValue = useMemo(
    () => ({
      count,
      handleClick,
    }),
    [count, handleClick],
  );

  return (
    /* 2. 提供 Context 值 */
    <MyContext.Provider value={contextValue}>
      <MyButton />
    </MyContext.Provider>
  );
};

const MyButton = () => {
  /* 3. 获取 Context 值 */
  const { count, handleClick } = useContext(MyContext);
  return (
    <div>
      {count}
      <button onClick={handleClick}>按钮</button>
    </div>
  );
};

export default App;

3. 进阶使用 ​

可以将 Provider 抽离出来单独做一层,负责数据的获取与传递:

tsx
import { createContext, useContext } from "react";
import { useTeamsData } from "@/hooks/teams/useTeamsData";

interface OrganizationContextValue {
  teams: Team[];
  isLoadingTeams: boolean;
  error: Error | null;
}

const OrganizationContext = createContext<OrganizationContextValue | null>(
  null,
);

export function OrganizationProvider({ children, initialTeams }) {
  const { data: teams, isLoading, error } = useTeamsData(initialTeams);

  return (
    <OrganizationContext.Provider
      value={{ teams, isLoadingTeams: isLoading, error }}
    >
      {children}
    </OrganizationContext.Provider>
  );
}

export function useOrganization() {
  const context = useContext(OrganizationContext);
  if (!context) {
    throw new Error("useOrganization must be used within OrganizationProvider");
  }
  return context;
}

2. useRef ​

1. 基础介绍 ​

用来创建一个不需要渲染的值。

ts
const ref = useRef(initialValue);
  • initialValue:ref 对象的 current 属性的初始值
  • ref:一个只有一个属性 current 的对象,在后续的渲染中,useRef 将返回同一个对象

2. 可以访问 DOM 节点或子组件 ​

tsx
import { useEffect, useRef } from "react";

function App() {
  const domRef = useRef(null);
  useEffect(() => {
    console.log(domRef.current);
  }, []);

  return <div ref={domRef}>dom</div>;
}

export default App;

3. 值的更改不会触发视图更新 ​

tsx
import { useRef, useState } from "react";

function App() {
  const countRef = useRef(0);
  const [count, setCount] = useState(0);

  console.log("组件渲染");

  return (
    <div>
      {countRef.current}
      {count}
      <button
        onClick={() => {
          countRef.current += 1;
        }}
      >
        ref+1
      </button>
      <button onClick={() => setCount((prev) => prev + 1)}>state+1</button>
    </div>
  );
}

export default App;

点击 ref+1 按钮,countRef 值增加,但视图不会更新。点击 state+1 按钮,视图更新、组件重新渲染,此时打印出 countRef 最新的值。

4. 返回的引用在组件更新时不会被改变 ​

返回同一个对象,可以用来清除定时器:

tsx
import { useEffect, useRef } from "react";

function App() {
  const timeRef = useRef<NodeJS.Timer>();

  useEffect(() => {
    timeRef.current = setInterval(() => {
      console.log("1");
    }, 1000);
    // 在组件卸载时清除定时器,防止内存泄漏
    return () => {
      clearInterval(timeRef.current);
    };
  }, []);

  return <div></div>;
}

export default App;

3. useImperativeHandle ​

1. 基础介绍 ​

与 forwardRef 配合,自定义暴露给父组件的实例值或函数。

ts
useImperativeHandle(ref, createHandle, [deps]);
  • ref:接受 forwardRef 传递过来的 ref
  • createHandle:处理函数,返回值作为暴露给父组件的 ref 对象
  • deps:依赖项,依赖项更改会形成新的 ref 对象

2. 使用场景:组件通信中,父组件调用子组件 ​

tsx
import { forwardRef, useRef, useImperativeHandle } from "react";

/* 定义 ref 类型 */
interface ForwardObject {
  focus: () => void;
}

function App() {
  const ref = useRef<ForwardObject>(null);

  return (
    <div>
      <MyInput ref={ref} />
      <button onClick={() => ref.current?.focus()}>使输入框获取焦点</button>
    </div>
  );
}

/* 子组件 */
const MyInput = forwardRef((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);
  useImperativeHandle(
    ref,
    () => ({
      focus: () => {
        inputRef.current?.focus();
      },
    }),
    [],
  );
  return <input ref={inputRef} />;
});

export default App;

五、工具类 ​

1. useDebugValue ​

1. 基础介绍 ​

在 React 开发者工具中为自定义 Hook 添加标签。

ts
useDebugValue(value, format?)
  • value:在 React 开发者工具中显示的值
  • format:一个格式化函数,将接收 value 作为参数,并返回格式化后的显示值

2. 使用场景:为自定义 Hook 添加标签 ​

tsx
import { useDebugValue, useState } from "react";

const App = () => {
  useNetworkStatus();
  return <div></div>;
};

function useNetworkStatus() {
  const [isOnline] = useState(false);
  // 在开发者工具中的这个 Hook 旁边显示标签:NetworkStatus: "Offline"
  useDebugValue(isOnline ? "Online" : "Offline");

  return isOnline;
}

export default App;

在 React 开发者工具中查看。

React 开发者工具的 hooks 区域显示 NetworkStatus: "Offline" 标签

2. useId ​

1. 基础介绍 ​

生成唯一 ID,解决了在服务器渲染中,服务端和客户端产生 id 不一致的问题。

ts
const id = useId();

2. 使用场景:为属性生成唯一 ID ​

tsx
import { useId } from "react";

export default function Form() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>chose</label>
      <input type="checkbox" id={id} name="chose" />
    </div>
  );
}

点击 chose,能够看到复选框被选中。

提示:不要使用 useId 来生成列表中的 key,key 应该由你的数据生成。