一 前言
React hooks是react16.8 以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。本章节笔者将介绍目前 React 提供的所有 hooks ,介绍其功能类型和基本使用方法。
创作不易,希望屏幕前的你能给笔者赏个赞,以此鼓励我继续创作前端硬文。🌹🌹🌹
二 hooks 之状态派生与保存
2.1 useMemo
useMemo 可以在函数组件 render 上下文中同步执行一个函数逻辑,这个函数的返回值可以作为一个新的状态缓存起来。那么这个 hooks 的作用就显而易见了:
场景一:在一些场景下,需要在函数组件中进行大量的逻辑计算,那么我们不期望每一次函数组件渲染都执行这些复杂的计算逻辑,所以就需要在 useMemo 的回调函数中执行这些逻辑,然后把得到的产物(计算结果)缓存起来就可以了。
场景二:React 在整个更新流程中,diff 起到了决定性的作用,比如 Context 中的 provider 通过 diff value 来判断是否更新
useMemo 基础介绍:
const cacheSomething = useMemo(create,deps)
- ① create:第一个参数为一个函数,函数的返回值作为缓存值,如上 demo 中把 Children 对应的 element 对象,缓存起来。
- ② deps: 第二个参数为一个数组,存放当前 useMemo 的依赖项,在函数组件下一次执行的时候,会对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。
- ③ cacheSomething:返回值,执行 create 的返回值。如果 deps 中有依赖项改变,返回的重新执行 create 产生的值,否则取上一次缓存值。
useMemo 基础用法:
派生新状态:
function Scope() {
const keeper = useKeep()
const {
cacheDispatch, cacheList, hasAliveStatus } = keeper
/* 通过 useMemo 得到派生出来的新状态 contextValue */
const contextValue = useMemo(() => {
return {
cacheDispatch: cacheDispatch.bind(keeper),
hasAliveStatus: hasAliveStatus.bind(keeper),
cacheDestory: (payload) => cacheDispatch.call(keeper, {
type: ACTION_DESTORY, payload })
}
}, [keeper])
return <KeepaliveContext.Provider value={
contextValue}>
</KeepaliveContext.Provider>
}
如上通过 useMemo 得到派生出来的新状态 contextValue ,只有 keeper 变化的时候,才改变 Provider 的 value 。
缓存计算结果:
function Scope(){
const style = useMemo(()=>{
let computedStyle = {
}
// 经过大量的计算
return computedStyle
},[])
return <div style={
style} ></div>
}
缓存组件,减少子组件 rerender 次数:
function Scope ({
children }){
const renderChild = useMemo(()=>{
children() },[ children ])
return <div>{
renderChild } </div>
}
2.2 useCallback
useCallback 基础介绍:
useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果,useCallback 返回的是函数,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的 props 函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过 usecallback 来处理此函数,然后作为 props 传递给子组件。
useCallback 基础用法:
/* 用react.memo */
const DemoChildren = React.memo((props)=>{
/* 只有初始化的时候打印了 子组件更新 */
console.log('子组件更新')
useEffect(()=>{
props.getInfo('子组件')
},[])
return <div>子组件</div>
})
const DemoUseCallback=({
id })=>{
const [number, setNumber] = useState(1)
/* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
经过处理赋值给 getInfo */
const getInfo = useCallback((sonName)=>{
console.log(sonName)
},[id])
return <div>
{
/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
<button onClick={
()=>setNumber(number+1) } >增加</button>
<DemoChildren getInfo={
getInfo} />
</div>
}
三 hooks 之工具 hooks
3.1 useDebugValue
我们不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。在某些情况下,格式化值的显示可能是一项开销很大的操作。除非需要检查 Hook,否则没有必要这么做。因此,useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。
useDebugValue 基础介绍:
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。这个hooks目的就是检查自定义hooks。
useDebugValue 基本使用:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
3.2 useId
useID 基础介绍:
useId 也是 React v18 产生的新的 hooks , 它可以在 client 和 server 生成唯一的 id , 解决了在服务器渲染中,服务端和客户端产生 id 不一致的问题,更重要的是保障了 React v18 中 streaming renderer (流式渲染) 中 id 的稳定性。
低版本 React ssr 存在的问题:
比如在一些项目或者是开源库中用 Math.random() 作为 ID 的时候,可以会有一些随机生成 id 的场景:
const rid = Math.random() + '_id_' /* 生成一个随机id */
function Demo (){
// 使用 rid
return <div id={
rid} ></div>
}
这在纯客户端渲染中没有问题,但是在服务端渲染的时候,传统模式下需要走如下流程:
在这个过程中,当服务端渲染到 html 和 hydrate 过程分别在服务端和客户端进行,但是会走两遍 id 的生成流程,这样就会造成 id不一致的情况发生。useId 的出现能有效的解决这个问题。
useId 基本用法:
function Demo (){
const rid = useId() // 生成稳定的 id
return <div id={
rid} ></div>
}
v18 ssr
在 React v18 中 对 ssr 增加了流式渲染的特性 New Suspense SSR Architecture in React 18 , 那么这个特性是什么呢?我们来看一下:
在传统 React ssr 中,如果正常情况下, hydrate 过程如下所示:
刚开始的时候,因为服务端渲染,只会渲染 html 结构,此时还没注入 js 逻辑,所以我们把它用灰色不能交互的模块表示。(如上灰色的模块不能做用户交互,比如点击事件之类的。)
hydrate js 加载之后,此时的模块可以正常交互,所以用绿色的模块展示。
但是如果其中一个模块,服务端请求数据,数据量比较大,耗费时间长,我们不期望在服务端完全形成 html 之后在渲染,那么 React 18 给了一个新的可能性。可以使用 包装页面的一部分,然后让这一部分的内容先挂起。
接下来会通过 script 加载 js 的方式 流式注入 html 代码的片段,来补充整个页面。接下来的流程如下所示:
在这个原理基础之上, React 个特性叫 Selective Hydration,可以根据用户交互改变 hydrate 的顺序。
比如有两个模块都是通过 Suspense 挂起的,当两个模块发生交互逻辑时,会根据交互来选择性地改变 hydrate 的顺序。
如上 C D 选择性的 hydrate 就是 Selective Hydration 的结果。那么回到主角 useId 上,如果在 hydrate 过程中,C D 模块 id 是动态生成的,比如如下:
let id = 0
function makeId(){
return id++
}
function Demo(){
const id = useRef( makeId() )
return <div id={
id} >...</div>
}
那么如果组件是 Selective Hydration , 那么注册组件的顺序服务端和客户端有可能不统一,这样表现就会不一致了。那么用 useId 动态生成 id 就不会有这个问题产生了,所以说 useId 保障了 React v18 中 streaming renderer (流式渲染) 中 id 的稳定性。
四 总结
本文详细介绍了 React Hooks 产生初衷以及 React Hooks,希望看到这篇文章的同学,可以记住每一个 hooks 的使用场景,在项目中熟练使用起来。