hello,我是海海
这一期我们继续
React Hooks之旅。阅读时间10~15分钟欢迎转载,请注明原文和作者
有任何疑惑的地方,欢迎后台留言
本期大纲:
useTransition和useDeferredValueuseRef/useImperativeHandle/React.forwardRefuseId和useSyncExternalStoreuseDebugValue- 补充干货:
hooks的使用条件?
useTransition和useDeferredValue
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,构建可中断的路由input的onChange不可使用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内部的实现是渲染可中断,而防抖则是推迟渲染
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>
)
})
useId和useSyncExternalStore
useId用于在可访问性的html属性生成唯一id。
useSyncExternalStore用于第三方状态管理工具。我相信大部分人应该也没用过,但它们其实在一些场景中非常好用。
useId
慢着,啥是可访问性属性?
这一块属于web中的无障碍知识,在这里特指aria-xxx的一系列属性(h5的api)。为了在html4中兼容,除了要编写这些aria属性,还要手动模拟和浏览器的交互行为。
额,服务端渲染可以用它做什么?
由于笔者对服务端渲染的了解不深。对官网这一块的解释很难理解。如果看到这里,可以跳过这一段,或者你也可以选择和我继续探索!
在服务端渲染中,有几个步骤。第一个步骤是server将html发送到client;第二个步骤是client通过Hydration技术,使静态页面变成可与用户交互的页面。
在第二步中,会对dom元素绑定事件处理程序。由于client和server在Hydration的顺序可能不同,为了使事件处理程序绑定在两个端保持一致,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
由于文章的篇幅所限,这里请参阅:
- 你所使用的第三方状态管理工具
- react.dev/reference/r…
可能在未来的某个时候,我会和大家重新探索这个
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插件才会调用此函数并打印结果。简而言之,就是延迟计算。
补充干货:hooks的使用条件?
hooks的使用条件也是常见的React面试题之一
- 最好
use开头 - 必须 在
FC组件/自定义hooks顶层调用,别在return之后调用 - 不得 在循环、条件、嵌套函数、事件处理程序中使用
- 不得 在类组件中使用
- 不得 在
useMemo、useReducer、useEffect中使用。(虽然官方文档没有明说,但是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
感谢你的耐心阅读,如果觉得好的话,可以给我点个赞吗
创作不易,感谢你的支持!
这是我的微信公众号,欢迎和我一起玩前端
本文使用 markdown.com.cn 排版