全面掌握React内置Hook

前言

Hook 让你可以在组件中使用不同的 React 功能,目前React官网一共列出了15个内置Hook,本文主要就是围绕这15个Hook的用法及使用场景进行介绍。

状态流转

useState

声明一个你可以直接更新的 state 变量。

【语法】

react 复制代码
// 这里可以任意命名,因为返回的是数组,数组解构
const [state, setState] = useState(initialState);

【使用示例】

react 复制代码
// 声明一个名为count的状态变量,初始值为0
const [count, setCount] = useState(0);

useEffect的详细用法可以看这一篇 👉 《简单易懂的React状态管理Hook useState详解》

useReducer

在组件的顶层作用域调用 useReducer 以创建一个用于管理状态的 reducer。

useReducer可以让你将状态更新的逻辑抽象成一个纯函数,这样可以提高代码的可读性和可维护性。也可以配合useContext来实现类似Redux的全局状态管理。

【语法】

jsx 复制代码
// state 当前的状态
// dispatch 一个函数,可以用来更新状态和触发重新渲染
const [state, dispatch] = useReducer(reducer, initialArg, init?)
  • reducer:你自定义的状态逻辑函数,参数为 state 和 action。用于更新 state ,返回值是更新后的 state
  • initialArg:初始化 state ,可以是一个简单的值,也可以是一个对象。
  • init?:计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg。

【使用示例】

实现一个计数器应用

react 复制代码
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
}

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

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

使用场景

  • 组件中有复杂的状态逻辑
  • 有多个子值的状态对象
  • 想要优化性能避免不必要的渲染时

useContext

useContext 可以让你在组件中读取和订阅一个 context 的值。context 是一种在组件树中传递数据的方式,不需要通过 props。

简单的说,就是useContext 可以让你在不同层级的组件之间共享数据,而不需要一层层地传递 props。例如,你可以用 context 来传递主题色、语言、用户信息等。

【语法】

react 复制代码
const value = useContext(SomeContext)
  • SomeContext:一个对象,由 React.createContext 返回。这个值由距离组件最近的 SomeContext.Providervalue prop 决定。如果没有对应的 Provider,那么返回值就是创建该 context 时传入的 defaultValue

  • value:返回值

【使用示例】

javascript 复制代码
// 创建一个 context
const SomeContext = React.createContext(defaultValue);

// 在组件中使用 useContext 读取 context 的值
function MyComponent() {
  const value = useContext(SomeContext);
  // ...
}

// 在组件树中提供 context 的值
function MyPage() {
  return (
    <SomeContext.Provider value={someValue}>
      <MyComponent />
    </SomeContext.Provider>
  );
}

操作节点

useRef

当你需要在组件中操作 DOM 元素,或者保存一些不随渲染变化的值的时候可以用useRef。

例如,你可以用 ref 来保存一个计时器的 ID、一个表单的数据、一个回调函数等。

【语法】

react 复制代码
const ref = useRef(initialValue)
  • initialValue:初始值
  • ref:返回值,一个包含 current 属性的对象。
    • 这个 current 属性的初始值就是传入的初始值,你可以随时修改它,但是修改它不会触发组件的重新渲染
    • 如果你把这个 ref 对象作为 JSX 元素的 ref 属性,那么 React 就会把这个元素的实例赋给 current 属性。在后续的渲染中,useRef 会返回同一个对象。

【使用示例】

javascript 复制代码
// 创建一个 ref
const someRef = useRef(initialValue);

// 在组件中使用 ref 访问 DOM 元素
function MyComponent() {
  const inputRef = useRef(null);
  // ...
  return <input ref={inputRef} />;
}

// 在组件中使用 ref 保存一个值
function MyTimer() {
  const timerIdRef = useRef(0);
  // ...
  function startTimer() {
    timerIdRef.current = setInterval(() => {
      // ...
    }, 1000);
  }
  function stopTimer() {
    clearInterval(timerIdRef.current);
  }
}

通过使用 ref,你可以确保:

  • 你可以在重新渲染之间 存储信息(不像是普通对象,每次渲染都会重置)。
  • 改变它 不会触发重新渲染(不像是 state 变量,会触发重新渲染)。
  • 对于你的组件的每个副本来说,这些信息都是本地的(不像是外面的变量,是共享的)。

注意:改变 ref 不会触发重新渲染,所以 ref 不适合用于存储期望显示在屏幕上的信息。

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时有更多的控制权,你可以自己决定子组件向父组件暴露什么。这样可以避免父组件过多地依赖子组件的细节,也可以保护子组件的内部状态。

不过,useImperativeHandle 应该尽量少用,因为它会破坏组件的封装性,增加代码的复杂度。

【语法】

react 复制代码
useImperativeHandle(ref, createHandle, dependencies?)
  • ref :一个 ref 对象
  • createHandle:一个函数,可以在这个函数中返回任何你想要暴露给父组件的方法或属性,这个返回值父组件可以通过 ref.current 访问到值。
  • dependencies?:用来指定什么时候更新 ref 的值

