全面掌握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 | 柱状图实用配置

相关推荐
栈老师不回家40 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙1 小时前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~2 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨2 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
帅比九日3 小时前
【HarmonyOS Next】封装一个网络请求模块
前端·harmonyos