Gutenbergcore/preferences数据存储完全指南:作用域、默认值、持久化层与实战用法
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
导读
本文以 Gutenberg 仓库中 docs/reference-guides/data/data-core-preferences.md 文档为核心,系统讲解 WordPress 块编辑器(Block Editor)中core/preferences数据存储的完整用法:从命名空间注册、四个核心概念(Scope / Key / Value / Defaults),到get选择器与set、setDefaults、toggle、setPersistenceLayer四个动作的签名与内部实现,并结合 packages/preferences 包的源码、reducer 持久化机制与单元测试,给出可直接落地的初始化、读写、开关切换与 localStorage / 异步 API 持久化示例。读完本文,你将掌握如何为自己的编辑器插件或独立应用接入一套「按作用域隔离、支持默认值回退、可选持久化」的用户偏好系统。
一、core/preferences是什么
core/preferences是 Gutenberg 提供的、面向应用级用户偏好(preferences)的键值对(key/value)存储。它解决的核心问题是:编辑器里的大量 UI 开关(如固定工具栏、聚焦模式、面板折叠状态、图标标签显示与否等)需要一个统一、可隔离、可持久化的存放位置,而不是散落在各处手动操作localStorage。
从仓库源码看,该 store 的定义与注册位于 packages/preferences/src/store/index.ts:
import { createReduxStore, register } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; import { STORE_NAME } from './constants'; export const store = createReduxStore<StoreState, typeof actions, typeof selectors>( STORE_NAME, { reducer, actions, selectors } ); register( store );其中STORE_NAME在 packages/preferences/src/store/constants.ts 中被定义为字符串'core/preferences',即文档开头声明的 Namespace。它通过@wordpress/data的createReduxStore+register完成全局注册,因此应用内任意位置都可以用wp.data.select( 'core/preferences' )和wp.data.dispatch( 'core/preferences' )访问它。
使用前提:
@wordpress/preferences包要求运行环境支持 ES2015+,可通过npm install @wordpress/preferences --save安装(见 packages/preferences/README.md)。
二、四大核心概念:Scope、Key、Value、Defaults
官方文档(packages/preferences/README.md)明确给出了理解该 store 的四个概念,这也是所有 API 参数设计的出发点:
| 概念 | 说明 | 示例 |
|---|---|---|
| Scope | 作用域,充当命名空间。不同模块可以有同名偏好而互不干扰 | core/edit-post、core/edit-site、namespace/editor-or-plugin-name |
| Key(Name) | 每个偏好在作用域内的唯一键名,必须是字符串 | fixedToolbar、myPreferenceName |
| Value | 偏好值,可以是任意类型;但实际类型受持久化层约束 | true、2、{ panels: [ 'a' ] } |
| Defaults | 默认值,仅在偏好值为undefined时返回;不参与持久化,只存于内存 | { myBooleanFeature: true } |
其中「Defaults 不持久化」这一点在源码中有明确体现:packages/preferences/src/store/reducer.ts 里defaults与preferences是两个独立 reducer,并通过combineReducers合并为完整状态{ defaults, preferences };withPersistenceLayer高阶 reducer 只监听SET_PREFERENCE_VALUE把用户偏好写入持久层,默认值永远不会被写出去。
值得注意的 Value 限制:如果持久化层使用 JSON 格式(例如localStorage),则 Value 只能使用 JSON 可序列化的类型。这是文档明确给出的约束,规划数据结构时应提前遵守。
三、Selector:get
core/preferences当前只暴露一个选择器get,签名如下(见 docs/reference-guides/data/data-core-preferences.md):
- 参数:
state(StoreState)、scope(string,如core/edit-post)、name(string,偏好名) - 返回:
*,该偏好是否启用/取值
其核心实现位于 packages/preferences/src/store/selectors.ts:
export const get = withDeprecatedKeys( ( state: StoreState, scope: string, name: string ) => { const value = state.preferences[ scope ]?.[ name ]; return value !== undefined ? value : state.defaults[ scope ]?.[ name ]; } );从源码可以看出读取优先级:已设置的偏好值 > 该作用域的默认值 >undefined。这一行为被 packages/preferences/src/store/test/selectors.ts 中的四组用例完整覆盖:
- 无状态时返回
undefined; - 只设默认值时返回默认值;
- 只设偏好值时返回偏好值;
- 两者都设时,偏好值优先于默认值。
关于已废弃键的兼容迁移
get外层的withDeprecatedKeys高阶封装值得特别注意:当name命中settingsToMoveToCore列表(allowRightClickOverrides、distractionFree、editorMode、fixedToolbar、focusMode、hiddenBlockTypes、inactivePanels、keepCaretInsideBlock、mostUsedBlocks、openPanels、showBlockBreadcrumbs、showIconLabels、showListViewByDefault、isPublishSidebarEnabled、isComplementaryAreaVisible、pinnedItems),且scope为core/edit-post或core/edit-site时,会触发deprecated警告(自 WordPress 6.5 起),并透明地改读core作用域下的同名偏好:
// 旧写法(6.5 起废弃) wp.data.select( 'core/preferences' ).get( 'core/edit-post', 'fixedToolbar' ); // 新写法 wp.data.select( 'core/preferences' ).get( 'core', 'fixedToolbar' );这意味着在新代码中,这类编辑器级偏好应统一存到core作用域,而不是继续挂在core/edit-post下。
四、Actions:set、setDefaults、toggle、setPersistenceLayer
文档列出了四个动作(见 packages/preferences/src/store/actions.ts),下面结合实现逐一讲解。
4.1set:写入偏好值
- 参数:
scope(string)、name(string)、value(*) - 返回:
SetAction(类型为SET_PREFERENCE_VALUE的动作对象)
export function set( scope: string, name: string, value: any ): SetAction { return { type: 'SET_PREFERENCE_VALUE', scope, name, value }; }该动作最终被preferencesreducer 处理,以不可变更新的方式写入state.preferences[ scope ][ name ](见 reducer.ts):
if ( action.type === 'SET_PREFERENCE_VALUE' ) { const { scope, name, value } = action; return { ...state, [ scope ]: { ...state[ scope ], [ name ]: value }, }; }4.2setDefaults:设置默认值
- 参数:
scope(string)、defaults(ScopedDefaults,即偏好名到值的键值映射) - 返回:
SetDefaultsAction(SET_PREFERENCE_DEFAULTS)
export function setDefaults( scope: string, defaults: ScopedDefaults ): SetDefaultsAction { return { type: 'SET_PREFERENCE_DEFAULTS', scope, defaults }; }reducer 侧会将新默认值与既有默认值浅合并(...state[ scope ], ...values)。文档强调:默认值应在应用初始化阶段设置,因为一旦用户写入过真实偏好,默认值便不会再被读到。
4.3toggle:翻转布尔偏好
- 参数:
scope(string)、name(string)
toggle是一个使用select/dispatch的函数式动作(thunk),内部先通过get读取当前值,再取反后调用set:
export function toggle( scope: string, name: string ) { return function ( { select, dispatch } ) { const currentValue = select.get( scope, name ); dispatch.set( scope, name, ! currentValue ); }; }因此它天然支持「读默认值 → 取反 → 写入」的完整链路:即使某偏好从未被设置过,toggle也会基于默认值正确翻转。
4.4setPersistenceLayer:配置持久化层
- 参数:
persistenceLayer(WPPreferencesPersistenceLayer<D>) - 返回:
Promise<SetPersistenceLayerAction<D>>
这是整个 store 最有设计含量的部分。持久化层接口定义在 packages/preferences/src/store/types.ts:
export interface WPPreferencesPersistenceLayer<D extends Object> { get: () => Promise<D>; // 异步读取,返回 Promise set: ( value: D ) => void; // 同步写入,fire-and-forget }setPersistenceLayer动作本身是一个async动作:先await persistenceLayer.get()拿到已持久化的数据,再派发包含persistenceLayer与persistedData的动作对象:
export async function setPersistenceLayer<D extends Object>( persistenceLayer: WPPreferencesPersistenceLayer<D> ): Promise<SetPersistenceLayerAction<D>> { const persistedData = await persistenceLayer.get(); return { type: 'SET_PERSISTENCE_LAYER', persistenceLayer, persistedData }; }文档明确说明,设置持久化层后 store 会做两件事:
- 立即调用
get,用返回值初始化 store 状态; - 每当任一偏好变化时调用
set,传入全部偏好。
这两条行为都能在 reducer.ts 的withPersistenceLayer高阶 reducer 中找到实现:收到SET_PERSISTENCE_LAYER时把persistedData直接作为新状态返回;收到SET_PREFERENCE_VALUE时在算出nextState后调用persistenceLayer?.set( nextState )。
文档特别提醒:
setPersistenceLayer应尽量在应用生命周期最开头派发,先于任何其他动作,否则此前的内存状态会被持久化数据覆盖。
对应单元测试见 packages/preferences/src/store/test/actions.ts:它验证了派发结果确实同时包含 persistenceLayer 对象与get()的返回结果。
五、实战一:初始化默认值与读写偏好
以官方 README 示例为骨架,完整的初始化 + 读写流程如下:
import { dispatch, select } from '@wordpress/data'; import { store as preferencesStore } from '@wordpress/preferences'; function initialize() { // 1. 应用启动时设置默认值(不会持久化) dispatch( preferencesStore ).setDefaults( 'namespace/editor-or-plugin-name', { myBooleanFeature: true, panelWidth: 300, } ); // 2. 读取偏好(未设置时回退到默认值) const enabled = select( preferencesStore ).get( 'namespace/editor-or-plugin-name', 'myBooleanFeature' ); // true(默认值) // 3. 写入偏好 dispatch( preferencesStore ).set( 'namespace/editor-or-plugin-name', 'panelWidth', 480 ); // 4. 再次读取,得到新值 const width = select( preferencesStore ).get( 'namespace/editor-or-plugin-name', 'panelWidth' ); // 480 }如果通过全局对象访问(未使用 ESM 导入时),等价写法为:
wp.data .select( 'core/preferences' ) .get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // 1 wp.data .dispatch( 'core/preferences' ) .set( 'namespace/editor-or-plugin-name', 'myPreferenceName', 2 ); wp.data .select( 'core/preferences' ) .get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // 2开关类偏好使用toggle:
wp.data .select( 'core/preferences' ) .get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // true wp.data .dispatch( 'core/preferences' ) .toggle( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); wp.data .select( 'core/preferences' ) .get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // false六、实战二:接入 localStorage 持久化
默认情况下 store 只做内存存储,刷新页面即丢失。接入localStorage的最小示例(来自官方 README):
wp.data.dispatch( 'core/preferences' ).setPersistenceLayer( { // get 是异步的,以支持未来通过 REST API 持久化。 // 它在 setPersistenceLayer 派发时立即被调用,返回值作为偏好初始状态。 async get() { return JSON.parse( window.localStorage.getItem( 'MY_PREFERENCES' ) ); }, // set 是同步的。使用异步代码也可以,但 store 不会等待 Promise 完成, // 该函数是 "fire and forget"(即发即忘)语义。 set( preferences ) { window.localStorage.setItem( 'MY_PREFERENCES', JSON.stringify( preferences ) ); }, } );需要说明:get中JSON.parse的结果可能为null(键不存在时),从源码看此时persistedData为null,state 会被直接替换为该值,因此生产代码建议先做空值兜底,例如JSON.parse(...) ?? {}。
七、实战三:异步 API 持久化与预加载缓存
文档专门讨论了「把偏好持久化到异步 API」场景下的启动性能问题:如果每次启动都先发一次异步请求读取偏好,会拖慢应用启动。
官方给出的推荐做法是预加载 + 本地缓存:
// 从服务端预加载的数据 let cache = preloadedData; wp.data.dispatch( 'core/preferences' ).setPersistenceLayer( { async get() { if ( cache ) { return cache; } // 调用某个异步 API return await api.preferences.get(); }, set( preferences ) { cache = preferences; api.preferences.set( { data: preferences } ); }, } );要点有二:
- 缓存写回:
set中先更新本地cache再发异步请求,保证后续get能立即命中缓存; - 防御未来变更:文档指出当前
get只在setPersistenceLayer派发时调用一次,但未来可能改变调用时机,因此用本地缓存优化get是稳妥的做法。
八、UI 组件:PreferenceToggleMenuItem
除了命令式 API,@wordpress/preferences还提供 React 组件PreferenceToggleMenuItem,可与DropdownMenu组合实现「菜单中的偏好开关」:
function MyEditorMenu() { return ( <DropdownMenu> { () => ( <MenuGroup label={ __( 'Features' ) }> <PreferenceToggleMenuItem scope="namespace/editor-or-plugin-name" name="myPreferenceName" label={ __( 'My feature' ) } info={ __( 'A really awesome feature' ) } messageActivated={ __( 'My feature activated' ) } messageDeactivated={ __( 'My feature deactivated' ) } /> </MenuGroup> ) } </DropdownMenu> ); }该组件实现位于 packages/preferences/src/components/preference-toggle-menu-item/index.tsx,内部即通过toggle动作驱动状态翻转,并把messageActivated/messageDeactivated作为操作反馈提示。同目录还提供了PreferenceBaseOption、PreferenceToggleControl、PreferencesModal等基础组件(见 packages/preferences/src/components),可在需要时组合出完整的偏好设置弹窗。
九、实现机制小结与使用注意
把文档与源码对照,可归纳出该 store 的完整数据流:
setDefaults ──► defaults reducer(仅内存,不持久化) set ──► SET_PREFERENCE_VALUE ──► preferences reducer ──► withPersistenceLayer ──► persistenceLayer.set(全部偏好) setPersistenceLayer ──► await persistenceLayer.get() ──► 以持久化数据替换 state get ──► state.preferences[scope][name] ?? state.defaults[scope][name]使用时的注意事项汇总:
- 作用域隔离:不同功能模块务必使用不同 scope(如
core/edit-post、namespace/plugin-name),避免键名冲突; - 默认值只在初始化设置:
setDefaults应发生在应用启动阶段,且默认值永不落盘; - 持久化层尽早挂载:
setPersistenceLayer应最先派发,否则早期内存改动会被持久化数据覆盖; - Value 类型受持久化限制:若使用 JSON 持久化,只存放 JSON 可序列化数据;
- 编辑器级偏好迁移:涉及
fixedToolbar、distractionFree等 16 个键时,请使用core作用域并避免触发 6.5 起的废弃警告; get与set支持任意类型值,toggle仅适用于布尔语义的偏好。
十、延伸阅读
- 数据层参考指南总览:docs/reference-guides/data/README.md
- 包级文档(含 Key concepts 与全部示例):packages/preferences/README.md
- 选择器实现:packages/preferences/src/store/selectors.ts
- 动作实现:packages/preferences/src/store/actions.ts
- reducer 与持久化机制:packages/preferences/src/store/reducer.ts
- 类型定义(
WPPreferencesPersistenceLayer、StoreState):packages/preferences/src/store/types.ts - 单元测试:selectors、actions、reducer
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考