【使用示例】

javascript 复制代码
// 在子组件中使用 useImperativeHandle 定义 ref 的值
function MyChildComponent(props, ref) {
  // ...
  useImperativeHandle(ref, () => ({
    // 返回一个对象,包含子组件想要暴露给父组件的方法或属性
    focus: () => {
      // ...
    },
    getValue: () => {
      // ...
    },
  }));
  // ...
}

// 使用 forwardRef 包裹子组件,使其能够接收 ref
const MyChildComponent = React.forwardRef(MyChildComponent);

// 在父组件中使用 ref 访问子组件的实例值
function MyParentComponent() {
  const childRef = useRef(null);
  // ...
  return <MyChildComponent ref={childRef} />;
}

处理副作用

useEffect

由渲染引起的副作用。每当组件渲染时,React 将更新屏幕,然后运行 useEffect 中的代码。

【语法】

react 复制代码
useEffect(setup, dependencies?)

【使用示例】

react 复制代码
useEffect(() => {
    if (isPlaying) { 
      // ...
    } else {
      // ...
    }
  }, [isPlaying]); // ......所以它必须在此处声明!

useEffect的详细用法可以看这一篇 👉 《React小白进阶之useEffect和ref》

useLayoutEffect

useLayoutEffect可以在DOM更新后同步地执行一些副作用操作。

【语法】

react 复制代码
useLayoutEffect(setup, dependencies?)

它的用法和useEffect类似,都接受一个函数作为参数,并且可以返回一个清理函数。(注意:useLayoutEffect 可能会影响性能。尽可能使用 useEffect

【使用示例】

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

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

  // useEffect会在页面渲染后执行
  useEffect(() => {
    if (count === 0) {
      // 设置count为随机数
      setCount(Math.floor(Math.random() * 100));
    }
  }, [count]);

  // useLayoutEffect会在页面渲染前执行
  useLayoutEffect(() => {
    if (count === 0) {
      // 设置count为随机数
      setCount(Math.floor(Math.random() * 100));
    }
  }, [count]);

  return (
    <div className="App">
      <h1>useLayoutEffect vs useEffect</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

export default App;

如果运行这个代码,你会发现useEffect和useLayoutEffect的效果是不一样的。当你点击Reset按钮时,

  • useEffect会先显示0,然后再显示一个随机数,因为它是在页面渲染后才执行的。
  • useLayoutEffect会直接显示一个随机数,因为它是在页面渲染前就执行了,所以不会出现闪烁的效果。

【useLayoutEffect和useEffect的不同点】

  • useLayoutEffect会在浏览器绘制页面之前执行
  • useEffect会在浏览器绘制页面之后执行

【使用场景区分】

  • 【useLayoutEffect】:如果你的副作用操作需要读取或修改DOM的布局,或者需要保证在页面渲染前就执行,比如获取元素的尺寸或位置,或者触发强制同步的重新渲染。那么就用useLayoutEffect。
  • 【useEffect】:如果你的副作用操作不涉及DOM的布局,或者可以延迟到页面渲染后再执行,比如发送网络请求或设置定时器。则用useEffect。

useInsertionEffect

useInsertionEffect 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect

【语法】

scss 复制代码
useInsertionEffect(setup, dependencies?)

【使用示例】

jsx 复制代码
import { useInsertionEffect } from 'react';

// 在你的 CSS-in-JS 库中
function useCSS(rule) {
  useInsertionEffect(() => {
    // ... 在此注入 <style> 标签 ...
  });
  return rule;
}

性能优化

useMemo

useMemo可以用来优化函数组件的性能。useMemo的作用是记住一个值,只有当它的依赖项发生变化时,才会重新计算这个值。这样可以避免在每次渲染时都执行一些复杂或开销大的计算。

【语法】

javascript 复制代码
const cachedValue = useMemo(calculateValue, dependencies)
  • calculateValue:是一个函数,它返回要记住的值
  • dependencies:是一个数组,它指定了依赖项。如果依赖项数组为空,那么只会在组件挂载时执行一次函数,并且永远不会重新计算。

【使用示例】

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

function App() {
  const [count, setCount] = useState(0); // 用于计数的状态
  const [name, setName] = useState(''); // 用于输入姓名的状态

  // 使用useMemo记住一个对象,只有当name变化时才重新创建
  const person = useMemo(() => ({ name }), [name]);

  return (
    <div>
      <h1>useMemo示例</h1>
      <p>你好,{person.name}!</p>
      <input value={name} onChange={e => setName(e.target.value)} />
      <p>你点击了{count}次。</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  );
}

export default App;

以上代码,当你点击按钮时,person对象的引用不会变化,因为它被useMemo记住了。这样可以避免触发不必要的渲染或副作用,比如如果你需要把person对象作为props传递给子组件。

【useMemo使用场景总结】

  • 需要对一个值进行复杂或开销大的计算,而这个值又只依赖于某些状态时,你可以使用useMemo来记住这个值,避免在每次渲染时都重新计算。
  • 需要把一个值作为props传递给子组件时,你可以使用useMemo来保证这个值的引用不变,避免子组件因为接收到新的props而重新渲染。
  • 需要创建一个对象或数组时,你可以使用useMemo来保证这个对象或数组的引用不变,避免触发不必要的渲染或副作用。

useCallback

useCallback可以让你在组件中创建一个只有在依赖项变化时才会更新的函数。useCallback的作用是优化性能,避免不必要的渲染和重新计算。

【语法】

react 复制代码
const cachedFn = useCallback(fn, dependencies)
  • fn:想要缓存的函数。可以接受任何参数并且返回任何值。
  • dependencies:有关是否更新 fn 的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。
  • cachedFn:返回值,返回你已经传入的 fn 函数

【使用示例】

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

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

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <Child increment={increment} />
    </div>
  );
}

