likes
comments
collection
share

第二期:继续React Hooks之旅

作者站长头像
站长
· 阅读数 20

hello,我是海海

这一期我们继续React Hooks之旅。阅读时间10~15分钟

欢迎转载,请注明原文和作者

有任何疑惑的地方,欢迎后台留言

本期大纲:

  • useTransitionuseDeferredValue
  • useRef/useImperativeHandle/React.forwardRef
  • useIduseSyncExternalStore
  • useDebugValue
  • 补充干货:hooks的使用条件?

第二期:继续React Hooks之旅

useTransitionuseDeferredValue

useTransition:状态变更不会阻塞UI

useDeferredValue:如果关联的值更新,它也将被更新,否则使用旧值(类似防抖机制,但与防抖有所不同)

  • useTransition

Index组件

import { useState, useTransition } from 'react'
import TabType from './TabType'
const Index = () => {
  const [tabType, setTabType] = useState('A')
  const [isPending, startTransition] = useTransition()
  // isPending表示任务的状态是完成还是进行中
  // startTransition是一个任务
  const selectTabType = (type) => {
    startTransition(() => setTabType(type))
  }
  // 在TabType组件渲染时间太长的情况下,如果不使用startTransition
  // 用户在点击按钮切换时,页面将等待,严重影响体验
  return (
    <>
      <div>
        <button onClick={() => selectTabType('A')}>A</button>
        <button onClick={() => selectTabType('B')}>B</button>
      </div>
      <TabType value={tabType} />
    </>
  )
}

TabType组件

const TabType = (props) => {
  return (
    <div> 当前值是:{props.value}</div>
  )
}

备注:

  • startTransition只接受同步任务
  • suspense无嵌套的情况下可以使用startTransiton代替(因为前者的fallback会替换无关的组件渲染,而后者只替换受影响的部分)
  • startTransition+suspense,构建可中断的路由
  • inputonChange不可使用startTransition
  • useDeferredValue
import { useState, useDeferredValue, useEffect } from 'react'
const Index = (props) => {
  const [value, setValue] = useState('')
  const deferredValue = useDeferredValue(value)
  useEffect(() => {
    getInfo().then(res) => {
      setValue(res)
    })
  }, [props.v]
  return (
    <div>{deferredValue}</div>
    // 这里使用deferredValue,在getInfo拿到值之前,会显示旧值;否则显示新值
    // 只调用一次的情况下,看起来,这和直接用useState没什么区别!
    // 如果在一段时间内多次调用,那么每次调用时,都会放弃之前的值并重新替换,这就是它的用途!
  )
}

与防抖的不同点:

  • useDeferredValue没有固定延时,而防抖会有
  • useDeferredValue内部的实现是渲染可中断,而防抖则是推迟渲染

第二期:继续React Hooks之旅

useRef/useImperativeHandle/React.forwardRef

useRef:可以存储与渲染无关的值(ahooks用到了这个特性);也可以用来操作dom;还可以用来缓存创建开销大的对象

useImperativeHandle:暴露给父组件,通过ref属性可以使用的属性/方法集合

React.forwardRef:使用ref属性对父组件暴露dom

接下来,我将从组件间通信的场景,介绍他们

父组件

