Gutenberg @wordpress/compose 的 ifCondition:用高阶组件实现按条件渲染
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
ifCondition是 WordPress 块编辑器(Gutenberg)@wordpress/compose包提供的高阶组件(Higher-Order Component, HOC)创建器,它根据一个谓词(predicate)函数的返回值决定是否渲染被包装的组件。在本文中,你将掌握ifCondition的完整 API 用法、其底层源码实现原理,以及它在 Gutenberg 编辑器内部(如本地自动保存监控)的真实应用案例,从而在自己的块编辑器扩展中实现按需渲染。
一、ifCondition 是什么
在 React 中,高阶组件是一种以组件为参数、返回增强后新组件的函数,常用于横切逻辑的复用,如状态注入、事件监听、条件渲染等。@wordpress/compose包正是 Gutenberg 收集这类工具的地方,除ifCondition外,还包含compose、withState、withInstanceId、pure等大量 HOC 与 Hook,详见 compose 包 README。
ifCondition的定位非常单一明确:创建一个新组件,仅当给定条件满足时才渲染。官方文档对其定义是:
ifConditionis a higher-order component creator, used for creating a new component which renders if the given condition is satisfied.
即:它是"创建 HOC 的 HOC"——第一层调用接收谓词函数,第二层调用接收要被条件包装的目标组件,最终返回一个仅在条件为真时渲染目标组件的新组件。
二、API 签名与行为语义
从 if-condition 源码 可以看到ifCondition的完整类型签名:
function ifCondition< Props extends {} >( predicate: ( props: Props ) => boolean ) { return createHigherOrderComponent( ( WrappedComponent: ComponentType< Props > ) => ( props: Props ) => { if ( ! predicate( props ) ) { return null; } return <WrappedComponent { ...props } />; }, 'ifCondition' ); }其行为可以拆解为三点:
- 接收一个谓词函数
predicate: ( props: Props ) => boolean,它接收目标组件自身的原始 props 作为唯一参数,返回布尔值(真值/假值均可)。 - 谓词为假则渲染
null:条件不满足时,整个被包装组件不会挂载,也不会渲染任何 DOM 节点。 - 谓词为真则原样转发 props 渲染:条件满足时,
WrappedComponent会收到与原始 props 完全一致的所有属性({ ...props }),对目标组件而言,多出的这一层包装是完全透明的。
值得注意的是ifCondition采用了泛型Props extends {},因此谓词函数与目标组件的 props 类型是强绑定、可推断的——如果目标组件声明了Props类型,谓词参数会自动获得同样的类型提示,这为 TypeScript 用户提供了开箱即用的类型安全。
三、官方示例:只渲染偶数
ifCondition的官方 README 给出一个非常直观的示例(见 if-condition README):只有当numberprop 为偶数时才渲染加粗的数字。
function MyEvenNumber( { number } ) { // This is only reached if the `number` prop is even. Otherwise, nothing // will be rendered. return <strong>{ number }</strong>; } MyEvenNumber = ifCondition( ( { number } ) => number % 2 === 0 )( MyEvenNumber );运行行为:
- 渲染
<MyEvenNumber number={ 2 } />→ 谓词2 % 2 === 0为true→ 输出<strong>2</strong>; - 渲染
<MyEvenNumber number={ 3 } />→ 谓词为false→ 组件直接返回null,页面中不渲染任何内容。
这里的MyEvenNumber被原地重新赋值,是 HOC 的典型写法——也可以赋给一个新名字,保留原始组件供它处复用。该模式的核心价值在于:把"是否渲染"的决策从组件内部逻辑中抽离出来,让目标组件保持纯净,只关心"渲染什么",而把"何时渲染"交由外层 HOC 统一决策。
四、结合 TypeScript 的类型化示例
在 compose 包 README 的 API 文档中,还给出了带类型标注的等价用法,更贴近真实工程:
type Props = { foo: string }; const Component = ( props: Props ) => <div>{ props.foo }</div>; const ConditionalComponent = ifCondition( ( props: Props ) => props.foo.length !== 0 )( Component ); <ConditionalComponent foo="" />; // => null <ConditionalComponent foo="bar" />; // => <div>bar</div>这段示例揭示了两个重要细节:
- 谓词拿到的是"组件自己的 props",即外层使用
<ConditionalComponent foo="..." />时传入的foo,并不会注入任何额外属性; ifCondition对"空值/空内容"的过滤是自然的:foo为空字符串时返回false,组件不渲染;有内容时正常渲染。这使它非常适合做"无数据时不渲染占位"之类的守卫逻辑。
五、源码级的实现原理
5.1 与 createHigherOrderComponent 的配合
ifCondition的实现并没有手写 HOC 工厂,而是复用了 compose 包的工具函数createHigherOrderComponent(源码见 create-higher-order-component/index.ts):
export function createHigherOrderComponent< TInner extends ComponentType< any >, TOuter extends ComponentType< any >, >( mapComponent: ( Inner: TInner ) => TOuter, modifierName: string ) { return ( Inner: TInner ) => { const Outer = mapComponent( Inner ); Outer.displayName = hocName( modifierName, Inner ); return Outer; }; }该工具的唯一额外职责是:为增强后的组件自动生成可读的displayName。hocName会把修饰名做 PascalCase 转换,再拼接内层组件的名称:
hocName( 'MyMemo', Widget )→MyMemo(Widget)hocName( 'MyMemo', <div /> )→MyMemo(Component)
因此ifCondition包装后的组件在 React DevTools 中会显示为IfCondition(MyEvenNumber)这类名称,内层组件名取自displayName || name || 'Component'的优先级链。这在调试多层 HOC 嵌套时非常有用。对应的单元测试见 create-higher-order-component/test/index.jsx,覆盖了匿名函数、类组件、自定义displayName、修饰名大小写转换等场景(例如'with-one-two_threeFOUR'会生成WithOneTwoThreeFour(Component))。
5.2 条件为假时返回 null
ifCondition的核心判定逻辑只有一行:
if ( ! predicate( props ) ) { return null; } return <WrappedComponent { ...props } />;从实现结构可以推断出几个值得注意的细节:
- 不做任何"假值替换":条件不满足时既不渲染占位符,也不渲染空标签,直接返回
null。这保证了输出 DOM 的最小化,也避免了对布局的意外影响; - props 完整转发:条件满足时通过展开运算符
{ ...props }透传全部属性,不增不减,目标组件的对外契约不受影响; - 不注入额外生命周期:
ifCondition是纯渲染守卫,不涉及 ref、context 或状态的注入,属于 compose 包中结构最简单的 HOC 之一,适合作为理解 HOC 工作原理的入门范例。
六、Gutenberg 中的真实应用:本地自动保存监控
ifCondition并不是一个仅供文档演示的玩具 API,它在 Gutenberg 编辑器源码中有真实落地。最典型的例子是packages/editor中的本地自动保存监控组件(源码见 local-autosave-monitor/index.jsx):
export default ifCondition( hasSessionStorageSupport )( LocalAutosaveMonitor );这里的hasSessionStorageSupport是一个环境能力探测函数(见同文件 L27-L44):它尝试向sessionStorage写入并删除一个测试键值,若成功则返回true,若抛出异常(例如 Safari 10 及更早版本的隐私浏览模式会在此抛错)则返回false。且探测结果会被缓存复用以避免重复执行:
let hasStorageSupport; const hasSessionStorageSupport = () => { if ( hasStorageSupport !== undefined ) { return hasStorageSupport; } try { window.sessionStorage.setItem( '__wpEditorTestSessionStorage', '' ); window.sessionStorage.removeItem( '__wpEditorTestSessionStorage' ); hasStorageSupport = true; } catch { hasStorageSupport = false; } return hasStorageSupport; };这个组合的含义是:仅在当前浏览器支持sessionStorage时才挂载本地自动保存监控,否则整棵监控子树完全不渲染,从根本上避免在不支持的环境中出现存储异常。这正是ifCondition最典型的实战形态——把"环境/能力探测"与"组件渲染"解耦,LocalAutosaveMonitor自身完全不需要关心存储可用性判断,代码职责清晰、可测试性更强。
七、常见应用场景与组合技巧
综合官方文档与仓库内真实用法,ifCondition适合以下场景:
| 场景 | 谓词示例 | 效果 |
|---|---|---|
| 数据守卫 | ( { items } ) => items.length > 0 | 空数据时不渲染列表组件 |
| 环境/能力探测 | hasSessionStorageSupport(缓存探测结果) | 不支持的特性干脆不挂载 |
| 权限控制 | ( { canEdit } ) => canEdit | 无权限时不渲染编辑按钮 |
| 功能开关(Feature Flag) | ( { featureEnabled } ) => featureEnabled | 关闭的功能不渲染相关 UI |
| 类型/数值过滤 | ( { number } ) => number % 2 === 0 | 只对满足条件的值渲染 |
组合技巧方面,由于ifCondition返回的仍是标准 HOC,它可以自由地与 compose 包的其他 HOC 叠加。例如借助 compose 的compose函数(右到左执行)将多个 HOC 组合:
import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; const enhance = compose( ifCondition( ( { posts } ) => posts && posts.length > 0 ), withSelect( ( select ) => ( { posts: select( 'core' ).getEntityRecords( 'postType', 'post' ), } ) ) ); const PostList = ( { posts } ) => ( <ul>{ posts.map( ( p ) => <li key={ p.id }>{ p.title.raw }</li> ) }</ul> ); export default enhance( PostList );八、使用注意事项
- 谓词只在渲染期执行:
ifCondition的谓词在每次组件渲染时被调用,因此应保持为纯函数,避免副作用;昂贵计算可考虑在外部做缓存(参考hasSessionStorageSupport的缓存写法)。 - 条件不满足时不卸载逻辑:当谓词由真变假时,返回
null会导致目标组件卸载(其useEffect清理函数会被执行);如需保留组件状态应改用内部条件渲染,而非ifCondition。 - 不影响 hooks 规则:谓词调用发生在 HOC 包装层,目标组件内部的 hooks 依然遵循 React 规则,不受包装层影响。
- displayName 便于调试:得益于
createHigherOrderComponent,包装后的组件在 DevTools 中显示为IfCondition(ComponentName),可据此快速定位多层嵌套中的渲染守卫。
九、延伸阅读
- if-condition 源码实现:
ifCondition的全部实现(约 34 行) - if-condition 官方 README:本文依据的原始文档
- compose 包 README:
@wordpress/compose完整 API 列表与安装方式 - compose 包入口:
ifCondition等 HOC 的统一导出位置 - createHigherOrderComponent 源码:HOC displayName 生成机制
- createHigherOrderComponent 测试:displayName 命名规则的单测
- local-autosave-monitor 源码:
ifCondition在编辑器内的真实应用
如需在自己的项目中使用,可安装@wordpress/compose并引入ifCondition:
npm install @wordpress/compose --saveimport { ifCondition } from '@wordpress/compose';总体而言,ifCondition是一个体积小、语义清晰、与 compose 生态深度集成的条件渲染工具:它以"谓词 + 组件"两段式调用封装了"按 props 条件决定是否渲染"这一高频需求,并在 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),仅供参考