react 有常用 Hooks,结合示例解释

40 阅读7分钟

React Hooks 详解

React 提供了多种内置的 Hooks,用于在函数组件中管理状态、处理副作用、优化性能等。Hooks 是 React 16.8 引入的一项功能,它使得函数组件能够拥有类组件的功能,代码更简洁灵活。下面介绍一些常见的 React Hooks,并通过代码示例展示如何使用它们。

1. useState —— 管理状态

useState 是最常用的 Hook,用于在函数组件中管理状态。它允许我们在函数组件中定义内部状态,并提供了一个更新状态的函数。

如何使用:

useState 接受一个初始值作为参数,返回一个数组,其中第一个元素是状态值,第二个元素是更新状态的函数。

示例:

jsx
复制代码
import React, { useState } from 'react';

function Counter() {
  // 使用 useState 定义状态变量和更新函数
  const [count, setCount] = useState(0);

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

export default Counter;

在这个例子中,count 是一个状态变量,setCount 是用于更新 count 的函数。每次点击按钮时,状态更新,组件会重新渲染并显示最新的 count 值。


2. useEffect —— 处理副作用

useEffect 用于在函数组件中处理副作用,比如数据获取、订阅、手动更改 DOM 等,它相当于类组件中的 生命周期函数

如何使用:

useEffect 接受一个回调函数,该函数会在组件渲染后执行。可以通过传递依赖数组,当数组中的元素发生变化时回调函数也会再次执行。

示例:

jsx
复制代码
import React, { useState, useEffect } from 'react';

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

  // 使用 useEffect 来实现一个计时器,每秒增加一次计数
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // 清除副作用,类似于 componentWillUnmount
    return () => clearInterval(timer);
  }, []); // 空数组表示只在组件挂载时执行一次

  return <div>Count: {count}</div>;
}

export default Timer;

在这里,useEffect 只会在组件初次渲染时启动计时器,并在组件卸载时清除计时器,避免内存泄漏。


3. useContext —— 使用上下文

useContext 用于从 React 的上下文中获取共享数据,可以用做跨组件传参。

如何使用:

首先,需要使用 React.createContext 创建上下文,使用 useContext 在任何需要的地方访问上下文的值。

示例:

jsx
复制代码
import React, { useContext } from 'react';

// 创建一个上下文
const ThemeContext = React.createContext('light');

function ThemedButton() {
  // 使用 useContext 获取上下文的值
  const theme = useContext(ThemeContext);

  return <button style={{ background: theme === 'light' ? '#fff' : '#333' }}>
    Themed Button
  </button>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

export default App;

在这个示例中,useContext 获取了 ThemeContext 的值,按钮的背景色根据上下文主题动态变化。


4. useReducer —— 管理复杂状态逻辑

useReduceruseState 的替代方案,适用于管理更复杂的状态逻辑,特别是当状态更新涉及多个子状态或复杂的操作时。它与 Redux 中的 reducer 概念类似。

如何使用:

useReducer 接受一个 reducer 函数和初始状态,返回当前状态和 dispatch 函数,用于派发动作来更新状态。

示例:

jsx
复制代码
import React, { useReducer } from 'react';

// 定义 reducer 函数
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();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

export default Counter;

这个例子展示了如何使用 useReducer 处理复杂的状态更新逻辑,通过 dispatch 触发不同的动作来更新状态。


5. useRef —— 引用 DOM 元素或保持不变的值

useRef 提供了一种直接访问 DOM 元素的方式,并且它还可以用于存储不会导致组件重新渲染的可变值。

如何使用:

useRef 返回一个可变的 ref 对象,其 current 属性可以存储 DOM 元素或其他数据。

示例:

jsx
复制代码
import React, { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // 在组件挂载时让输入框自动获取焦点
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

export default FocusInput;

在这个示例中,useRef 引用一个输入框,useEffect 确保组件挂载时输入框获得焦点。


6. useMemo —— 性能优化

useMemo 用于缓存计算结果,避免在每次渲染时重复执行高开销的计算操作。它只有在依赖项发生变化时才会重新计算值。

如何使用:

useMemo 接受一个回调函数和依赖项数组,返回缓存的计算结果。

示例:

jsx
复制代码
import React, { useState, useMemo } from 'react';

function ExpensiveCalculation(num) {
  console.log('Expensive calculation running...');
  return num * num;
}

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

  // 使用 useMemo 缓存计算结果,只有 count 改变时才会重新计算
  const squared = useMemo(() => ExpensiveCalculation(count), [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Squared: {squared}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input value={input} onChange={e => setInput(e.target.value)} placeholder="Type something..." />
    </div>
  );
}

export default App;

useMemo 在这个例子中确保只有 count 变化时才会重新计算平方值,从而避免不必要的计算。


7. useCallback —— 缓存函数引用

useCallback 用于缓存函数,防止每次组件重新渲染时创建新的函数引用。这在依赖函数作为 props 传递时尤为有用,可以防止子组件的不必要重新渲染。

如何使用:

useCallback 接受一个回调函数和依赖项数组,返回缓存的函数。

示例:

jsx
复制代码
import React, { useState, useCallback } from 'react';

function Button({ handleClick }) {
  console.log('Button rendered');
  return <button onClick={handleClick}>Click me</button>;
}

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

  // 使用 useCallback 缓存 handleClick 函数
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <Button handleClick={handleClick} />
    </div>
  );
}

