title: "编码规范" post_status: publish comment_status: open taxonomy: category: - gutenberg-docs post_tag: - Code - Contributors - Repos
编码规范
本文档旨在规定 Gutenberg 项目特有的编码规范。基础编码规范遵循 WordPress 编码标准。以下章节概述了 Gutenberg 项目中使用的额外模式和约定。
CSS
命名规范
为避免类名冲突,类名必须遵循以下准则,这些准则的灵感部分来源于 BEM(块、元素、修饰符)方法论。
分配给元素的所有类名都必须以包名为前缀,后跟一个短横线和组件所在目录的名称。组件根元素的所有后代元素必须在基础类名后追加一个由短横线分隔的描述符,并通过两个连续的下划线 __ 与基础部分分隔。
- 根元素:
package-directory - 子元素:
package-directory__descriptor-foo-bar
根元素被视为 index.js 中默认导出返回的最高祖先元素。值得注意的是,如果你的文件夹包含多个文件,每个文件都有自己的默认导出组件,那么只有 index.js 导出的组件所渲染的元素才能被视为根元素。所有其他元素都应被视为后代。
示例:
考虑位于 packages/components/src/notice/index.js 的以下组件:
export default function Notice( { children, onRemove } ) {
return (
<div className="components-notice">
<div className="components-notice__content">{ children }</div>
<Button
className="components-notice__dismiss"
icon={ check }
label={ __( 'Dismiss this notice' ) }
onClick={ onRemove }
/>
</div>
);
}
组件可以被分配表示状态的类名(例如,“活动”标签或“已打开”面板)。这些修饰符应作为单独的类名应用,并通过 is- 作为形容词表达式前缀(is-active 或 is-opened)。在极少数情况下,你可能会遇到修饰符前缀的变体,通常是为了提高可读性(has-warning)。由于修饰符类名不与特定组件上下文绑定,因此在样式表中应始终与正在修改的组件一起编写(.components-panel.is-opened)。
示例:
再次考虑通知示例。我们可能希望对可关闭的通知应用特定样式。clsx 包 可以作为一个有用的工具,用于有条件地应用修饰符类名。
import clsx from 'clsx';
export default function Notice( { children, onRemove, isDismissible } ) {
const classes = clsx( 'components-notice', {
'is-dismissible': isDismissible,
} );
return <div className={ classes }>{ /* ... */ }</div>;
}
组件的类名绝不应在其自身文件夹之外使用(极少数例外情况如 _z-index.scss)。若需在自己的组件中继承其他组件的样式,应渲染该其他组件的实例。最坏情况下,也应在自身组件的样式表中复制这些样式。此举旨在通过将共享组件隔离为可复用接口来提升可维护性,通过适配有限的通用组件以支持多样化用例,从而减少相似UI元素的表面积。
块级元素的 SCSS 文件命名规范
构建流程会在 Webpack 运行时,将块级元素库目录中的 SCSS 拆分为两个独立的 CSS 文件。
置于 style.scss 文件中的样式将被构建到 blocks/build/style.css,以便在前端主题和编辑器中加载。如需针对块级元素在编辑器中的显示添加特定样式,请将其添加到 editor.scss 文件中。
同时出现在主题和编辑器中的样式示例包括图库列和首字下沉效果。
JavaScript
Gutenberg 中的 JavaScript 使用了 ECMAScript 语言规范的现代语言特性以及 JSX 语言语法扩展。这些功能通过预设配置组合启用,特别是项目 Babel 配置中用作预设的 @wordpress/babel-preset-default。
虽然引入新 JavaScript 语言特性的分阶段流程允许在功能完成前使用新特性,但 Gutenberg 项目和 @wordpress/babel-preset-default 配置仅支持已达到第 4 阶段("已完成")的提案。
导入
在 Gutenberg 项目中,我们使用 ES2015 导入语法来创建模块化代码,从而清晰地区分特定功能的代码、跨不同 WordPress 功能共享的代码以及第三方依赖。
这些区分通过文件顶部的多行注释来标识,这些注释用于说明从其他文件或源导入的代码。
外部依赖
外部依赖是指不由 WordPress 贡献者维护的第三方代码,而是作为默认脚本包含在 WordPress 中或从外部包管理器(如 npm)引用的代码。
示例:
/**
* External dependencies
*/
import moment from 'moment';
WordPress 依赖项
为提升功能间的可复用性,我们的 JavaScript 被拆分为特定领域的模块,这些模块会export一个或多个函数或对象。在 Gutenberg 项目中,我们在顶级目录下区分了这些模块。每个模块服务于独立的目的,并且代码经常在它们之间共享。例如,为了本地化其文本,编辑器代码需要包含来自 i18n 模块的函数。
示例:
/**
* WordPress 依赖项
*/
import { __ } from '@wordpress/i18n';
内部依赖
在特定功能模块中,代码被组织到独立的文件和文件夹中。与外部依赖和 WordPress 依赖类似,你可以使用 import 关键字将这些代码引入作用域。主要区别在于:导入内部文件时,应使用相对于当前工作顶级目录的相对路径。
示例:
/**
* 内部依赖
*/
import VisualEditor from '../visual-editor';
遗留实验性 API、插件专用 API 及私有 API
遗留实验性 API
历史上,Gutenberg 使用 __experimental 和 __unstable 前缀来表明某个 API 尚未稳定,可能会发生变更。这是一种遗留惯例,应避免使用,转而采用下文所述的插件专用 API 模式或私有 API 模式。
使用这些前缀的问题在于,这些 API 很少被稳定化或移除。截至 2022 年 6 月,WordPress 核心包含了 280 个在主要 WordPress 版本发布期间从 Gutenberg 插件合并而来的公开导出实验性 API。许多插件和主题开始依赖这些实验性 API 来实现无法通过其他方式访问的核心功能。
遗留的 __experimental API 已不能再随意移除。它们已成为 WordPress 公共 API 的一部分,并受 WordPress 向后兼容性政策 约束。移除它们需要经过弃用流程。对于某些 API 来说可能相对容易,但对于其他 API 则可能需要付出努力并跨越多个 WordPress 版本发布周期。
总而言之,请勿为新 API 使用 __experimental 前缀。应改用插件专用 API 和私有 API。
仅限插件使用的 API
仅限插件使用的 API 是从模块导出的临时值,其存在要么有待未来修订,要么为实现目标提供即时手段。
对于外部使用者:
仅限插件使用的 API 不提供支持承诺。 它们可能且将会在没有提前警告的情况下被移除或更改,包括作为次要版本或补丁版本的一部分。作为外部使用者,您应避免使用这些 API。
对于项目贡献者:
仅限插件使用的 API 是指计划最终公开可用,但需进一步实验、测试和讨论的 API。应尽快使其稳定或移除。
仅限插件使用的 API 被排除在 WordPress 核心之外,仅在 Gutenberg 插件中可用:
// 使用 globalThis.IS_GUTENBERG_PLUGIN 允许 Webpack 将此导出
// 从 WordPress 核心中排除:
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
export { doSomethingExciting } from './api';
}
此类 API 的公共接口尚未最终确定。除了代码中的引用外,这些 API 不应在任何 CHANGELOG 中记录或提及。从外部视角来看,它们实际上应被视为不存在。在大多数情况下,它们应仅被暴露以满足此仓库中维护的包之间的需求。
虽然仅限插件使用的 API 通常可能稳定为公开可用的 API,但不能保证一定会如此。
私有 API
每个需要内部访问或暴露私有 API 的 @wordpress 包可以通过选择加入 @wordpress/private-apis 来实现:
// 在 packages/block-editor/private-apis.js 中:
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
export const { lock, unlock } =
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
'我确认私有功能不适用于主题或插件,否则将在下一版 WordPress 中失效。',
'@wordpress/block-editor' // 调用 __dangerousOptInToUnstableAPIsOnlyForCoreModules 的包名称,
//(不是你想访问其 API 的包名称)
);
每个 @wordpress 包只能选择加入一次。该流程明确告知扩展者不应使用它。本文档将重点介绍使用示例,但你可以在其 README.md 中了解更多关于 @wordpress/private-apis 包的信息。
一旦包选择加入,你就可以使用 lock() 和 unlock() 工具:
// 假设这个对象是从包中导出的:
export const publicObject = {};
// 但这个字符串是内部的,不应公开可用:
const privateString = '私有信息';
// 解决方案:将字符串“锁定”在对象内部:
lock( publicObject, privateString );
// 字符串未嵌套在对象中,也无法从中提取:
console.log( publicObject );
// {}
// 访问字符串的唯一方式是“解锁”对象:
console.log( unlock( publicObject ) );
// "私有信息"
// lock() 接受所有数据类型,不仅仅是字符串:
export const anotherObject = {};
lock( anotherObject, function privateFn() {} );
console.log( unlock( anotherObject ) );
// function privateFn() {}
继续阅读以了解如何使用 lock() 和 unlock() 来避免公开导出不同类型的 私有 API。
Private selectors and actions
You can attach private selectors and actions to a public store:
// In packages/package1/store.js:
import { privateHasContentRoleAttribute } from './private-selectors';
import { privateToggleFeature } from './private-actions';
// The `lock` function is exported from the internal private-apis.js file where
// the opt-in function was called.
import { lock, unlock } from './lock-unlock';
export const store = registerStore( /* ... */ );
// Attach a private action to the exported store:
unlock( store ).registerPrivateActions( {
privateToggleFeature,
} );
// Attach a private action to the exported store:
unlock( store ).registerPrivateSelectors( {
privateHasContentRoleAttribute,
} );
// In packages/package2/MyComponent.js:
import { store } from '@wordpress/package1';
import { useSelect } from '@wordpress/data';
// The `unlock` function is exported from the internal private-apis.js file where
// the opt-in function was called.
import { unlock } from './lock-unlock';
function MyComponent() {
const hasRole = useSelect(
( select ) =>
// Use the private selector:
unlock( select( store ) ).privateHasContentRoleAttribute()
// Note the unlock() is required. This line wouldn't work:
// select( store ).privateHasContentRoleAttribute()
);
// Use the private action:
unlock( useDispatch( store ) ).privateToggleFeature();
// ...
}
私有函数、类和变量
// 在 packages/package1/index.js 中:
import { lock } from './lock-unlock';
export const privateApis = {};
/* 将私有数据附加到导出的对象上 */
lock( privateApis, {
privateCallback: function () {},
privateReactComponent: function PrivateComponent() {
return <div />;
},
privateClass: class PrivateClass {},
privateVariable: 5,
} );
// 在 packages/package2/index.js 中:
import { privateApis } from '@wordpress/package1';
import { unlock } from './lock-unlock';
const {
privateCallback,
privateReactComponent,
privateClass,
privateVariable,
} = unlock( privateApis );
请记住,始终在已注册的 store 上注册私有 actions 和 selectors。
有时这很简单:
export const store = createReduxStore( STORE_NAME, storeConfig() );
// `register` 使用与 `createReduxStore` 创建的相同的 `store` 对象。
register( store );
unlock( store ).registerPrivateActions( {
// ...
} );
然而,某些包可能同时调用 createReduxStore 和 registerStore。在这种情况下,请始终选择已注册的 store:
export const store = createReduxStore( STORE_NAME, {
...storeConfig,
persist: [ 'preferences' ],
} );
const registeredStore = registerStore( STORE_NAME, {
...storeConfig,
persist: [ 'preferences' ],
} );
unlock( registeredStore ).registerPrivateActions( {
// ...
} );
Private function arguments
To add a private argument to a stable function you'll need
to prepare a stable and a private version of that function.
Then, export the stable function and lock() the unstable function
inside it:
// In @wordpress/package1/index.js:
import { lock } from './lock-unlock';
// A private function contains all the logic
function privateValidateBlocks( formula, privateIsStrict ) {
let isValid = false;
// ...complex logic we don't want to duplicate...
if ( privateIsStrict ) {
// ...
}
// ...complex logic we don't want to duplicate...
return isValid;
}
// The stable public function is a thin wrapper that calls the
// private function with the private features disabled
export function validateBlocks( blocks ) {
privateValidateBlocks( blocks, false );
}
export const privateApis = {};
lock( privateApis, { privateValidateBlocks } );
// In @wordpress/package2/index.js:
import { privateApis as package1PrivateApis } from '@wordpress/package1';
import { unlock } from './lock-unlock';
// The private function may be "unlocked" given the stable function:
const { privateValidateBlocks } = unlock( package1PrivateApis );
privateValidateBlocks( blocks, true );
Private React component properties
To add a private argument to a stable component you'll need
to prepare a stable and a private version of that component.
Then, export the stable function and lock() the unstable function
inside it:
// In @wordpress/package1/index.js:
import { lock } from './lock-unlock';
// The private component contains all the logic
const PrivateMyButton = ( { title, privateShowIcon = true } ) => {
// ...complex logic we don't want to duplicate...
return (
<button>
{ privateShowIcon && <Icon src={ someIcon } /> } { title }
</button>
);
};
// The stable public component is a thin wrapper that calls the
// private component with the private features disabled
export const MyButton = ( { title } ) => (
<PrivateMyButton title={ title } privateShowIcon={ false } />
);
export const privateApis = {};
lock( privateApis, { PrivateMyButton } );
// In @wordpress/package2/index.js:
import { privateApis } from '@wordpress/package1';
import { unlock } from './lock-unlock';
// The private component may be "unlocked" given the stable component:
const { PrivateMyButton } = unlock( privateApis );
export function MyComponent() {
return <PrivateMyButton data={ data } privateShowIcon={ true } />;
}
私有编辑器设置
WordPress 扩展程序无法自行更新私有区块设置。@wordpress/block-editor 存储的 updateSettings() 操作将过滤掉所有不属于公共 API 的设置。实际存储这些设置的唯一方法是通过私有操作 __experimentalUpdateSettings()。
要将区块编辑器设置私有化,请将其添加到 /packages/block-editor/src/store/actions.js 中的 privateSettings 列表:
const privateSettings = [
'inserterMediaCategories',
// 在此处列出区块编辑器设置以使其私有化
];
私有 block.json 与 theme.json API
目前尚无法将 block.json 和 theme.json API 限制在 Gutenberg 代码库内使用。但未来新的私有 API 将仅适用于核心 WordPress 区块,插件和主题将无法访问这些 API。
在 thunk 中内联小型操作
最后,与其引入新的操作创建器,不如考虑使用 thunk:
export function toggleFeature( scope, featureName ) {
return function ( { dispatch } ) {
dispatch( { type: '__private_BEFORE_TOGGLE' } );
// ...
};
}
公开私有 API
某些私有 API 可能受益于社区反馈,将其暴露给 WordPress 扩展开发者是有意义的。但同时,将它们转变为 WordPress 核心的公共 API 并不合适。你应该怎么做?
你可以将该私有 API 重新导出为仅限插件的 API,使其仅在 Gutenberg 插件中公开:
// 此函数在任何上下文中都不能被扩展开发者使用:
function privateEverywhere() {}
// 此函数可以在 Gutenberg 插件中被扩展开发者使用,但不能在原生 WordPress 核心中使用:
function privateInCorePublicInPlugin() {}
// Gutenberg 在内部将这两个函数都视为私有 API:
const privateApis = {};
lock( privateApis, { privateEverywhere, privateInCorePublicInPlugin } );
// privateInCorePublicInPlugin 函数被显式导出,
// 但由于 globalThis.IS_GUTENBERG_PLUGIN 检查,此导出不会合并到 WordPress 核心中。
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
export const privateInCorePublicInPlugin =
unlock( privateApis ).privateInCorePublicInPlugin;
}
对象
在定义对象属性值时,尽可能使用简写表示法:
const a = 10;
// 不推荐:
const object = {
a: a,
performAction: function () {
// ...
},
};
// 推荐:
const object = {
a,
performAction() {
// ...
},
};
字符串
字符串字面量应使用单引号声明,_除非_字符串本身包含需要转义的单引号——这种情况下应使用双引号。如果字符串同时包含单引号_和_双引号,可以使用 ES6 模板字符串来避免转义引号。
注意: 在面向用户的字符串中(如 it’s 或 haven’t),切勿使用单引号字符(')代替撇号(’)。在测试代码中仍鼓励使用真正的撇号。
通常应避免使用反斜杠转义引号:
// 错误:
const name = "Matt";
// 正确:
const name = 'Matt';
// 错误:
const pet = "Matt's dog";
// 同样错误(未使用撇号):
const pet = "Matt's dog";
// 正确:
const pet = 'Matt’s dog';
// 同样正确:
const oddString = "She said 'This is odd.'";
应尽可能使用 ES6 模板字符串而非字符串拼接:
const name = 'Stacey';
// 错误:
alert( 'My name is ' + name + '.' );
// 正确:
alert( `My name is ${ name }.` );
可选链
可选链是 ECMAScript 规范 2020 版本引入的新语言特性。虽然该特性对于访问可能为 null-ish(null 或 undefined)对象的属性非常方便,但在使用可选链时需要注意一些常见的陷阱。这些问题可能在未来通过代码检查(linting)和/或类型检查来帮助防范。目前,您需要注意以下几点:
- 当对使用可选链求值的值进行取反(
!)操作时,应注意如果可选链到达无法继续的点,它将产生一个假值,该值在取反时会转换为true。在许多情况下,这不是预期的结果。- 示例:
const hasFocus = ! nodeRef.current?.contains( document.activeElement );如果nodeRef.current未赋值,将得到true。 - 相关 issue:#21984
- 类似的 ESLint 规则:
no-unsafe-negation
- 示例:
- 当赋值布尔值时,注意可选链可能产生假值(
undefined、null),但不严格等于false。当该值以期望为布尔值(true或false)的方式传递时,这可能成为一个问题。虽然这在布尔值中很常见——因为布尔值通常以逻辑上广泛考虑真值和假值的方式使用——但当急切地假设属性访问链末端的结果类型时,其他可选链也可能出现这些问题。类型检查可能有助于防止这类错误。- 示例:
document.body.classList.toggle( 'has-focus', nodeRef.current?.contains( document.activeElement ) );可能错误地添加类,因为第二个参数是可选的。如果传递undefined,它不会像传递false那样取消设置该类。 - 示例:
<input value={ state.selected?.value.trim() } />可能通过受控和非受控输入之间的切换,在 React 中无意间导致警告。当急切地假设trim()的结果总是返回字符串值时,很容易陷入这个陷阱,而忽略了可选链可能导致求值更早中止并返回undefined值的事实。
- 示例:
React 组件
建议将所有组件实现为函数组件,并使用钩子来管理组件状态和生命周期。除了错误边界外,您不应遇到必须使用类组件的情况。请注意,WordPress 关于代码重构的指南在此适用:无需集中精力批量更新类组件,而应将其视为结合其他变更的良好重构机会。
使用 JSDoc 的 JavaScript 文档
Gutenberg 遵循 WordPress JavaScript 文档标准,并针对其在文件组织中对导入语义的特殊使用、使用 TypeScript 工具进行类型验证,以及使用 @wordpress/docgen 自动生成文档,制定了额外的指南。
如需更多指导,请查阅以下资源:
自定义类型
使用 JSDoc @typedef 标签 定义自定义类型。
自定义类型应包含描述,并且始终应包含其基础类型。
自定义类型的命名应尽可能简洁,同时保持含义清晰并避免与其他全局或作用域内的类型冲突。所有自定义类型都应使用 WP 前缀。避免使用多余或冗余的前缀和后缀(例如 Type 后缀或 Custom 前缀)。自定义类型默认不是全局的,因此自定义类型不需要过度针对特定包。然而,其命名应具有足够的特异性,以避免在与其他类型处于同一作用域时产生歧义或名称冲突。
/**
* 块选择对象。
*
* @typedef WPBlockSelection
*
* @property {string} clientId 块客户端 ID。
* @property {string} attributeKey 块属性键。
* @property {number} offset 属性值偏移量,基于富文本值。
*/
请注意,在 @typedef 和类型名称之间没有 {Object}。由于下面的 @property 告诉我们这是一个对象类型,建议在定义对象类型时不要使用 {Object}。
自定义类型也可用于描述一组预定义选项。虽然类型联合可以与字面值一起用作内联类型,但在遵守 80 字符最大行长度的同时对齐标签可能很困难。使用自定义类型定义联合类型可以提供描述这些选项目的的机会,并有助于避免这些行长问题。
/**
* 命名的断点尺寸。
*
* @typedef {'huge'|'wide'|'large'|'medium'|'small'|'mobile'} WPBreakpoint
*/
请注意在定义一组字符串字面量时使用引号。根据 JavaScript 编码标准,当将字符串字面量指定为类型或作为默认函数参数时,或在指定导入类型的路径时,应使用单引号。
导入和导出类型
使用 TypeScript import 函数 从其他文件或第三方依赖项导入类型声明。
由于导入的类型声明可能占用过多的可用行长度,并且在多次引用时变得冗长,建议在文件顶部紧随 import 分组之后,使用 @typedef 声明为外部类型创建别名。
/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */
请注意,定义在其他文件中的所有自定义类型都可以被导入。
在考虑应从 WordPress 包中提供哪些类型时,应将包入口点脚本中的 @typedef 语句视为与其公共 API 基本相同。了解这一点很重要,既可以避免无意中将内部类型暴露在公共接口上,也可以作为暴露项目公共类型的一种方式。
// packages/data/src/index.js
/** @typedef {import('./registry').WPDataRegistry} WPDataRegistry */
在此代码片段中,@typedef 将支持前一个示例中 import('@wordpress/data') 的用法。
外部依赖
许多第三方依赖会分发它们自己的 TypeScript 类型声明。对于这些依赖,import 语义应该"直接可用"。

如果你在编辑器中使用了 TypeScript 集成,通常可以看到当类型解析为除默认的 any 类型之外的任何类型时,此功能即正常工作。
对于不自行分发 TypeScript 类型的包,如果存在相应的社区维护类型定义,欢迎安装并使用 DefinitelyTyped 的类型定义。
泛型类型
当记录泛型类型(如 Object、Function、Promise 等)时,务必包含关于预期记录类型的详细信息。
// 错误示例:
/** @type {Object} */
/** @type {Function} */
/** @type {Promise} */
// 正确示例:
/** @type {Record<string,number>} */ /* 或 */ /** @type {{[setting:string]:any}} */
/** @type {(key:string)=>boolean} */
/** @type {Promise<string>} */
当对象被用作字典时,可以通过两种方式定义其类型:可索引接口({[setting:string]:any})或 Record。如果对象的键名(如 setting)能为开发者提供操作提示,请使用可索引接口。否则,使用 Record。
此处的函数表达式使用了 TypeScript 的函数类型语法,有助于提供关于预期参数名称和类型的更详细信息。更多信息,请参阅 TypeScript @type 标签函数建议。
在更高级的情况下,您可以使用 TypeScript @template 标签将自定义类型定义为泛型类型。
类似于关于类型联合和字面量值的“自定义类型”建议,您可以考虑创建自定义类型 @typedef 来更好地描述对象记录的预期键值,或提取复杂的函数签名。
/**
* apiFetch 中间件处理程序。传入 fetch 选项后,中间件应在完成处理后调用 `next` 中间件。
*
* @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware
*/
/**
* 命名的断点尺寸。
*
* @typedef {"huge"|"wide"|"large"|"medium"|"small"|"mobile"} WPBreakpoint
*/
/**
* 断点名称与生效像素宽度的哈希映射。
*
* @type {Record<WPBreakpoint,number>}
*/
const BREAKPOINTS = { huge: 1440 /* , ... */ };
可为空、undefined 和 void 类型
你可以使用前置的 ? 来表示一个可为空的类型。仅当描述类型或显式的 null 值时,才使用类型的可为空形式。不要使用可为空形式作为可选参数的指示符。
/**
* 返回给定键的配置值(如果存在)。如果没有配置值,则返回 null。
*
* @param {string} key 要检索的配置键。
*
* @return {?*} 配置值(如果存在)。
*/
function getConfigurationValue( key ) {
return config.hasOwnProperty( key ) ? config[ key ] : null;
}
类似地,仅当你期望一个显式的 undefined 值时,才使用 undefined 类型。
/**
* 如果下一个 HTML 令牌关闭了当前令牌,则返回 true。
*
* @param {WPHTMLToken} currentToken 要比较的当前令牌。
* @param {WPHTMLToken|undefined} nextToken 要比较的下一个令牌。
*
* @return {boolean} 如果 `nextToken` 关闭了 `currentToken`,则为 true,否则为 false。
*/
如果参数是可选的,请使用方括号表示法。如果一个可选参数有一个默认值,并且可以在函数表达式中表示为默认参数,则无需在 JSDoc 中包含该值。如果函数参数有一个有效的默认值,该值需要复杂的逻辑且无法使用默认参数语法表达,你可以选择在 JSDoc 中包含该默认值。
/**
* 渲染一个工具栏。
*
* @param {Object} props 组件属性。
* @param {string} [props.className] 要设置在容器 `<div />` 上的类。
*/
当一个函数不包含 return 语句时,它被称为具有 void 返回值。如果返回类型是 void,则无需包含 @return 标签。
如果一个函数有多个代码路径,其中某些(但非全部)条件导致 return 语句,你可以将其记录为包含 void 类型的联合类型。
/**
* 返回给定键的配置值(如果存在)。
*
* @param {string} key 要检索的配置键。
*
* @return {*|void} 配置值(如果存在)。
*/
function getConfigurationValue( key ) {
if ( config.hasOwnProperty( key ) ) {
return config[ key ];
}
}
在记录函数类型时,你必须始终包含 void 返回值类型,否则函数将被推断为返回混合("any")值,而不是 void 结果。
/**
* 一个 apiFetch 中间件处理程序。传递获取选项后,中间件应在完成其处理后调用 `next` 中间件。
*
* @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware
*/
记录示例
由于使用 @wordpress/docgen 工具生成的文档会包含已定义的 @example 标签,因此为函数和组件包含使用示例被视为最佳实践。这对于记录包公共 API 的成员尤为重要。
在记录示例时,请使用 Markdown 的 ``` 代码块来标记代码示例的开始和结束。一个示例可以跨越多行。
/**
* 给定已注册存储的名称,返回该存储选择器的对象。选择器函数已预先绑定,以自动传递当前状态。作为使用者,您只需传递选择器的参数(如果适用)。
*
* @param {string} name 存储名称。
*
* @example
* ```js
* select( 'my-shop' ).getPrice( 'hammer' );
* ```
*
* @return {Record<string,WPDataSelector>} 包含该存储选择器的对象。
*/
记录 React 组件
在可能的情况下,所有组件都应实现为函数组件,并使用钩子来管理组件生命周期和状态。
记录函数组件应被视为与记录任何其他函数相同。记录组件时的主要注意事项是,该函数通常只接受一个参数("props"),该参数可能包含许多属性成员。使用参数属性的点语法来记录各个 prop 的类型。
/**
* 将区块配置的标题渲染为字符串,如果无法确定标题,则渲染为空。
*
* @example
*
* ```jsx
* <BlockTitle clientId="afd1cb17-2c08-4e7a-91be-007ba7ddc3a1" />
* ```
*
* @param {Object} props
* @param {string} props.clientId 区块的客户端 ID。
*
* @return {?string} 区块标题。
*/
对于类组件,没有关于记录组件 props 的建议。Gutenberg 不使用也不认可 propTypes 静态类成员。
PHP
我们使用 phpcs (PHP_CodeSniffer) 配合 WordPress 编码标准规则集 来对本项目中的所有 PHP 代码进行大量自动化检查。这确保我们遵循 WordPress PHP 编码标准。
使用 PHPCS 最简单的方式是通过本地环境。安装完成后,你可以运行 npm run lint:php 来检查你的 PHP 代码。
如果你倾向于在本地安装 PHPCS,应该使用 composer。在你的电脑上安装 composer,然后运行 composer install。这将安装 phpcs 和 WordPress-Coding-Standards,之后你可以通过 composer lint 来运行检查。