React Hooks

119 阅读2分钟

Hook 是 React 16.8 新增的特性,提供了一系列 use 开头的函数,使开发者能在函数组件内保存状态 (state) 和触发副作用 (effect)。函数组件 + Hook 的写法解决了类组件的一些缺点,正逐渐成为 React 的主流写法。

React 内置了常用的 Hook,包括 useStateuseEffectuseContext 等。开发者也可以基于内置 Hook 编写自己的自定义 Hook,抽取不同组件之间的共同状态逻辑。

Hook 规则

Hook 的使用十分简洁方便,但是为了让 React 正确识别 Hook,需要遵循以下 2 条规则:

  • 只在最顶层使用 Hook,确保每次渲染时都以相同的顺序调用 Hook
    • 函数组件或自定义 Hook 的顶层作用域
    • 不能在条件判断、循环体、嵌套函数内调用 Hook
  • 只在函数组件和自定义 Hook 中调用 Hook,不能在普通函数中使用 Hook
    • 确保组件的状态逻辑清晰可读

useState

使用 useState 声明一个状态变量,传入初始值,返回状态变量与修改状态值的 setter 函数。初始值可以是任意类型,包括原始类型和对象。通过解构语法为返回的变量和 setter 函数声明合适的名称。

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

然后就可以访问和修改状态的值。

return (
  <div>
    <p>You clicked {count} times</p>
    <button onClick={() => setCount(count + 1)}>
      Click me
    </button>
  </div>
);

可以在一个函数中多次调用 useState,每次返回的状态变量都是独立的。

useEffect

使用 useEffect 执行副作用操作,包括访问 DOM、获取数据、建立订阅等,通过返回的函数可以进行一些后处理,比如取消订阅、取消定时器等。

useEffect(() => {
  document.title = `You clicked ${count} times`;
});

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

随着 state 的更新,组件也会重新执行并渲染,useEffect 会在组件第一次挂载和每次更新时执行。如果有返回的清理函数,清理函数会在每次更新和组件最终卸载时执行。组件更新时,先执行清理函数,后重新执行 useEffect

每次组件更新都清理并重新执行 useEffect 有时是不必要的,会带来额外的性能开销,可以在第二个参数指定一个 state 数组,只有当数组中的 state 发生变化时才重新执行。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

自定义 Hook

React 内置的 Hook 提供了最基本的抽象,开发者可以将公共的组件状态逻辑提取到一个自定义 Hook 函数中,实现更好的代码复用,并且使用 Hook 能够把属于同一功能的代码组合到一个函数中,而不是分散在多个生命周期函数中。

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });
  return isOnline;
}

useFriendStatus 传入一个好友的 ID,内部保存该好友的在线状态,通过外部 API 订阅好友在线状态的变化,当状态变化时更新内部 state 的值,最后把这个 state 返回。

外部组件可以使用这个自定义 Hook 监听指定好友的在线状态,并确保好友的状态变化时,返回值能够及时更新。避免在多个需要相同功能的组件中存储相同的状态、编写相同的订阅代码,实现了状态、逻辑的统一复用。