export default App;

通过 useCallback,可以防止在每次渲染时重新创建 handleClick 函数,优化性能。


8. useLayoutEffect —— 同步执行副作用

useLayoutEffect 的作用与 useEffect 类似,都是用于执行副作用,但它的触发时机有所不同。useLayoutEffect 会在浏览器完成 DOM 的布局和渲染之后,同步地运行回调函数。这使得它更适合需要读取 DOM 布局信息并进行同步更新的场景,比如测量 DOM 大小或强制重绘。

如何使用:

useLayoutEffect 的用法和 useEffect 类似,它也接收一个回调函数和依赖项数组。不同的是,useLayoutEffect 在 DOM 更新后但在浏览器完成绘制之前同步运行。

示例:

jsx
复制代码
import React, { useLayoutEffect, useRef } from 'react';

function LayoutComponent() {
  const divRef = useRef(null);

  useLayoutEffect(() => {
    // 在 DOM 更新后立即读取并修改布局
    console.log('Div width:', divRef.current.offsetWidth);
  });

  return <div ref={divRef} style={{ width: '100%' }}>Resize me!</div>;
}

export default LayoutComponent;

在这个示例中,useLayoutEffect 会在 DOM 元素更新后立即读取宽度信息,而不是等到屏幕完成绘制。


9. useImperativeHandle + forwardRef + DOM 引用

  • forwardRef: 它允许父组件能够通过 ref 直接访问函数组件内部的某些 DOM 元素或方法。通常情况下,ref 无法直接作用于函数组件,而通过 forwardRef,我们可以将 ref 从父组件传递到子组件,并应用到子组件的内部 DOM 元素上。

  • useImperativeHandle: 它允许子组件有选择性地暴露一些内部方法或属性给父组件。通常情况下,父组件使用 ref 只能访问子组件的 DOM 元素,而通过 useImperativeHandle,我们可以让父组件不仅仅访问 DOM 元素,还能访问子组件内部定义的逻辑,比如某个自定义方法。

示例:

import { useRef, forwardRef, useImperativeHandle } from 'react';

// 子组件:通过 forwardRef 和 useImperativeHandle 将特定方法暴露给父组件
const Son = forwardRef((props, ref) => {
  const refInput = useRef(null);

  // 定义一个让输入框聚焦的方法
  const inputFocusHandle = () => {
    refInput.current.focus();
  };

  // 通过 useImperativeHandle 将 inputFocusHandle 方法暴露给父组件
  useImperativeHandle(ref, () => ({
    inputFocusHandle, // 让父组件可以通过 ref 调用这个方法
  }));

  return <input type="text" ref={refInput} />;
});

// 父组件:调用子组件的方法
function App() {
  const refSon = useRef(null);

  const handleClick = () => {
    // 调用子组件的 inputFocusHandle 方法,让输入框获得焦点
    if (refSon.current) {
      refSon.current.inputFocusHandle(); // 通过 ref 调用子组件的方法
    }
  };

  return (
    <div className="App">
      <button onClick={handleClick}>Focus Input</button>
      <Son ref={refSon} /> {/* 将 ref 传递给子组件 */}
    </div>
  );
}

export default App;


总结

React Hooks 是函数组件强大的功能拓展工具,使得函数组件具备了处理状态、执行副作用、优化性能等能力。常用的 Hooks 包括:

  • useState:管理组件状态。
  • useEffect:处理副作用(如数据获取)。
  • useContext:在组件树中共享数据。
  • useReducer:处理复杂状态逻辑。
  • useRef:访问 DOM 或存储不需要触发渲染的变量。
  • useMemo:缓存计算结果,优化性能。
  • useCallback:缓存函数引用,避免不必要的重新创建。
  • useImperativeHandle:让父组件访问子组件的方法。
  • useLayoutEffect:在布局和绘制完成之前同步执行副作用。

这些 Hooks 不仅使代码更加简洁,而且提高了代码的可读性和可维护性,帮助开发者更灵活地构建功能丰富的 React 应用程序。