在这个例子中,Parent组件有一个count状态和一个increment函数,它们都被传递给了Child组件。increment函数使用了useCallback,它只有在第一次渲染时才会创建,之后就会一直保持不变,除非依赖项发生变化。这样就保证了Child组件在接收到increment函数时,不会因为它的引用变化而重新渲染。

useMemo和useCallback的区别

  • useMemo返回的是一个值
  • useCallback返回的是一个函数。(useCallback也可以用来优化性能,特别是当需要把函数作为props传递给子组件时,可以避免不必要的重新渲染。)

useTransition

它可以让你在不阻塞UI的情况下更新状态,帮助你实现一些并发模式的功能,比如:在数据加载时显示一个加载指示器,或者在路由切换时显示一个过渡动画。

【语法】

react 复制代码
const [isPending, startTransition] = useTransition()
  • isPending:一个布尔值,表示是否有一个过渡正在进行中。
  • startTransition:一个函数,用于将一个状态更新标记为过渡。

【使用示例】

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

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

  const increment = () => {
    startTransition(() => {
      setCount(c => c + 1); 
    });
  }

  return (
    <div>
      {isPending ? (
        'Loading...' 
      ) : (
        <div>{count}</div>  
      )}

      <button onClick={increment}>+</button>
    </div>
  );
}

在上面例子中,点击按钮时,通过 startTransition() 标记一个更新将要开始。此时 isPending 会变为 true,显示 Loading 状态。等到 setCount 完成更新,isPending 变为 false,组件退出 pending 状态,显示更新后的 count 值。

【主要使用场景】

  • 列表中的项添加/删除时,平滑过渡显示新的列表状态。
  • UI 组件切换时,实现组件之间平滑过渡。
  • 数据加载时,显示加载状态过渡。

useDeferredValue

useDeferredValue它可以用来延迟值的更新,让UI更流畅。

在 React 中,当状态或属性发生变化时,组件会重新渲染。有时候,某些状态的变化并不会立即影响组件的显示,而是需要一些时间。例如,当用户在输入框中输入文字时,每次按键都会触发状态的更新,但实际上并不需要立即重新渲染组件,而是在用户停止输入一段时间后才需要更新显示。

这时候就可以使用 useDeferredValue 来延迟状态的更新。

【语法】

react 复制代码
const deferredValue = useDeferredValue(value)
  • value:你想延迟的值,可以是任何类型。

【使用示例】

假设有一个搜索框的组件,用户每次输入都会触发搜索功能。我们可以使用 useDeferredValue 来延迟搜索词的更新,以避免频繁的搜索请求

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

function SearchBox() {
  const [searchTerm, setSearchTerm] = useState('');
  const deferredSearchTerm = useDeferredValue(searchTerm);

  // 模拟搜索请求的函数
  const performSearch = async (term) => {
    // 发起搜索请求
    // ...
  };

  // 当 deferredSearchTerm 更新时,执行搜索
  useEffect(() => {
    performSearch(deferredSearchTerm);
  }, [deferredSearchTerm]);

  const handleChange = (event) => {
    setSearchTerm(event.target.value);
  };

  return (
    <input type="text" value={searchTerm} onChange={handleChange} />
  );
}

其他Hook

这些 Hook 主要对库的作者有用,在应用代码中并不常用。

useDebugValue

允许你在 React 开发者工具中为自定义 Hook 添加一个标签。

scss 复制代码
useDebugValue(value, format?)

useId

允许组件绑定一个唯一 ID。通常与可访问性 API 一起使用。

ini 复制代码
const id = useId()

useSyncExternalStore

允许一个组件订阅一个外部 store。

ini 复制代码
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

🎨【点赞】【关注】不迷路,更多前端干货等你解锁

往期推荐

👉 React小白快速入门教程

👉 React小白进阶之useEffect和ref

👉 ES6中一些很好用的数组方法

👉 echarts | 柱状图实用配置

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax