十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Gutenberg `@wordpress/compose` 版本演进全解析:从函数组合到 React 19 时代的 Hooks 与 HOC 工具库

Gutenberg `@wordpress/compose` 版本演进全解析:从函数组合到 React 19 时代的 Hooks 与 HOC 工具库 Gutenbergwordpress/compose版本演进全解析从函数组合到 React 19 时代的 Hooks 与 HOC 工具库【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg本指南以 Gutenberg 仓库中 packages/compose/CHANGELOG.md 为主线系统梳理wordpress/compose包从 2018 年至今的每一次功能性变更、破坏性变更与内部重构并对照 packages/compose/src 下的真实源码与测试逐条印证其实现原理。读完本文你将理解 compose 包中compose/pipe、useDialog、useEvent、useResizeObserver、useViewportMatch、useFixedWindowList、observableMap等核心 API 的演进动机与落地实现掌握 React 18/19 迁移、SSR 兼容、焦点管理、性能优化等实战要点并能据此判断各版本升级对自身项目的潜在影响。一、包定位Gutenberg 的 React 工具百宝箱wordpress/compose是 Gutenberg 单体仓库中负责提供“可复用的 React 组件能力”的核心包。其 README.md 的定位说明指出它是一组开箱即用的 React Hooks。从包依赖结构可以清晰看到它与 Gutenberg 其它基础设施包的协作关系package.jsonwordpress/element提供useState、useEffect、useSyncExternalStore、createContext等 React 抽象层所有 Hook 均基于此实现wordpress/dom提供focus.tabbable、getScrollContainer等 DOM 能力支撑useFocusOnMount、useFixedWindowListwordpress/keycodes提供PAGEUP、PAGEDOWN、HOME、END等键位常量wordpress/deprecated为pure、withState、useCopyOnClick等被弃用 API 输出运行时警告mousetrap支撑useKeyboardShortcut的快捷键绑定wordpress/priority-queue、wordpress/undo-manager、wordpress/is-shallow-equal、wordpress/private-apis等分别服务于不同工具。包同时提供 CJSbuild/index.cjs、ESMbuild-module/index.mjs与类型声明build-types/index.d.ts三套产物声明sideEffects: false以便摇树优化。peer 依赖为react: ^18 || ^19、types/react: ^18 || ^19其中types/react为可选Node 要求18.12.0。二、函数组合核心compose与pipe2.1 设计思想Lodash flowRight / flow 的函数式移植CHANGELOG 5.16.02022-09-21记录了引入自研compose与pipe工具、废弃 Lodash 版本的变更#43943、#44112。两者均受 Lodash 启发compose等价于flowRight执行从右到左的组合pipe等价于flow执行从左到右的组合。README 给出了最直观的两函数示例const compose ( f, g ) x f( g( x ) );2.2 源码实现basePipe两者的底层是同一个basePipe工厂函数pipe.tsconst basePipe ( reverse: boolean false ) ( ...funcs: ( Function | Function[] )[] ) ( ...args: unknown[] ) { const functions funcs.flat(); if ( reverse ) { functions.reverse(); } return functions.reduce( ( prev, func ) [ func( ...prev ) ], args )[ 0 ]; };其中compose basePipe( true )compose.tspipe basePipe()。实现要点支持传入嵌套函数数组funcs.flat()摊平便于按模块分组传入通过reduce将上一个函数的返回值作为下一个函数的入参最终只返回最后一个结果文件头部保留了“派生自 LodashMIT 许可”的版权声明属合规移植。2.3 实战用法来自PluginSidebar的真实案例README 以 Gutenberg 编辑器中PluginSidebarMoreMenuItem组件为例展示compose的价值。使用compose的写法const applyWithSelect withSelect( ( select, ownProps ) { return doSomething( select, ownProps ); } ); const applyWithDispatch withDispatch( ( dispatch, ownProps ) { return doSomethingElse( dispatch, ownProps ); } ); export default compose( withPluginContext, applyWithSelect, applyWithDispatch )( PluginSidebarMoreMenuItem );不使用compose的等价嵌套写法export default withPluginContext( applyWithSelect( applyWithDispatch( PluginSidebarMoreMenuItem ) ) );可见compose将“洋葱式”嵌套从内到外改写为从外到内的可读列表是组合多个 HOC 的标准姿势。配套的createHigherOrderComponentutils/create-higher-order-component负责为增强组件自动生成displayName便于 DevTools 调试。三、debounce / throttle自研的 Lodash 替代3.1 与 Lodash 的关键差异CHANGELOG 5.16.0 同时引入了自研debounce()README 明确其与 Lodash 的两点区别是“简化且类型完备”的版本始终使用定时器setTimeout而 Lodash 在部分场景会改用requestAnimationFrame——这让行为更可预期、更易测试。3.2debounce的 API 与实现类型签名utils/debounce/index.tsexport interface DebounceOptions { leading: boolean; maxWait: number; trailing: boolean; }参数语义func被防抖的函数wait延迟毫秒数options.leading是否在超时区间开始时立即调用默认falseoptions.trailing是否在超时区间结束时调用默认trueoptions.maxWait允许func被延迟的最大毫秒数保证高频率连续调用下函数仍能周期性执行。实现细节同文件 L103-L259maxWait若传入会被钳制为Math.max( options.maxWait, wait )避免小于wait的矛盾配置shouldInvoke判定四种应当立即执行的场景首次调用、距上次调用超过wait、系统时钟回拨、或已触发maxWait上限返回的防抖函数附带cancel丢弃待执行调用、flush立即执行待调用、pending是否还有未执行的调用三个方法特别说明当wait为0且leading为false时调用会推迟到下一个 tick等价于setTimeout(fn, 0)。3.3throttle本质上是带maxWait的debounceutils/throttle/index.ts 的实现非常简洁——它就是把debounce的maxWait设为waitexport const throttle ( func, wait, options ) { let leading true; let trailing true; if ( options ) { leading leading in options ? !! options.leading : leading; trailing trailing in options ? !! options.trailing : trailing; } return debounce( func, wait, { leading, trailing, maxWait: wait } ); };也就是说wordpress/compose中的节流是“每wait毫秒至多执行一次”的防抖变体默认在开始leading与结束trailing边界都会触发这与常见节流的默认行为一致。四、Hook 层重大新特性追踪4.1useEvent稳定回调直击“过期闭包”痛点CHANGELOG 7.8.02024-09-19#64943新增useEvent。它创建“能访问最新状态、但引用永远稳定”的回调可安全用于事件处理器与 effect 依赖。源码实现use-event/index.ts非常精巧export default function useEvent T extends AnyFunction ( callback?: T ) { const ref useRef AnyFunction | undefined ( () { throw new Error( Callbacks created with useEvent cannot be called during rendering. ); } ); useInsertionEffect( () { ref.current callback; } ); return useCallback AnyFunction ( ( ...args ) ref.current?.( ...args ), [] ) as T; }三个要点通过useInsertionEffect在 DOM 变更前把最新回调写入 ref保证返回的稳定函数永远读到最新闭包返回的包装函数用useCallback(..., [])固定引用配合React.memo子组件可避免无谓重渲染渲染阶段调用会抛错防止在 render 中错误使用与 React 官方useEvent草案语义一致。README 示例中useEffect的依赖数组只含onClick因此props.onClick更新不会重复触发 effect。4.2useResizeObserver从旧 API 到新 APICHANGELOG 5.8.02022-05-18将内部实现迁移到 ResizeObserver API7.8.0 提供“全新改进版”同时保留旧 API 兼容7.9.0 特别注明为 React Native 在顶层导出 legacy API8.8.0 修复了卸载时未 disconnect observer、导致 teardown 后队列回调仍执行的问题#82687。新 API 的核心特征use-resize-observer/index.ts接收ResizeObserverCallback与ResizeObserverOptions返回回调 ref setterconst setElement useResizeObserver( ( resizeObserverEntries ) console.log( resizeObserverEntries ), { box: border-box } ); div ref{ setElement } /; // 高级用法在 layout effect 中手动观察任意元素 useLayoutEffect( () { setElement( document.querySelector( data-element-id${ elementId } ) ); }, [ elementId ] );旧 API 则返回[ resizeListener, sizes ]元组渲染一个监听元素 读取尺寸现已被标记为deprecated当不传 callback 时自动回退到旧 API同文件 L67-L74。README 提醒useResizeObserver在首次渲染后才会报告尺寸此前为null。4.3useMediaQuery/useViewportMatchSSR 友好与多窗口支持7.42.02026-03-18#76446两者新增可选view参数类型Window可在非全局 window如 iframe中执行媒体查询8.0.02026-05-27view默认值改为惰性解析修复 SSR 下ReferenceError: window is not defined的回归8.6.02026-08-12#81367useViewportMatch将生成的媒体查询限定为screen避免打印print时把视口误判为变窄。useViewportMatch源码use-viewport-match/index.ts维护了与_breakpoints.scss一致的断点表Breakpoint像素宽度生效阈值xhuge1920huge1440wide1280xlarge1080large960medium782small600mobile480操作符映射生成min-width查询生成max-width查询。默认操作符为。典型用法useViewportMatch( huge, ); // 视口宽度 1440px useViewportMatch( medium ); // 视口宽度 782px它还通过ViewportMatchWidthContext支持注入“模拟宽度”__experimentalWidthProvider便于在测试或特殊宿主环境中计算匹配结果。useMediaQuery的实现use-media-query/index.ts同样值得注意它以WeakMapWindow, Mapquery, subscriber做按window, query去重的全局缓存多个 React 消费者共享同一个MediaQueryList的change监听通过内部的Set扇出通知——源码注释指出这避免了每个消费者单独addEventListener的成本大型文章编辑器挂载时可节省约 85ms并基于useSyncExternalStore订阅外部 store。4.4useFixedWindowList固定窗口虚拟列表的性能迭代CHANGELOG 8.6.02026-08-12#80935对useFixedWindowList做了两项优化仅当渲染窗口缺失条目时才触发重渲染避免列表首次绘制前的第二次样式重算移除重复的resize监听注册并改为按测量到的视口高度分页而非初始窗口尺寸。该 Hook 用于“固定行高 滚动容器”的窗口化列表use-fixed-window-list/index.tsuseFixedWindowList( elementRef, // 用于定位最近滚动容器的元素 ref itemHeight, // 固定行高px totalItems, // 列表总条数 { windowOverscan, // 可视窗口前后多渲染的条目数 useWindowing, // false 时不做窗口化计算默认 true initWindowSize, // 首帧无法测量前的初始窗口大小默认 30 expandedState, // 列表展开状态变化时触发重算 } );关键实现策略可视条目数用Math.ceil( scrollContainer.clientHeight / itemHeight )实时测量并存入 ref 而非 state避免测量本身触发渲染L67measureWindow与键盘处理函数均通过useEvent获得稳定引用因此 scroll/resize/keydown 监听只需挂载一次state 更新器会“保守”地保留仍能覆盖新窗口的旧窗口lastWindow.start start lastWindow.end end因为渲染窗口是昂贵操作仅缩小窗口只会丢弃马上又要用的 DOM 节点且引发一次无收益的样式重算L98-L128首次测量时若首帧窗口已覆盖可视区则直接复用节省列表打开过程的一次渲染L113-L119支持Home/End/PageUp/PageDown键盘滚动。五、useDialog与焦点管理族的演进5.1useDialog合并多个焦点 Hook 的复合 APIuseDialog是编辑器中 Popover、Modal 等弹出层的底层支撑它将四个能力合而为一use-dialog/index.ts约束 Tab 键循环useConstrainedTabbing挂载时聚焦useFocusOnMount卸载时归还焦点useFocusReturn焦点移出时触发onCloseuseFocusOutside。选项一览选项说明默认值focusOnMountfirstElement聚焦第一个可 Tab 元素firstInputElement聚焦第一个输入控件true聚焦元素自身false不做任何事除非实现了等效无障碍方案否则不建议firstElementconstrainTabbing是否把 Tab 限制在弹层内由focusOnMount ! false推导onClose关闭回调—onKeyDown可选的按键处理与内置逻辑合并—__unstableOnClose已废弃请用onClose—返回值是[ refCallback, props ]元组其中 props 含onFocus、onMouseDown、onMouseUp、onTouchStart、onTouchEnd、onBlur、onKeyDown与tabIndex: -1。5.2 焦点与键盘语义的历次修复CHANGELOG 中与useDialog相关的记录非常密集恰好呈现了“无障碍细节迭代”的过程7.43.02026-04-01#76861Escape 处理器增加event.stopPropagation()防止事件冒泡到父级遮罩层**8.0.02026-05-27#78433先执行消费者提供的处理器可preventDefault()选择退出关闭行为再判断Escape且未defaultPrevented时关闭Unreleased#81930useFocusOutside取消上一个待执行的 blur 检查避免 portal 内容的连续 blur 事件在焦点返回后仍误报“焦点已离开”。5.3useFocusOnMount的firstInputElement模式7.36.02025-11-26#72322为useFocusOnMount新增firstInputElement模式优先聚焦第一个输入控件而非第一个可 Tab 元素。实现细节use-focus-on-mount/index.ts若当前焦点已在节点内或focusOnMount false直接返回对firstInputElement用选择器input:not([typehidden]):not([disabled]), select:not([disabled]), textarea:not([disabled])查找表单控件并聚焦找不到输入控件时回退到focus.tabbable.find( node )[0]来自wordpress/dom聚焦时传入{ preventScroll: true }避免弹层首帧定位未完成时产生布局位移实际聚焦通过setTimeout(..., 0)延后到挂载之后并在清理函数中clearTimeout。六、可观察数据与撤销历史observableMap、useObservableValue、useStateWithHistory6.1observableMap与useObservableValueCHANGELOG 6.34.02024-05-02#60945新增observableMap数据结构与配套的useObservableValueHook。observableMap是“条目级可观察”的 Maputils/observable-map/index.ts每个订阅者只观察某一个特定 keyset/delete时仅通知该 key 的监听集合不会因其它 key 的变化而打扰无关订阅者。返回对象提供get、set、delete、subscribe四个方法subscribe返回退订函数当某个 key 的监听清空时对应的监听集合会被删除以释放内存。useObservableValue( map, name )则让 React 组件订阅该 key 的当前值值变化时触发组件更新无值时返回undefined。这对跨组件共享“细粒度可观察状态”非常有用可避免大范围重渲染。6.2useStateWithHistory带撤销/重做的状态CHANGELOG 6.19.02023-09-20新增useStateWithHistory提供内置 undo/redo 的状态管理const [ value, setValue, { hasUndo, hasRedo, undo, redo } ] useStateWithHistory( initialValue );其依赖wordpress/undo-manager见 package.json与编辑器中的撤销栈能力共享底层实现。七、破坏性变更时间线升级必读CHANGELOG 中标注为 Breaking Changes 的条目是升级决策的关键依据版本时间破坏性变更影响2.0.02018-09-05随 Babel 7 调整内置 polyfill 策略老旧环境低版本 IE需自行引入core-js或babel/polyfill3.0.02018-11-15移除remountOnPropChange2.1.0 已废弃使用方需改用其它受控手段4.0.02021-05-14放弃 IE11Node 最低升至 v12老旧浏览器用户受影响5.0.02021-07-29升级到 React 17React 16 环境不再兼容6.0.02022-12-14要求 React 18与 React 18 的并发特性对齐7.0.02024-05-31Node 最低升至 v18.12.0匹配 LTS构建/CI 环境需升级 Node8.0.02026-05-27useDialog暴露onKeyDown处理器展开到同时接收外部onKeyDown的包装元素时需通过新选项合并八、弃用Deprecation路线图withStateHOC4.2.02021-07-21弃用改用useStateHookpureHOC6.27.02024-02-09首次标注弃用改用memo或PureComponent8.0.0 起输出运行时弃用警告useCopyOnClickREADME 已标注 Deprecated功能由useCopyToClipboard承接8.0.0 修复了useCopyToClipboard在触发节点先于复制完成卸载时仍应调用onSuccess的边界问题#78387useResizeObserver旧 API新 API 已就位legacy 返回元组的用法被标记deprecated但为 React Native 仍保留顶层导出见 7.9.0useDialog的__unstableOnClose改用onClose。pure与withState的弃用路径体现了 Gutenberg 全面向 Hooks 迁移的战略HOC 形态的工具正逐步被函数式 Hook 取代useEvent、useMergeRefs等新能力则为函数组件补齐了此前 HOC 才能提供的场景。九、useMergeRefsReact 19 清理函数模式的落地CHANGELOG 8.4.02026-07-14#80133修复了合并 ref 在“元素于调用组件渲染之外挂载”如合并 ref 传给子组件、子组件在自己的 commit 中挂载元素时首次 ref 变更被跳过的问题8.0.0 则引入 React 19 的ref 回调清理函数模式支持。核心语义use-merge-refs/index.ts合并的 ref 回调仅在元素节点变化时被调用依赖数组为空与 React 原生 ref 回调行为一致传入的 ref 数组变化时例如useCallback依赖更新旧 ref 被拆下、新 ref 被挂上同一节点内部 ref 若返回清理函数则在 teardown节点变化、依赖变化或卸载时调用该清理函数而不是用null回调未返回清理函数的 ref 仍按旧行为收到null通过“不传该 ref”enabled ref即可禁用某个 ref 及其行为。典型用法README 示例const ref useCallback( ( node ) { node.addEventListener( ... ); return () node.removeEventListener( ... ); }, [ ...dependencies ] ); const otherRef useRef(); const mergedRefs useMergeRefs( [ enabled ref, otherRef ] ); return div ref{ mergedRefs } /;十、其它值得关注的能力速查CHANGELOG 之外README 自动生成的 API 文档还列出以下常用工具供读者在 packages/compose/src 中按名查阅useDisabled5.7.0 新增5.19.0 重构为基于 HTMLinert属性为块预览等场景禁用容器内全部可聚焦元素useConstrainedTabbing/useFocusReturn/useFocusableIframe/useFocusOutside焦点管理四件套分别约束 Tab、归还焦点、转发 iframe 焦点事件、监听焦点移出useInstanceId为组件生成稳定唯一 ID5.16.0 重构为 TypeScript对应 HOC 版withInstanceIduseAsyncList异步分批发列表项以提升首屏性能useKeyboardShortcut基于mousetrap绑定快捷键useReducedMotion3.4.0 随useMediaQuery一同加入响应系统“减少动态效果”偏好useIsomorphicLayoutEffect3.24.0SSR 安全地替代useLayoutEffectusePrevious获取上一次渲染的值useDebouncedInput为输入框提供“防抖后的值”三元组useWarnOnChange浅比较 props 变化并打印辅助排查多余重渲染withSafeTimeout/withGlobalEvents/withInstanceId/ifConditionHOC 形态的补充工具。内部质量层面CHANGELOG 5.19.0、5.15.0、5.16.0 等条目显示多个 HookuseFocusOutside、useDialog、useInstanceId、useFocusableIframe、useDisabled已陆续重构为 TypeScript 并采用现代 RTL/jest 测试8.7.0 进一步将 tsconfig 拆分为构建工程与开发工程8.8.0 则统一使用.jsx扩展名标记含 JSX 的源文件——这些都属于对开发者无感知的工程化打磨。结语CHANGELOG 是理解包演化的最佳入口纵观 packages/compose/CHANGELOG.mdwordpress/compose的演进主线非常清晰从 2018 年 Lodash 风格的工具移植compose/pipe/debounce/throttle到 2021–2024 年全面 TypeScript 化与 Hooks 化useEvent、useResizeObserver、observableMap再到 2025–2026 年面向 React 19 与 SSR 的精细化打磨ref 清理函数、惰性window解析、screen媒体查询限定、portal 焦点与键盘事件语义。对正在使用wordpress/compose的开发者而言本文梳理的破坏性变更时间线、弃用路线图与源码级实现可以直接作为升级评估与 API 选型的依据。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表