React Hooks(实例及详解)

React Hooks 是 React 16.8 版本引入的新特性,它们允许在函数组件中使用状态和其他 React 特性。以下是一些主要的

React Hooks:

  1. useState: 是 React 的一个 Hook,它允许在函数组件中添加和管理状态。在此之前,只有类组件才能拥有自己的状态。useState 返回一个包含当前状态值和更新状态的函数的数组。

基本用法如下:

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

function Example() {
  // 声明一个新的状态变量,其初始值为 0
  const [count, setCount] = useState(0);

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

在这个例子中:

  • const [count, setCount] = useState(0); 创建了一个新的状态变量 count 和一个用于更新这个状态的函数 setCount

  • 当用户点击按钮时,调用 setCount(count + 1) 更新状态。

  • 状态改变会触发组件重新渲染,并且新的状态值将反映在界面上。

    注意,setCount 函数不会立即修改状态,而是创建一个新的状态对象并安排组件进行重新渲染。这是因为在 React 中,所有的状态更新都是异步的。

    你也可以传递一个函数给 setCount 来计算下一个状态值,这样可以确保使用的总是最新的状态:

    jsx 复制代码
    <button onClick={() => setCount((prevCount) => prevCount + 1)}>
      Click me
    </button>

    此外,如果状态是复杂的数据结构(例如对象或数组),并且你需要根据现有的状态来创建一个新的状态,你应该使用函数形式的更新器以避免状态突变问题。

  1. useEffect: 是 React 的一个 Hook,它允许在函数组件中执行副作用操作。这些副作用可能包括数据获取、订阅、手动更改 React 组件的 DOM 输出等。useEffect 可以看作是 componentDidMount, componentDidUpdate, 和 componentWillUnmount 生命周期方法的组合。

基本用法如下:

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

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

  // 使用 Effect Hook 来监听 count 状态的变化
  useEffect(() => {
    document.title = `You clicked ${count} times`;

    return () => {
      // 清理工作(可选)
    };
  }, [count]); // 当依赖项数组中的值改变时,重新运行 effect

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

在这个例子中:

  • useEffect 接收两个参数:一个包含要执行的副作用代码的函数和一个依赖项数组。

  • 当组件渲染完成并挂载到 DOM 上后,useEffect 中的副作用函数会被调用一次。这类似于 componentDidMount 生命周期方法。

  • 当依赖项数组中的值发生变化时,useEffect 中的副作用函数会再次被调用。这类似于 componentDidUpdate 生命周期方法。

  • 如果指定了返回一个清理函数,那么当组件卸载或者依赖项变化导致重新计算 effect 时,这个清理函数会在下次 effect 运行之前被执行。这类似于 componentWillUnmount 生命周期方法。

    注意,如果省略了第二个参数(依赖项数组),那么 effect 将只在组件挂载和卸载时运行,而不是每次渲染之后。这可以用来模拟 componentDidMountcomponentWillUnmount 生命周期方法。

  1. useContext: 是 React 的一个 Hook,它允许在函数组件中访问上下文对象。React 上下文 API 使得数据可以在组件树中传递而无需手动通过每个层级的 props

基本用法如下:

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

// 创建一个新的 context 对象
const ThemeContext = createContext();

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

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 使用 useContext 获取当前的 theme 值
  const theme = useContext(ThemeContext);

  return <button style={{ background: theme }}>Click me</button>;
}

在这个例子中:

  • 首先创建了一个新的 context 对象 ThemeContext

  • 然后在 App 组件中使用 <ThemeContext.Provider> 来提供上下文值。

  • 在子组件 ThemedButton 中,我们使用 useContext(ThemeContext) 来获取当前的上下文值(即主题)。

    useContext 返回的是当前 context 的值。当 context 的值发生变化时,所有订阅了这个 context 的组件都会重新渲染。

    注意,如果你在没有包裹 <Provider> 的情况下使用 useContext,将会返回 context 的默认值。此外,为了避免无谓的重渲染,你应该尽可能地将 useContext 的调用放在最接近需要使用这些值的组件中。

  1. useReducer: 是 React 的一个 Hook,它允许在函数组件中管理复杂的、可复用的逻辑。useReducer 接收一个 reducer 函数和初始状态作为参数,并返回当前的状态以及用于更新状态的 dispatch 方法。

基本用法如下:

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

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

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

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

