React 设计模式 0x3:Ract Hooks

53 阅读5分钟

React Hooks

React Hooks 是在函数式组件中使用的生命周期方法,React Hooks 在 React 16.8 中被引入。在类组件中的生命周期方法已被合并成 React Hooks,React Hooks 无法在类组件中使用。

其中一些内置的 React Hooks 包括以下几个:

  • useState
  • useReducer
  • useEffect
  • useLayoutEffect
  • useMemo
  • useCallback
  • useRef
  • useContext

在使用 React Hooks 时,需要遵循一些规则:

  • Hooks 只能在函数式组件中调用
  • Hooks 必须从顶层调用,不能在循环、条件语句等内部调用
  • 可以创建自己的 Hooks,但必须遵循前面两条规则

useState

useState 方法是常用的 React Hooks 之一。该 Hook 被归类为 React 中的受控组件中,useState 方法设置了一个初始值,可以随着用户执行操作而更新。

import React, { useState } from "react";

function Example() {
  // 声明一个新的叫做 "count" 的 state 变量
  const [count, setCount] = useState(0);

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

export default Example;

useReducer

useReducer 方法是常用的 React Hooks 之一。当应用程序中存在复杂的状态更改时,可以使用此 Hook,类似于 useState,但是需要发送 action 来更新状态:

import React, { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const initialState = { count: 0 };

function Example() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

export default Example;

useEffect

useEffect 方法是常用的 React Hooks 之一。useEffect 有两个参数(箭头函数和可选的依赖项数组),用于异步操作。

依赖项数组是可选的,不传入数组时,回调函数会在每次渲染后执行,传入空数组时,回调函数只会在组件挂载和卸载时执行。依赖项数组可以接受任意数量的值,这意味着对于依赖项数组中更改的任何值,useEffect 方法将再次运行。

useEffect 箭头函数支持返回一个函数,该函数会在组件卸载时执行,用于清理定时器、取消事件监听等。

通常在组件挂载之前进行 API 调用时,会使用 useEffect

import React, { useState, useEffect } from "react";

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

  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`;
  }, [count]);

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

export default Example;

useLayoutEffect

useLayoutEffect 是 React Hooks 提供的一个用于执行副作用操作的 Hook,它与 useEffect 相似,但有一些区别。

useEffect 一样,useLayoutEffect 也会在组件渲染之后执行,但是它会在浏览器 layoutpaint 之前同步执行。这意味着 useLayoutEffect 中的任何操作都将在浏览器更新 DOM 之前执行,这使得它适用于需要精确控制渲染结果的情况。

useEffect 不同的是,useLayoutEffect 不会异步执行,这意味着它会阻塞渲染过程,直到它完成。因此,它的性能比 useEffect 差,特别是在执行昂贵操作的情况下。

使用 useLayoutEffect 的场景通常是需要在浏览器更新 DOM 前同步计算布局或者执行某些 DOM 操作。如果没有必要进行同步的操作,建议使用 useEffect 来代替,以获得更好的性能和更流畅的用户体验。

import React, { useState, useLayoutEffect } from "react";

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

  useLayoutEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`;
  }, [count]);

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

export default Example;

useMemo

useMemo 用于在组件重新渲染时缓存计算结果,它会缓存一个计算的返回值。可用于性能优化,因为它会缓存计算出的值,并在依赖项数组中的值不改变时返回该值。如果这些值发生变化,那么 useMemo 就会重新运行,然后返回新计算出的值。

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

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

  const expensive = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < count; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

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

export default Example;

useCallback

useCallback 主要用于避免在每次渲染时都重新创建函数。

在 React 中,当父组件重新渲染时,所有的子组件也会重新渲染。如果子组件的某个函数作为 props 传递给子组件,而父组件重新渲染时,这个函数会被重新创建。这可能会导致不必要的渲染,因为即使没有必要更新组件,子组件也会重新渲染。这时就可以使用 useCallback 来优化性能。

useCallback 接收两个参数:回调函数和一个依赖项数组。当依赖项数组中的任何一个值发生变化时,回调函数就会重新生成。这意味着当 useCallback 返回的函数被传递给子组件时,只有在依赖项变化时才会重新生成。

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

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

  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]);

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

function Child({ onClick }) {
  console.log("Child render");
  return <button onClick={onClick}>Click me</button>;
}

export default Example;

useRef

useRef 用于在函数组件中创建一个持久化的引用变量,该变量的值在组件重新渲染时不会被重置。useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(即初始值),可以通过对 current 属性的修改来更新其值。与 useState 的主要区别在于,useState 的状态更新会触发组件重新渲染,而 useRef 的引用更新不会。

例如,可以使用 useRef 存储上一次的状态值,以便在下一次状态更新时进行比较,从而避免不必要的副作用。

useRef 方法主要用于以下两个方面:

  • 指向 DOM 中的一个元素

    import React, { useRef } from "react";
    
    function Example() {
      const inputRef = useRef(null);
    
      const handleClick = () => {
        inputRef.current.focus();
      };
    
      return (
        <div>
          <input ref={inputRef} />
          <button onClick={handleClick}>Focus the input</button>
        </div>
      );
    }
    
    export default Example;
    
  • 存储一些不需要触发重新渲染的变量或状态值

    import React, { useState, useRef } from "react";
    
    function Example() {
      const [count, setCount] = useState(0);
      const prevCountRef = useRef();
    
      const handleClick = () => {
        prevCountRef.current = count;
        setCount(count + 1);
      };
    
      return (
        <div>
          <p>
            Now: {count}, before: {prevCountRef.current}
          </p>
          <button onClick={handleClick}>Click me</button>
        </div>
      );
    }
    
    export default Example;
    

useContext

useContext 用于访问在 React.createContext 中创建的上下文对象。它允许在 React 组件之间共享数据,而不需要通过多层逐层 props 传递数据。

useContext 接受一个上下文对象(通过 React.createContext 创建),并返回该上下文的当前值。在组件渲染期间,当上下文的值发生更改时,React 将重新渲染组件。

import React, { useContext, createContext } from "react";

const ThemeContext = createContext("light");

const Comp1 = () => {
  const theme = useContext(ThemeContext);
  return <div>{theme}</div>;
};

const Comp2 = () => {
  const theme = useContext(ThemeContext);
  return <div>{theme}</div>;
};

function Example() {
  return (
    <ThemeContext.Provider value="dark">
      <Comp1 />
      <Comp2 />
    </ThemeContext.Provider>
  );
}

export default Example;

自定义 Hooks

可以编写自己的 Hooks,这些 Hooks 是以 use 开头的函数,并且遵循之前提到的 React Hooks 的相同原则。

import React, { useState, useEffect } from "react";

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

function Example() {
  const isOnline = useFriendStatus(0);

  if (isOnline === null) {
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

export default Example;