第二期:继续React Hooks之旅
hello,我是海海
这一期我们继续
React Hooks
之旅。阅读时间10~15分钟欢迎转载,请注明原文和作者
有任何疑惑的地方,欢迎后台留言
本期大纲:
useTransition
和useDeferredValue
useRef
/useImperativeHandle
/React.forwardRef
useId
和useSyncExternalStore
useDebugValue
- 补充干货:
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 排版
转载自:https://juejin.cn/post/7256047142539477048