import { useRef, useEffect } from 'react'
import Son from './Son'
const Father = (props) => {
  const ref = useRef()
  useEffect(() => {
    if (props.value) {
      ref.current.handleClick()
else {
      ref.current.inputFocus()
    }
  }, [props.value])
  return (
    <Son ref={ref} />
  )
}

子组件

import { useImperativeHandle, useRef } from 'react'
const Son = React.forwarRef((props, ref) => {
  const inputRef = useRef(null)
  const handleClick = (e) => console.log(e.target.value
  useImperativeHandle(ref, () => {
    return {
      handleClick: handleClick,
      // 用法一:这是一种比较常见的用法,对父组件暴露子组件定义的方法
      inputFocus() => inputRef.current.focus()
      // 用法二:我认为是一种比较灵活的用法。
      // 这是官方文档的实例,对父组件暴露子组件的子元素的原生方法。
      // 如果继续“递归”下去,可以把孙组件的事件也暴露出去...
    }
  }, [])
  // 依赖数组,通过`Object.is`进行浅比较
  return (
    <div>
      <span onClick={handleClick}>海海</span>
      <input ref={inputRef} type="text" />
    </div>
  )
})

第二期:继续React Hooks之旅

useIduseSyncExternalStore

useId用于在可访问性的html属性生成唯一id

useSyncExternalStore用于第三方状态管理工具。

我相信大部分人应该也没用过,但它们其实在一些场景中非常好用。

  • useId

慢着,啥是可访问性属性?

这一块属于web中的无障碍知识,在这里特指aria-xxx的一系列属性(h5的api)。为了在html4中兼容,除了要编写这些aria属性,还要手动模拟和浏览器的交互行为。

额,服务端渲染可以用它做什么?

由于笔者对服务端渲染的了解不深。对官网这一块的解释很难理解。如果看到这里,可以跳过这一段,或者你也可以选择和我继续探索!

在服务端渲染中,有几个步骤。第一个步骤是serverhtml发送到client;第二个步骤是client通过Hydration技术,使静态页面变成可与用户交互的页面。

在第二步中,会对dom元素绑定事件处理程序。由于clientserverHydration的顺序可能不同,为了使事件处理程序绑定在两个端保持一致,useId相比递增计数器更好,因为useId只要调用它的组件的父组件一致,就会保持一致。

OK。我们来看useId的实例。

页面中有多个重复表单时,可以使用useId进行标记。这样,在键盘tab时,就可以精准聚焦每个input元素

import { useId } from 'react'
const MyForm = () => {
  const id = useId()
  return (
    <div>
      <label htmlFor={id + 'username'}>username</label>
      <input id={id + 'username'} type="text" />
      <label htmlFor={id + 'password'}>password</label>
      <input id={id + 'password'} type="text" />
    </div>
  )
}
import MyForm from './MyForm'
const App = () => {
  return (
    <>
      <p>form-1</p>
      <MyForm />

      <p>form-2</p>
      <MyForm />
    </>
  )
}

备注:不要将useId用于生成React列表的key属性,这应当由数据生成。

  • useSyncExtenalStore

由于文章的篇幅所限,这里请参阅:

可能在未来的某个时候,我会和大家重新探索这个hooks

第二期:继续React Hooks之旅

useDebugValue

调试自定义hook之利器

这个hook比较简单,我们看一个例子即可:

import { useState, useDebugValue } from 'react'
const useCount = (props) => {
  const [count, setCount] = useState(props.value % 2)
  useDebugValue(count, (count) => count !== 0 ? '非零' : '零')
  // 第一个参数,可以是任何类型的`value`值,用于调试时打印
  // 第二个可选参数,是一个格式化函数,接收`value`并格式化输出,没有此参数默认返回`value`
  return [count]
} 

以下是需要强调的点:

  • 不要滥用。在含有复杂数据结构的(难以调试的情况下)、被多模块公用的hook才是值得使用的
  • 善用第二个参数,提升运行性能。第二个参数是一个格式化参数,只有在组件(使用这个自定义hook)被检查时(断点检查),React DevTools插件才会调用此函数并打印结果。简而言之,就是延迟计算

第二期:继续React Hooks之旅

补充干货:hooks的使用条件?

hooks的使用条件也是常见的React面试题之一

参考:react.dev/warnings/in…

  • 最好 use开头
  • 必须FC组件/自定义hooks顶层调用,别在return之后调用
  • 不得 在循环、条件、嵌套函数、事件处理程序中使用
  • 不得 在类组件中使用
  • 不得useMemouseReduceruseEffect中使用。(虽然官方文档没有明说,但是useCallback应该也不行)

备注:可以使用eslint-plugin-react-hooks检测hooks是否被正确使用

进阶:其实,这一部分官方文档只是说了Hooks自己的规则。此外,还有两种情况会导致Hooks的使用出错:

  • 情况一:不支持Hooks版本的ReactNative (< 0.59) 或React DOM (< 16.8.0)。
  • 情况二:项目中有多个React版本。比如,项目依赖React版本v1,依赖的React-dom版本v2,这会造成无法使用hooks的情况。前者提供核心功能,后者提供和浏览器的交互功能,因此必须保持一致。
# 可以查看依赖关系,判断是否`React`版本是否一致
npm ls react

感谢你的耐心阅读,如果觉得好的话,可以给我点个赞吗

第二期:继续React Hooks之旅

创作不易,感谢你的支持!

本文使用 markdown.com.cn 排版