React Hook: 高级 Hook API

news/2024/7/19 14:18:38 标签: react, js, hooks, api

React Hook: 高级 Hook API

文章目录

  • React Hook: 高级 Hook API
  • 前言
  • 正文
    • 1. useReducer
    • 2. useMemo
    • 3. useCallback
    • 4. useRef
    • 5. useImperativeHandle
    • 6. useLayoutEffect
    • 7. useDebugValue
  • 结语
  • 其他资源
    • 参考连接
    • 完整代码示例

前言

继前一篇 - React 升级: Hook API 基础,介绍了三个基本的 Hook:useState、useEffect、useContext

今天要来介绍更多跟多延伸的高级 Hook API

正文

1. useReducer

第一个有点像是 redux 的简易版,原来的 useState 是这样用的

const [state, setState] = useState(initState)

useReducer 则是类似 redux 的提交方式来维护状态,适合比较复杂的状态更新逻辑

首先我们先定义好一个简单的 reducer

import { useReducer, useState } from 'react'

type TimerAction =
  | { type: 'INCREMENT' }
  | { type: 'RESET' }
  | { type: '' }

const timerReducer = (count: number, action: TimerAction) => {
  switch (action.type) {
    case 'INCREMENT':
      return count + 1
    case 'RESET':
      return 0
    default:
      return count
  }
}

接下来将与 reducer 相关的逻辑封装成一个自定义 Hook

export default function useTimer() {
  const [count, dispatch] = useReducer(timerReducer, 0)

  const increment = () => dispatch({ type: 'INCREMENT' })

  const reset = () => dispatch({ type: 'RESET' })

  return { count, increment, reset }
}
  • /src/tests/TestUseReducer.tsx

最后组件内部则可以无感知的使用改状态

import React from 'react'
import useTimer from '../hooks/useTimer'

const TestUseReducer = () => {
  const { count, increment, reset } = useTimer()
  return (
    <div>
      <h2>useReducer</h2>
      <h3>count: {count}</h3>
      <div>
        <button onClick={increment}>increment</button>
        <button onClick={reset}>reset</button>
      </div>
    </div>
  )
}

export default TestUseReducer

2. useMemo

第二个比较像是一种优化手段,根据依赖数组值来决定需不需要重新计算,因此尽量不要在回调函数内部体现顺序强相关的逻辑,也不可以依赖该函数的调用时机

首先我们稍微封装以下输入控件要用的钩子

import { ChangeEvent, ChangeEventHandler, useState } from 'react'

export default function useInput(
  initValue: string = ''
): [string, ChangeEventHandler<HTMLInputElement>] {
  const [value, setValue] = useState(initValue)

  const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
    setValue(e.target.value)

  return [value, handleChange]
}
  • /src/tests/TestUseMemo.tsx

接下来为导出量 z 调用一个缓存钩子,并依赖于 x、y 值的变化而更新

import React, { ChangeEventHandler, useMemo } from 'react'
import useInput from '../hooks/useInput'

export interface RowProps {
  label: string
  value: string
  onChange?: ChangeEventHandler<HTMLInputElement>
  disabled?: boolean
}

export const Row = (props: RowProps) => {
  const { label, ...rest } = props
  return (
    <div>
      <label>
        {label} <input type="text" {...rest} />
      </label>
    </div>
  )
}

const TestUseMemo = () => {
  const [x, changeX] = useInput()
  const [y, changeY] = useInput()

  const z = useMemo(() => {
    console.log(`recalculate concatXY = ${x + y}`)
    return x + y
  }, [x, y])

  return (
    <div>
      <h2>useCallback</h2>
      <Row label={'x:'} value={x} onChange={changeX} />
      <Row label={'y:'} value={y} onChange={changeY} />
      <Row label={'z:'} value={z} disabled />
    </div>
  )
}

export default TestUseMemo

3. useCallback

第三个与 useMemo 有点像,不过不一样的是 useMemo 是缓存回调结果,而 useCallback 则是缓存回调函数本身,可能可以使函数进行重新绑定相关变量

  • /src/tests/TestUseCallback.tsx
import React, { useCallback } from 'react'
import useInput from '../hooks/useInput'
import { Row } from './TestUseMemo'

