
Gutenberg 深入解析wordpress/compose 的 usePrevious Hook——在 React 渲染间追踪上一次的值【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenbergusePrevious 是 Gutenberg 的wordpress/compose工具包中用于在 React 组件中获取上一次渲染时某值的 Hook广泛服务于块编辑器Block Editor中需要比较前后状态变化的场景例如自动保存状态监测、网格布局同步与插入器分页。阅读本文后你将掌握usePrevious的 API 签名、基于 ref 与 effect 的底层实现原理、首次渲染的特殊行为以及它在 Gutenberg 源码中的真实调用模式与常见使用陷阱。背景为什么需要上一次渲染的值React 的每次渲染都是独立的。组件状态myNumber在本次渲染中的值与上一次渲染时的值是两个互不相关的快照React 不会自动帮你保留历史值。然而在真实业务中大量需求都依赖前后对比数值增加还是减少方向判断属性从未定义变为有值首次触发选中项从 A 变为 B去重或联动保存状态从isSaving: true变为false触发已保存反馈。在没有usePrevious之前开发者通常需要手动引入一个 ref在useEffect中滞后一步更新它以缓存上一次的值。这正是usePrevious存在的原因它把这段样板代码封装成一个可复用的 Hook让组件可以在本次渲染时同步拿到上一次渲染的值。usePrevious 的 API 与基础用法Gutenberg 官方文档packages/compose/src/hooks/use-previous/README.md给出了非常简洁的定义usePrevious通过一个 ref 追踪你传入的值并返回上一次渲染时的值。该 Hook 从wordpress/compose包导出可与 React 的useState、useEffect组合使用。以下是官方文档中的完整示例import { useEffect, useState } from react; import { usePrevious } from wordpress/compose; function MyCustomElement() { const [ myNumber, setMyNumber ] useState( 5 ); const [ lastChange, setLastChange ] useState( none ); const prevNumber usePrevious( myNumber ); useEffect( () { // On the first render, prevNumber will be undefined. if ( prevNumber ! undefined ) { if ( myNumber prevNumber ) { setLastChange( up ); } else if ( myNumber prevNumber ) { setLastChange( down ); } } }, [ myNumber ] ); return ( p My number is { myNumber }. Last change: { lastChange } /p ); }这个例子的运行逻辑可以拆解为四步初始渲染时myNumber 5usePrevious( 5 )返回undefined因为还没有上一次渲染用户操作导致myNumber变化例如变为8组件重新渲染本次渲染中usePrevious( 8 )返回上一次渲染时的5useEffect中比较8 5将lastChange置为up页面展示My number is 8. Last change: up。prevNumber ! undefined这一判断非常重要它确保首次渲染没有历史值时不会把undefined误判为一次有效变化。源码剖析一个 ref 加一个 effect 的全部秘密usePrevious的实现异常精简完整源码位于 packages/compose/src/hooks/use-previous/index.tsimport { useEffect, useRef } from wordpress/element; /** * Use somethings value from the previous render. * Based on https://usehooks.com/usePrevious/. * * param value The value to track. * * return The value from the previous render. */ export default function usePrevious T ( value: T ): T | undefined { const ref useRef T ( undefined ); // Store current value in ref. useEffect( () { ref.current value; }, [ value ] ); // Re-run when value changes. // Return previous value (happens before update in useEffect above). return ref.current; }这段 20 行左右的代码背后有三个值得深挖的要点1. 利用 ref 的跨渲染持久性。useRef返回的引用对象在整个组件生命周期内保持稳定ref.current的修改不会触发重新渲染。因此它可以充当上一次值的存储仓库而又不破坏 React 的渲染循环。2. 渲染时序决定了先读旧值、后写新值。这是整个 Hook 最精妙的地方组件渲染时usePrevious直接返回ref.current此刻还是上一次渲染时写入的旧值渲染提交之后React 才执行useEffect把本次的value写入ref.current。也就是说读取发生在写入之前天然形成了本次渲染返回上一渲染的值的语义。源码注释Return previous value (happens before update in useEffect above)明确点出了这一时序。3. 依赖数组[ value ]保证精准更新。只有当被跟踪的value发生变化时effect 才会重新执行并更新 ref。如果依赖被省略effect 会在每次渲染后都执行虽然多数情况下结果相同但显式声明依赖更符合 React 的 lint 规范也让意图更清晰。此外源码注释还标注了该实现参考了 usehooks.com 的 usePrevious 模式属于社区成熟方案的官方封装。类型签名usePreviousT(value: T): T | undefined用泛型保证了任意类型基本类型、对象、函数引用都能被跟踪返回值类型中的| undefined则准确反映了首次渲染没有历史值的现实。首次渲染行为与边界情况usePrevious的边界行为可以直接从 index.ts 的初始化语句useRefT( undefined )推导出来场景返回值说明组件首次渲染undefinedref 初始值为 undefinedeffect 尚未执行值发生变化后的渲染上一次渲染时的值effect 已把旧值写入 ref值连续两次渲染相同上一次即相同的值依赖数组[ value ]未变化ref 不被更新值从某值变为undefined再变化逐次保留上一渲染值与跟踪普通值无差异其中第一条首次渲染返回undefined是使用频率最高的注意点。官方示例中通过prevNumber ! undefined显式跳过首次比较这正是因为在第一次渲染时根本没有可比较的历史基准。如果你的业务逻辑把undefined当作有效值例如跟踪一个本来就可能为undefined的属性需要额外设计区分策略例如先初始化一个哨兵值。另一个容易忽略的细节是usePrevious比较的是引用reference而非深比较。对于对象类型只要每次渲染传入的是新对象effect 都会因引用变化而更新 ref——这符合 React 依赖比较的惯例但也意味着它不适合直接用于比较对象内容是否相等的场景后者应当配合深比较工具使用。在 Gutenberg 源码中的真实应用usePrevious并非文档中的孤立示例而是被 Gutenberg 编辑器自身大量使用的核心工具。它在 packages/compose/src/index.js 第 40 行通过export { default as usePrevious } from ./hooks/use-previous;对外导出并与useDebounce、useInstanceId、useViewportMatch等 Hook 并列。以下是几个有代表性的调用点。网格布局同步use-grid-layout-sync.js在 packages/block-editor/src/components/grid/use-grid-layout-sync.js 中Gutenberg 的 Grid 块需要感知所选区块的矩形位置、“是否手动放置”、“区块顺序”在前后渲染间的变化从而决定如何自动调整网格布局const previouslySelectedBlockRect usePrevious( selectedBlockRect ); const previousIsManualPlacement usePrevious( gridLayout.isManualPlacement ); const previousBlockOrder usePrevious( blockOrder );这是一个典型场景在 effect 中拿当前值与上一次的值做差量比较从而只在真正的变化发生时触发布局计算避免每次渲染都无谓地重算。保存状态反馈post-saved-state/index.jsxpackages/editor/src/components/post-saved-state/index.jsx 是编辑器右上角的保存状态组件它通过usePrevious监测isSaving的跳变来触发已保存的瞬时提示动画并在 1 秒后自动消失setForceSavedMessage( false )。如果没有usePrevious组件无法得知保存流程是刚开始还是刚结束。同包衍生实现use-warn-on-change值得顺带一提的是wordpress/compose内部还有另一个基于相同思想、但专门用于值变化时告警的 HookuseWarnOnChangepackages/compose/src/hooks/use-warn-on-change/index.ts。它与usePrevious都服务于感知前后差异但用途相反一个返回旧值供业务自行比较另一个在检测到变化时直接抛出警告。这组对照可以帮助读者理解 compose 工具包在渲染历史这一主题上的完整设计。从源码看 Hook 的运行时依赖从 packages/compose/package.json 可以看出wordpress/compose当前版本为 8.8.0usePrevious只依赖wordpress/elementReact 的 WordPress 封装提供useRef/useEffect并且整个包声明了sideEffects: false便于打包器做 tree-shaking。在安装层面usePrevious属于wordpress/compose的公开导出可以直接通过包管理器安装使用npm install wordpress/compose随后通过import { usePrevious } from wordpress/compose引入。由于该包以file:../*方式在工作区中依赖其他 WordPress 包见 package.json 的dependencies在 Gutenberg monorepo 之外使用时应优先从 npm registry 安装正式发布版本以保证依赖完整性。实战基于 usePrevious 的通用模式结合官方示例与 Gutenberg 源码中的用法可以提炼出usePrevious的三个高频实战模式模式一变化方向检测这是官方示例演示的场景——通过比较新旧值判断上升 / 下降 / 不变const prev usePrevious( value ); useEffect( () { if ( prev ! undefined value ! prev ) { // value 相对上一次渲染发生了变化可在此做方向或差值计算。 } }, [ value, prev ] );模式二从未就绪到就绪的首次触发很多异步数据场景下数据先为undefined加载中加载完成后变为真实数据。借助usePrevious可以只在这一跳变发生时执行副作用const data useSelect( ( select ) select( store ).getData(), [] ); const prevData usePrevious( data ); useEffect( () { if ( prevData undefined data ! undefined ) { // 数据首次加载完成。 } }, [ data, prevData ] );模式三状态跳变沿检测例如自动保存完成Gutenberg 的post-saved-state正是这种模式只关心isSaving从true回到false的那一瞬间用于触发保存成功的反馈。const prevSaving usePrevious( isSaving ); useEffect( () { if ( prevSaving ! isSaving ) { // 保存刚结束。 } }, [ isSaving, prevSaving ] );使用注意事项小结首次渲染必为undefined不要在首次渲染时对旧值做业务判断除非你显式处理了undefined。读取先于写入usePrevious的返回发生在 effect 写入之前所以拿到的一定是上一次渲染的值而非当前值。基于引用比较对对象、数组等引用类型传入新对象即视为变化需要内容级比较时请自行深比较。配合useEffect使用拿到旧值后的副作用逻辑应放在依赖数组包含value与prev的 effect 中避免在渲染期直接执行非纯操作。在 WordPress 生态中通过wordpress/element导入与react直接导入相比wordpress/element保证了与 WordPress 运行时的一致性。总结usePrevious是 Gutenbergwordpress/compose中一个小而美的典型工具它以约 20 行的实现index.ts借助useRef的跨渲染持久性与useEffect的滞后执行时序为编辑器内部几乎所有需要前后对比的模块提供了统一、可靠的解法。从网格布局同步到保存状态反馈从官方文档示例到 index.js 的公开导出理解它的实现原理与边界行为不仅能帮助你更熟练地在 WordPress 区块开发中追踪渲染历史也能让你对 React Hook 的组合时机有更深的体会——值得作为阅读 Gutenberg 源码的入门范本反复品味。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考