React Hooks总览

总览

hooks 功能分类 具体 hooks 具体功能 React v18新特性 跨端支持
数据更新驱动 useState 定义要在页面中渲染的数据 ❌ ✔
useReducer 定义要在页面中渲染的数据,且这个数据有多种处理逻辑 ❌ ✔
useSyncExternalStore concurrent 模式下,订阅外部 store 的行为,触发更新 ✔ ❌
useTransition concurrent 模式下,在不阻塞 UI 的情况下来更新状态 ✔ ❌
useDeferredValue concurrent 模式下,延迟更新 UI 的某些部分 ✔ ❌
执行副作用 useEffect 异步状态下,视图更新后,执行副作用 ❌ ✔
useLayoutEffect 同步状态下,视图更新前,执行副作用 ❌ ✔
useInsertionEffect 用于处理 CSS-in-JS 缺陷问题 ✔ ❌
状态获取与传递 useContext 接收祖先组件的 context 传递的信息 ❌ ✔
useRef 存储一个不需要渲染的值 ❌ ✔
useImperativeHandle 配合 forwardRef 将子组件的 ref 传递给父组件 ❌ ✔
状态派生与保存 useMemo 缓存结果 ❌ ✔
useCallback 缓存函数 ❌ ✔
工具 hooks useId 生成唯一的 ID ✔ ❌
useDebugValue 在 React 开发者工具中为自定义 Hook 添加标签 ❌ ❌

数据更新驱动

useState

useState 允许向组件添加一个 状态变量。

jsx 复制代码
/**
 * @param { any } initValue:初始值
 * @return { array } arr:状态信息
           state { any } 状态名
           setState { function } 修改状态的函数
 */
const [state, setState] = useState(initValue)

// 第一种方式:传入值
setState(newValue);

// 第二种方式:传入函数
setState(preValue => newValue);

示例:

jsx 复制代码
const [number, setNumber] = useState(0)

// 第一种方式:传入值
setNumber(number + 1)

// 第二种方式:传入函数
setNumber(number => number + 1)

注意事项:

  1. 在函数组件一次执行上下文中,state 的值是固定不变的
  2. 如果两次 setState 传入相同的 state 值,那么组件就不会更新
  3. 当触发 setState 在当前执行上下文中获取不到最新的 state,只有在下一次组件 render 中才能获取到

useReducer

useReducer 能够在无状态组件中运行的类似 redux 的功能 api 。

当对一个状态有多种处理逻辑时建议使用 useReducer。

jsx 复制代码
/**
 * @param { function } reducer:处理函数
 * @param { any } initValue:初始值
 * @param { function } compareInitValueFn:计算初始值的函数,如果存在则初始值为 compareInitValueFn(initValue)
 * @return { array } arr:状态信息
           state { any } 状态名
           dispatchState { function } 派发状态的函数
 */
const [state, dispatchState] = useReducer(stateReducer, initValue, compareInitValueFn?)

示例:

jsx 复制代码
const numberReducer = (state, action) => {
  switch(action.type) {
    case 'add':
      return state + 1;
    case 'reduce':
      return state - 1;
  }
}
const [number, dispatchNumber] = useReducer(numberReducer, 0);

dispatchNumber({
  type: 'add'
})

useSyncExternalStore

useSyncExternalStore 可以订阅外部 store。

jsx 复制代码
/**
 * @param { function } subscribe:订阅函数
 * @param { function } getSnapshot:返回组件需要的 store 中的数据快照 带有记忆功能的选择器
 * @param { function } getServerSnapshot:用于 hydration 模式下的 getSnapshot
 * @return { any } snapshot:该 store 的当前快照
 */
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

示例:

jsx 复制代码
import { combineReducers , createStore  } from 'redux'

/* number Reducer */
function numberReducer(state = 1, action) {
  switch(action.type) {
    case 'ADD':
      return state + 1
    case 'DEL':
      return state - 1
    default:
      return state
  }
}

/* 注册reducer */
const rootReducer = combineReducers({ number: numberReducer })
/* 创建 store */
const store = createStore(rootReducer, { number: 1})

function Index(){
  /* 订阅外部数据源 */
  const state = useSyncExternalStore(store.subscribe, () => store.getState().number)
  return (
    <div>
      <button onClick={()=>store.dispatch({type:'ADD'})}>{ state }</button>
    </div>
  )
}

