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?)
相关推荐
GIS程序媛—椰子24 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00130 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端33 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x36 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100937 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习