const TestUseCallback = () => {
  const [x, changeX] = useInput()
  const [y, changeY] = useInput()

  const concatXY = useCallback(() => {
    console.log(`concatXY = ${x + y}`)
    return x + y
  }, [x, y])

  return (
    <div>
      <h2>useCallback</h2>
      <Row label={'x:'} value={x} onChange={changeX} />
      <Row label={'y:'} value={y} onChange={changeY} />
      <Row label={'z:'} value={concatXY()} disabled />
    </div>
  )
}

export default TestUseCallback

4. useRef

第四个其实在第一篇已经略微提过了,就是一个 Hook 版本的 React.createRef,用于创建一个 ref 的函数

  • /src/tests/TestUseCallback.tsx
import React, { useCallback, useEffect, useRef } from 'react'

const TestUseRef = () => {
  const inputRef = useRef<HTMLInputElement>()

  const handleChange = useCallback(() => {
    console.log(`value = ${inputRef.current.value}`)
  }, [inputRef])

  return (
    <div>
      <h2>useRef</h2>
      <input type="text" ref={inputRef} onChange={handleChange} />
    </div>
  )
}

export default TestUseRef

5. useImperativeHandle

接下来这一个也是与 ref 有关的,我们知道上层组件想要传递 ref 给子组件的时候可以使用 React.forwardRef 来传递给函数组件,也可以透过别名的方式来传递 ref,甚至可以通过 render props 或 children 等方式直接将 ref 标记到目标标签上

然而不论是哪一种似乎都对多 ref 的支持不友好,甚至我们没办法定制对于外部的 ref 表现,这时候我们就可以用上 useImperativeHandle 来为上级组件定制一个统一接口约定的组件

  • /src/tests/TestUseImperativeHandle.tsx

首先 useImperativeHandle 的用法大致如下,透过与 React.forwardRef 配合,将传递下来的 ref 作为第一个参数,第二个参数则是一个回调函数,返回的是对外部可见的 InnerRefElement 类型,第三个参数一样是依赖列表

interface InnerRefElement {
  hi: () => void
  value: () => void
}

interface InnerProps {
  onChange: () => void
}

const Inner = React.forwardRef((props: InnerProps, ref) => {
  const inputRef = useRef<HTMLInputElement>()

  useImperativeHandle(
    ref,
    (): InnerRefElement => ({
      hi: () => {
        console.log('say Hi')
      },
      value: () => {
        console.log(`value = ${inputRef.current.value}`)
      },
    }),
    [inputRef]
  )

  return (
    <input type="text" ref={inputRef} onChange={props.onChange} />
  )
})

接下来对于外部组件来说,就可以根据定制化的接口来使用这个 ref

const TestUseImperativeHandle = () => {
  const ref = useRef<InnerRefElement>()

  useEffect(() => {
    if (ref.current) {
      ref.current.hi()
    }
  }, [ref])

  const onChange = useCallback(() => {
    ref.current.value()
  }, [ref])

  return (
    <div>
      <h2>useImperativeHandle</h2>
      <Inner ref={ref} onChange={onChange} />
    </div>
  )
}

export default TestUseImperativeHandle

6. useLayoutEffect

useLayoutEffect 与 useEffect 是几乎一摸一样的,不过差别在于 useLayoutEffect 的调用时机是处于 DOM 渲染之前,也就是说会阻塞渲染

所以除非特别需要直接修改 DOM 的操作,否则尽量使用 useEffect 来先渲染再修改

  • /src/tests/TestUseLayoutEffect.tsx
import React, { useEffect, useLayoutEffect } from 'react'

const TestUseLayoutEffect = () => {
  useEffect(() => {
    console.log('useEffect')
  }, [])

  useLayoutEffect(() => {
    console.log('useLayoutEffect')
  }, [])

  return (
    <div>
      <h2>useLayoutEffect</h2>
    </div>
  )
}

export default TestUseLayoutEffect

7. useDebugValue

useDebugValue 比较特别的是它其实是一个开发专属的 Hook 负责为开发时在开发者工具内提供 Hook 的特定标签

  • /src/hooks/useNothing.tsx

首先我们写一个自定义 Hook 并使用 useDebugValue 打上标签

import { useDebugValue } from 'react'