注意:

  1. getSnapshot 返回的 store 快照必须是不可变

    如果底层 store 有可变数据,要在数据改变时返回一个新的不可变快照;否则,返回上次缓存的快照。

  2. 如果在重新渲染时传入一个不同的 subscribe 函数,React 会用新传入的 subscribe 函数重新订阅该 store

    可以通过在组件外声明 subscribe 来避免。

useTransition

useTransition 可以在不阻塞 UI 的情况下来更新状态。

jsx 复制代码
/**
 * @return { array } transitionInfo:转换状态信息
          isPending { boolean } 是否存在待处理的转换
          startTransition { function } 将状态由更新状态标记为转换状态
                   @param { function } scope:作用域函数
 */
const [isPending, startTransition] = useTransition()

示例:

jsx 复制代码
export default function Index(){
  const [number, setNumber] = React.useState(0) // 需要立即响应的任务,立即更新任务
  const [count, setCount] = useState(0) // 不需要立即响应的任务,过渡任务
  const [isPending, startTransition] = useTransition() 
  const btnClick = () => {
     setNumber(number + 1) // 立即更新
     startTransition(() => { // startTransition 里面的任务优先级低
       setCount(count + 1);
     })
  }
  return (
    <div>
      <button onClick={()=>btnClick()}>{ count }</button>
    </div>
  )
}

注意:

  1. 只有在可以访问该状态的 set 函数时,才能将更新包装为转换状态

    如果想响应某个 prop 或自定义 Hook 值启动转换,请尝试使用 useDeferredValue

  2. 传递给 startTransition 的函数必须是同步的。

useDeferredValue

useDeferredValue 可以延迟更新 UI 的某些部分。

jsx 复制代码
/**
 * @param { any } value:延迟更新的值
 * @return { any } deferredValue:延迟更新的值
 */
const deferredValue = useDeferredValue(value)

示例:

jsx 复制代码
export default function Index(){
  const [number, setNumber] = useState(1) // 需要立即响应的任务,立即更新任务
  const deferNumber = useDeferredValue(number) // 把状态延时更新,类似于过渡任务
  const btnClick = () => {
    setNumber(number + 1) // 立即更新
  }
  return (
    <div>
      <button onClick={()=>btnClick()} >{ deferNumber }</button>
    </div>
  )
}
  • 在组件的初始渲染期间,返回的延迟值将与提供的值相同
  • 但是在组件更新时,React 将会先尝试使用旧值进行重新渲染(因此将返回旧值)
  • 然后再在后台使用新值进行另一个重新渲染(这时将返回更新后的值)

注意:应该向 useDeferredValue 传递原始值或在渲染之外创建的对象。

如果在渲染期间创建了一个新对象,并立即将其传递给 useDeferredValue,那么每次渲染时这个对象都会不同,这将导致后台不必要的重新渲染。

执行副作用

useEffect

useEffect 用于异步 监听组件的 state 属性,在浏览器绘制后执行。

jsx 复制代码
/**
 * @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
 * @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
 */
useEffect(setup, dependencies?)

示例:

jsx 复制代码
useEffect(() => {
  let timer = setInterval(() => {
    console.log(1)
  }, 1000)
  return () => {
    timer = null
  }
}, [])

可以把 useEffect 看作是以下三个生命周期的组合:

  • componentDidMount():组件挂载完成执行 callback
  • componentDidUpdate():监听的 state 变化执行 callback
  • componentWillUnmount():组件将要销毁执行 callback 的返回函数

useLayoutEffect

useLayoutEffect 是 useEffect 的同步 版本,并且是在浏览器绘制前执行,主要用于操作 DOM。

注意:useLayoutEffect callback 中代码执行会阻塞浏览器绘制

jsx 复制代码
/**
 * @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
 * @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
 */
useLayoutEffect(setup, dependencies?)

useInsertionEffect

useInsertionEffect 可以在布局副作用触发之前将元素插入到 DOM 中。

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

jsx 复制代码
/**
 * @param { function } setup:回调函数,初始化执行一次,当监听的 state 改变时执行,返回一个当组件被销毁时执行的函数
 * @param { array } dependencies:要监听的 state,默认为所有 state,空数组表示不监听任何 state
 */
useInsertionEffect(setup, dependencies?)

状态获取与传递

useContext

useContext 用于接收祖先组件的 context 传递的信息。

jsx 复制代码
/**
 * @param { context } context:context 容器
 * @return { any } value:祖先组件传递的 context
 */
const value = useContext(context)

示例:

jsx 复制代码
// 1、创建 context 容器
const NumberContext = createContext(null);

// 2、祖先组件定义 Provider
function GrandFather() {
  return (
    <NumberContext.Provider value={1}>
      <Father />
    </NumberContext.Provider>
  );
}