在这个例子中:

  • 我们首先定义了一个 reducer 函数,它接收两个参数:当前的状态和一个动作对象。根据动作类型,我们可以决定如何更新状态。

  • Counter 组件中,我们使用 useReducer 来创建一个新的状态变量(初始化为 0)和一个 dispatch 方法。dispatch 方法用于发送一个动作给 reducer,从而更新状态。

  • 当用户点击按钮时,我们将对应的增加或减少的动作对象通过 dispatch 方法发送给 reducer,然后状态就会被相应地更新。

    useReducer 可以替代多个 useState 调用,特别是当你的组件需要维护多个相关的状态值时。此外,由于 reducer 是纯函数,这使得测试变得更容易。

  1. useCallback:` 是 React 的一个 Hook,它用于优化性能。当你在函数组件中使用内联回调(例如事件处理器)时,每次渲染都会创建一个新的函数实例。这可能会导致不必要的子组件重新渲染,即使它们没有依赖于这些回调。

    useCallback 接收两个参数:一个需要 memoize 的回调函数和一个依赖项数组。当依赖项数组中的值发生变化时,返回的 memoized 回调函数将会更新;否则,它会返回先前缓存的版本。

基本用法如下:

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

function Example({ a, b }) {
  // 使用 useCallback 来记住计算的值
  const memoizedCallback = useCallback(
    () => {
      console.log(a + b);
    },
    [a, b] // 当 a 或 b 变化时,重新计算
  );

  return (
    <button onClick={memoizedCallback}>
      Click me
    </button>
  );
}

在这个例子中:

  • 我们定义了一个包含 ab 的加法操作的回调函数,并通过 useCallback 返回了一个 memoized 版本。

  • 如果 ab 的值发生变化,useCallback 将返回一个新的 memoized 回调函数;否则,它将返回先前缓存的版本。

  • 当用户点击按钮时,我们触发的是 memoized 回调函数。这样可以避免不必要的子组件重新渲染,因为除非 ab 的值变化,否则回调函数不会改变。

    注意,只有当你的回调函数被传递给子组件并且这个子组件执行了 shouldComponentUpdate() 或者使用了 React.memo() 时,useCallback 才能真正地提升性能。如果你的回调函数仅用于内部操作(如直接操作 DOM),那么使用 useCallback 不会带来任何好处。

  1. useMemo:是 React 的一个 Hook,它用于优化性能。当你在函数组件中执行昂贵的计算或者创建复杂的对象时,每次渲染都会重新进行这些操作。这可能会导致不必要的性能开销。

    useMemo 接收两个参数:一个需要 memoize 的值(通常是返回结果)和一个依赖项数组。当依赖项数组中的值发生变化时,返回的 memoized 值将会更新;否则,它会返回先前缓存的版本。

    基本用法如下:

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

function Example({ a, b }) {
  // 使用 useMemo 来记住计算的值
  const memoizedValue = useMemo(() => {
    console.log('Computing expensive value...');
    return a + b;
  }, [a, b]); // 当 a 或 b 变化时,重新计算

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

在这个例子中:

  • 我们定义了一个包含 ab 的加法操作,并通过 useMemo 返回了一个 memoized 版本。

  • 如果 ab 的值发生变化,useMemo 将返回一个新的 memoized 值;否则,它将返回先前缓存的版本。

  • 每次渲染时,我们使用的是 memoized 值,而不是重新计算的结果。

    注意,只有当你的计算结果被传递给子组件并且这个子组件执行了 shouldComponentUpdate() 或者使用了 React.memo() 时,useMemo 才能真正地提升性能。如果你的计算结果仅用于内部操作(如直接操作 DOM),那么使用 useMemo 不会带来任何好处。

  1. useRef:是 React 的一个 Hook,它允许在函数组件中创建可变的引用。这个 Hook 返回一个 ref 对象,其 .current 属性被初始化为传递的参数(initialValue)。返回的对象在整个组件生命周期内保持不变。

基本用法如下:

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

function TextInputWithFocusButton() {
  // 创建一个 ref 来保存文本输入框的 DOM 元素
  const inputEl = useRef(null);

  const onButtonClick = () => {
    // 当按钮被点击时,使文本输入框获取焦点
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

在这个例子中:

  • 我们通过调用 useRef() 创建了一个新的 ref,并将其赋值给 inputEl 变量。

  • <input> 标签上,我们将 inputEl 赋值给了 ref 属性。这样,React 将会把对应的 DOM 元素绑定到 inputEl.current 上。

  • 当用户点击按钮时,我们可以通过调用 inputEl.current.focus() 来使文本输入框获取焦点。

    注意,useRef() 不仅可以用于 DOM 元素,还可以用于任何类型的变量。它的主要用途是当需要在不同的渲染之间保留一个值或对象时。

  1. useImperativeHandle: 是 React 的一个 Hook,它用于定制暴露给父组件的 ref 值。通常情况下,我们可以通过 ref 访问子组件内部的一些实例方法或属性。然而,在某些场景下,我们可能希望暴露一些特定的方法或属性,而不是默认的整个实例。

基本用法如下:

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

function FancyInput(props, ref) {
  const inputEl = useRef(null);

  // 使用 useImperativeHandle 来暴露 customMethod 给父组件
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputEl.current.focus();
    },
    customMethod: () => {
      console.log('I am a custom method');
    }
  }));

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

// 使用 forwardRef 将 ref 转发到 FancyInput 中
const WrappedFancyInput = forwardRef(FancyInput);

function ParentComponent() {
  const fancyInputRef = useRef(null);

  const handleClick = () => {
    // 在父组件中调用子组件的 customMethod 方法
    fancyInputRef.current.customMethod();
  };

  return (
    <>
      <WrappedFancyInput ref={fancyInputRef} />
      <button onClick={handleClick}>Call Custom Method</button>
    </>
  );
}

在这个例子中:

  • 我们创建了一个名为 FancyInput 的函数组件,并使用了 forwardRef 高阶函数来接收一个 ref 参数。

  • FancyInput 内部,我们使用 useImperativeHandle 来定义要暴露给父组件的方法和属性。这里我们暴露了 focuscustomMethod 方法。

  • 父组件通过 ref 属性将 ref 对象传递给 WrappedFancyInput 子组件。这样,父组件就可以通过 fancyInputRef.current 访问到子组件暴露的 focuscustomMethod 方法。

    注意,过度使用 useImperativeHandle 可能会导致代码难以理解和维护。因此,你应该尽量避免直接操作子组件的 DOM,而是尽可能地使用 React 的数据流进行通信。

  1. useLayoutEffect: 是 React 的一个 Hook,它类似于 useEffect,但有两个关键的区别:

    1. useLayoutEffect 在所有 DOM 变更之后同步调用,但在浏览器绘制之前。这使得你可以读取布局并同步触发重新渲染。
    2. 在服务器端渲染中,useLayoutEffect 不会被执行。

基本用法如下:

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

function Example() {
  useLayoutEffect(() => {
    // 这里可以访问到最新的 DOM 样式和几何信息
    console.log('DOM 已经更新,但我还没有被绘制');
  });

  return <div>Example</div>;
}

在这个例子中:

  • 当组件完成渲染时,useLayoutEffect 内的回调函数将会被执行。此时,所有的 DOM 更新都已经完成,但浏览器还没有开始绘制。

  • 如果你需要在浏览器绘制之前访问或修改 DOM(例如设置样式),你应该使用 useLayoutEffect

    注意,过度使用 useLayoutEffect 可能会导致视觉闪烁或性能问题。因此,你应该尽量避免阻塞浏览器的渲染过程,并尽可能地使用标准的 useEffect 来处理副作用。

  1. useDebugValue:是 React 的一个 Hook,它用于在 React 开发者工具中显示自定义 hook 的标签。这对于调试复杂的自定义 hook 很有用。

基本用法如下:

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

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // 获取 friendID 对应的好友在线状态
  // ...

  // 使用 useDebugValue 来为这个 hook 显示一个可读的标签
  useDebugValue(isOnline ? '在线' : '离线');

  return isOnline;
}

function FriendStatus() {
  const isOnline = useFriendStatus(123456);

  return (
    <span>{isOnline ? '在线' : '离线'}</span>
  );
}

在这个例子中:

  • 我们创建了一个名为 useFriendStatus 的自定义 hook,它接收一个 friendID 参数,并返回好友的在线状态。

  • useFriendStatus 内部,我们使用 useDebugValue 来为这个 hook 显示一个可读的标签。这个标签将在 React 开发者工具的组件树中显示。

  • FriendStatus 组件中,我们调用了 useFriendStatus 并渲染了相应的在线状态。

    注意,useDebugValue 只在开发模式下工作,并且只会影响 React 开发者工具中的展示。因此,在生产环境中,你不需要担心它会带来额外的性能开销。

这些是核心的 React Hooks,此外社区还开发了许多其他的自定义 Hooks,例如 useDebounceuseThrottle 等,以解决特定的问题和场景。

相关推荐
一个专注写代码的程序媛1 小时前
vue组件间通信
前端·javascript·vue.js
一笑code1 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员1 小时前
layui时间范围
前端·javascript·layui
NoneCoder1 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19702 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端
烛阴2 小时前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript
GetcharZp2 小时前
xterm.js 终端神器到底有多强?用了才知道!
前端·后端·go
JiangJiang2 小时前
🚀 React 弹窗还能这样写?手撸一个高质量 Modal 玩起来!
前端·javascript·react.js
吃炸鸡的前端2 小时前
el-transfer穿梭框数据量过大的解决方案
前端·javascript
高德开放平台2 小时前
文末有奖|高德MCP 2.0 出行领域首发打通大模型与高德地图APP互联
前端