export default function useNothing(bool: boolean) {
  useDebugValue(bool ? 'Online' : 'Offline')
}
  • /src/tests/TestUseDebugValue.tsx

然后我们在组件内用一下,就可以在开发者工具中看到效果了

import React, { useState } from 'react'
import useNothing from '../hooks/useNothing'

const TestUseDebugValue = () => {
  const [state, setState] = useState(false)

  useNothing(state)

  return (
    <div>
      <h2>useDebugValue</h2>
      <button onClick={() => setState(!state)}>Toggle</button>
    </div>
  )
}

export default TestUseDebugValue

结语

其他资源

参考连接

TitleLink
Hook API 索引 - React 官方https://react.docschina.org/docs/hooks-reference.html

完整代码示例

https://github.com/superfreeeee/Blog-code/tree/main/front_end/react/react_hook_advanced


http://www.niftyadmin.cn/n/735252.html

相关文章

从源码角度分析Activity、Window、View的关系

View依附于Window&#xff0c;而Activity负责管理Window。为什么会产生这样的关系呢&#xff1f;文章围绕这个问题。将会从Activity加载View的整个流程去分析Activity、Window、View三者之间的关系。 1、在启动Activity时&#xff0c;会在onCreate()方法中调用setContentView()…

Git 实战: 利用 rebase 让你的提交/合并记录更清晰

Git 实战: 利用 rebase 让你的提交/合并记录更清晰 文章目录Git 实战: 利用 rebase 让你的提交/合并记录更清晰前言正文实验一&#xff1a;普通 merge1.1 提交记录1.2 图解说明实验二&#xff1a;rebase 应用之一 - 合并记录2.1 提交记录2.2 图解说明实验三&#xff1a;rebase …

Less 教程

1. Less 基本教程 1.1 Less 入门简介 1.1.1 什么是LESS&#xff1f; CSS&#xff08;层叠样式表&#xff09;是一门历史悠久的标记性语言&#xff0c;同 HTML 一道&#xff0c;被广泛应用于万维网&#xff08;World Wide Web&#xff09;中。HTML 主要负责文档结构的定义&#…

Git 实战: 利用 stash 保存当前未完成工作

Git 实战: 利用 stash 保存当前未完成工作 文章目录Git 实战: 利用 stash 保存当前未完成工作前言场景正文1. 使用 stash 保存未完成工作2. 恢复修改3. 在不同 commit 恢复修改4. 恢复记录产生冲突4.1 冲突后记录未消失5. 总结结语其他资源参考连接完整代码示例前言 场景 在我…

前端工作流: 自动化 Code Lint 使你的项目代码更规范

前端工作流: 自动化 Code Lint 使你的项目代码更规范 文章目录前端工作流: 自动化 Code Lint 使你的项目代码更规范前言正文0. 使用工具项介绍1. Prettier 代码格式化2. Commitlint 提交信息校验3. ESLint 代码校验(可以覆盖TS&#xff01;)4. Stylelint 样式表校验5. Husky 接…

SSH框架搭建基础入门

2019独角兽企业重金招聘Python工程师标准>>> 1.首先先创建一个动态web项目。 2.一路点击next最后记得勾选创建创建xml文档选项。 3.创建完成之后首先添加本次项目所需要的jar包。为避免在找jar过程中的麻烦&#xff0c;我已经所有jar包上传至百度网盘。 链接&#x…

JS 实战: 一文了解 5 种文件上传场景(React + Koa 实现)

JS 实战: 一文了解 5 种文件上传场景(React Koa 实现) 文章目录JS 实战: 一文了解 5 种文件上传场景(React Koa 实现)前言正文1. 单文件上传2. 多文件上传3. 按目录多文件上传4. 多文件合成压缩包上传5. 大文件分块上传结语其他资源参考连接完整代码示例前言 今天来跟大家分…

React 优化: 到底怎么用 useCallback 才是正确的?

React 优化: 到底怎么用 useCallback 才是正确的&#xff1f; 文章目录React 优化: 到底怎么用 useCallback 才是正确的&#xff1f;前言正文0. 环境准备1. 普通场景2. 存在子组件3. 没有子组件的情况下使用 useCallback4. 存在子组件的情况下使用 useCallbackReact 组件概念 &…