// 3、父组件中使用子组件
function Father() {
  return (
    <Son></Son>
  )
}

// 4、子组件中通过 useContext 接收祖先组件传递的数据
function Son() {
  const number = useContext(NumberContext);
}

useContext() 总是在调用它的组件 上面 寻找最近的 provider。它向上搜索,不考虑 调用 useContext() 的组件中的 provider。

useRef

useRef 可以存储一个不需要渲染的值。

与 state 的区别:ref 的改变不会渲染,state 会

与普通对象的区别:ref 的值不会重置,普通对象会

jsx 复制代码
/**
 * @param { any } initValue:初始值
 * @return { ref } ref:只有 current 属性的 ref 对象
 */
const ref = useRef(initValue)

示例:

jsx 复制代码
function Demo() {
  const divRef = useRef(null)
  return (
    <div ref={divRef}>ref节点</div>
  )
}

不要在 渲染期间 写入或者读取 ref.current。

可以在 事件处理程序或者 effects 中读取和写入 ref。

useImperativeHandle

useImperativeHandle 配合 forwardRef 将子组件的 ref 传递给父组件。

jsx 复制代码
/**
 * @param { ref } ref:forWardRef 渲染函数中获得的第二个参数
 * @param { function } createHandle:处理函数,返回值作为暴露给父组件的 ref 对象
 * @param { array } dependencies:依赖项
 */
useImperativeHandle(ref, createHandle, dependencies?)

示例:

jsx 复制代码
// 子组件
const Son = forwardRef((props, ref) => {
  const divRef = useRef(null)
  useImperativeHandle(ref, () => {
    return {
      sendData: () => {
        console.log(divRef.current)
      }
    }
  }, [])
  return <div ref={divRef}>子组件</div>
})
jsx 复制代码
// 父组件
function Parent() {
  const sonRef = useRef(null)
  return <Son ref={sonRef}></Son>
}

状态派生与保存

useMemo

useMemo 在每次重新渲染的时候能够缓存计算的结果。

jsx 复制代码
/**
 * @param { function } calculateValue:要缓存计算值的函数,返回值作为缓存值
 * @param { array } dependencies:依赖项数组
 * @return { any } cachedValue:缓存值(calculateValue 返回的值)
 */
const cachedValue = useMemo(calculateValue, dependencies)
  • cachedValue 初次为不带参数调用 calculateValue 的返回值
  • 后续如果依赖项没有变,就返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果

注意:应该仅仅把 useMemo 作为性能优化的手段

useCallback

useCallback 允许在多次渲染中缓存函数。

jsx 复制代码
/**
 * @param { function } fn:想要缓存的函数
 * @param { array } dependencies:依赖项数组
 * @return { function } cachedFn:缓存的函数
 */
const cachedFn = useCallback(fn, dependencies)
  • 在初次渲染时,useCallback 返回你已经传入的 fn 函数
  • 在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn。

注意:

  1. 不应在循环或者条件语句中调用 useCallback
  2. 应该仅仅把 useMemo 作为性能优化的手段

工具 hooks

useId

useId 可以生成传递给无障碍属性的唯一 ID。

jsx 复制代码
/**
 * @return { string } id:唯一的字符串 ID
 */
const id = useId()

注意:不要使用 useId 来生成列表中的 key。

useDebugValue

useDebugValue 可以在 React 开发工具 中为自定义 Hook 添加标签。

jsx 复制代码
/**
 * @param { any } value:在 React 开发工具中显示的值
 * @param { function } format:如果传入,则值为将 value 作为参数调用 format 返回的值;否则值为 value
 */
useDebugValue(value, format?)
相关推荐
半个落月3 小时前
LangSmith 全链路观测(下):用 Dataset 与 LLM Judge 量化评估 RA
javascript·langchain
半个落月3 小时前
LangSmith 全链路观测(上):用 LangGraph 与 Milvus 构建可追踪的 RAG 客服
javascript·langchain
Java后端的Ai之路4 小时前
06-React-Props
前端·react.js·前端框架
半个落月4 小时前
LangChain.js Agent 中间件实战:生命周期、动态工具与文件权限
javascript·langchain
Flynt4 小时前
开源"间谍卫星"说数据全是真的,我把它的免key数据源挨个试了一遍
前端·开源·数据可视化
Csvn5 小时前
渲染管线
前端
程序员大阳5 小时前
副队长CSS教程(33)–圆角与透明
前端·css·透明·圆角
志尊宝5 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
鬼手点金6 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉6 小时前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化