ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深入解读 Calypso Preferences 状态仓库:本地与持久化用户偏好管理实战指南

2026/9/28 3:19:02 拓冰建站 浏览量
深入解读 Calypso Preferences 状态仓库:本地与持久化用户偏好管理实战指南 前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载CalypsoThe JavaScript and API powered WordPress.com在client/state/preferences中实现了一套专用于用户偏好Preferences的状态仓库将「只存活于当前页面/会话的本地偏好」与「持久化到用户账号设置的远端偏好」统一收纳进 Redux 状态树并通过calypso_preferences键持久化在/me/settings上。阅读本文后你将掌握 QueryPreferences 的加载机制、setPreference与savePreference的取舍、getPreference的四级取值优先级、偏好 Key 的 JSON Schema 校验与默认值注册以及组件中接入偏好的完整可运行示例。概览偏好数据存到哪里本仓库是 Calypso 的客户端 Redux 状态仓库核心职责是持有并同步 Calypso 专属的用户偏好。与站点数据、主题数据等按资源模型存储的状态不同Preferences 是散列的「键值对集合」其定义与读取逻辑集中在入口说明文档client/state/preferences/README.mdAction 定义client/state/preferences/actions.jsReducer 定义client/state/preferences/reducer.jsSelector 定义client/state/preferences/selectors.js默认值与持久化键client/state/preferences/constants.jsJSON Schema 校验client/state/preferences/schema.js异步 Hookclient/state/preferences/use-async-preference.ts持久化的物理位置所有持久化偏好统一存放在用户设置接口/me/settings响应的calypso_preferences键下。也就是说远端只存在一个calypso_preferences对象Calypso 所有偏好 key 都是它的属性。这一点在 constants.js 中被定义为常量USER_SETTING_KEY calypso_preferences并在 actions.js 构造 payload 时直接引用。快速上手在组件中读写偏好文档给出了标准的接入流程共两步先渲染数据加载组件再通过connect把偏好值和方法注入组件。下面的示例完整还原了 README 中的用法对象recentSites是 Calypso 内建的一条偏好见下文默认值表import { connect } from react-redux; import { bindActionCreators } from redux; import QueryPreferences from calypso/components/data/query-preferences; import { getPreference } from calypso/state/preferences/selectors; import { savePreference } from calypso/state/preferences/actions; export default connect( ( state ) { return { recentSites: getPreference( state, recentSites ), }; }, ( dispatch ) { return bindActionCreators( { saveRecentSitesPreference: savePreference.bind( null, recentSites ), }, dispatch ); } )( MyExampleComponent );第一步渲染 QueryPreferences 触发加载QueryPreferences位于 client/components/data/query-preferences/index.jsx它本身不渲染任何 UI返回null只是一个纯副作用组件挂载时通过useEffectdispatch 一个fetchPreferencesthunk从而把远端偏好拉取进 Redux store。其内部还做了防重保护——只有isFetchingPreferences为假时才真正发起请求避免同一页面多个组件重复触发网络请求见 query-preferences/index.jsx。在路由级别的父组件中渲染一次即可例如function MyPage() { return ( div QueryPreferences / MyExampleComponent / /div ); }第二步connect 注入值与写操作getPreference( state, recentSites )负责取值savePreference.bind( null, recentSites )生成一个「固定了 key」的 action creator组件内只需调用this.props.saveRecentSitesPreference( value )即可写入并同步到远端。添加新的偏好 Key当需要新增偏好时有两处登记点职责分明JSON Schema必选用于校验持久化值在 client/state/preferences/schema.js 的remoteValuesSchema中为你的 key 定义类型约束。默认值可选在 client/state/preferences/constants.js 的DEFAULT_PREFERENCE_VALUES映射中给出初始值。remoteValuesSchema通过withSchemaValidation包裹在remoteValuesreducer 上见 reducer.js任何不符合 schema 的远端数据都不会被写进状态树从而保证下游组件拿到的永远是「形状正确」的数据。内建偏好一览默认值 校验规则下表完整整理了当前仓库中已注册的偏好偏好 key默认值Schema 类型约束guided-tours-history[]对象数组每项必含tourName(string)、timestamp(number, ≥0)、finished(boolean)recentSites[]数字数组站点 ID 列表mediaScale0.157number范围0~1homeQuickLinksToggleStatusexpandedstring枚举collapsed/expandedreader-seen-poststrueboolean此外 schema 还允许两类动态 keydismissible-card-任意后缀值为 boolean 或 object用于可关闭卡片的关闭状态time-mismatch-warning-数字值为 boolean 或 number用于按站点记录时区不匹配警告以及upwork-dismissible-banner、jetpack-review-prompt、persistent-counter等结构化对象含dismissedAt、dismissedCount、count等子字段完整定义见 schema.js。本地偏好 vs 持久化偏好两种写入方式文档特别强调了两种 action 的本质区别这正是本仓库设计的关键维度setPreference(key, value)savePreference(key, value)生命周期仅当前页面/会话跨会话持久化到用户账号网络请求无纯本地 dispatchPUT /me/preferences异步写入底层 actionPREFERENCES_SET先PREFERENCES_SET成功后再PREFERENCES_RECEIVEPREFERENCES_SAVE_SUCCESS适用场景临时 UI 状态、本次会话内才需要的值需要下次登录仍保留的用户设置实现细节印证了文档的描述见 actions.jssetPreference返回一个普通 action 对象PREFERENCES_SET不产生任何副作用savePreference是 thunk先同步 dispatchsetPreference让 UI 立即更新乐观更新随后构造 payload{ calypso_preferences: { key: value } }调用wpcom.req.put(/me/preferences)请求成功后 dispatchPREFERENCES_RECEIVE用服务端回传的最新值整体替换远端状态并派发PREFERENCES_SAVE_SUCCESS失败则派发PREFERENCES_SAVE_FAILURE记录错误。本地值的回收机制一个值得注意的细节当某个 key 通过savePreference保存成功后PREFERENCES_SAVE_SUCCESS会触发localValuesreducer 将该 key 从本地状态中移除见 reducer.js。原因是保存成功后该值已进入remoteValues无需再占用本地副本——这正是「本地值一旦落盘就移交远端」的闭环设计相关行为在 test/reducer.js 有专门测试覆盖。getPreference四级取值优先级getPreference( state, key )的取值策略见 selectors.js依次尝试四个来源命中即返回本地偏好状态state.preferences.localValues—— 当前会话内通过setPreference设置的临时值优先级最高远端持久化状态state.preferences.remoteValues—— 从/me/settings拉取或保存后回写的值默认值表DEFAULT_PREFERENCE_VALUES—— 尚未有任何写入时的兜底null—— 以上均未命中。对应测试 test/selectors.js 逐一验证了「未知 key 返回 null」「只有默认值可用时返回默认值」「远端值优先于默认值」「本地值优先于远端值」四种场景可作为理解该语义的权威参考。Reducer 状态切分preferences子树的 reducer 由 reducer.js 组合而成并通过withStorageKey(preferences, ...)挂载各字段职责如下状态字段默认值含义localValues{}仅本会话生效的偏好PREFERENCES_SET写入、PREFERENCES_SAVE_SUCCESS移除对应 keyremoteValuesnull持久化的远端偏好PREFERENCES_RECEIVE整体替换受 schema 校验fetchingfalse是否正在拉取远端偏好savingfalse是否有偏好正在保存PREFERENCES_SET置 true成功/失败置 falselastFetchedTimestampfalse最近一次成功拉取的时间戳Date.now()lastSaveErrornull最近一次保存失败的错误对象进阶useAsyncPreferenceHook 与加载状态除了手动connect仓库还提供了函数组件友好的 Hook client/state/preferences/use-async-preference.tsconst [ mediaScale, setMediaScale ] useAsyncPreference( { defaultValue: 0.157, preferenceName: mediaScale, } );其设计要点通过hasReceivedRemotePreferences判断远端偏好是否已加载未加载时返回占位值none加载完成后再用getPreference(...) ?? defaultValue填充真实值避免在远端数据到达前把默认值误写回本地setValue内部调用savePreference并同步更新本地 React state返回[value, setValue]元组remoteValues初始为null即表示尚未收到远端数据见 selectors.js这也是hasReceivedRemotePreferences的判定依据对于运行在 Jetpack 站点中的场景该 selector 直接返回config.isEnabled(is_running_in_jetpack_site)为真。底层调用链与测试验证一次完整的「读取 持久化」闭环可概括为QueryPreferences (mount) └─ fetchPreferences() GET /me/preferences └─ receivePreferences(values) PREFERENCES_RECEIVE → remoteValues └─ getPreference(state, key) 本地 → 远端 → 默认值 → null savePreference(key, value) ├─ setPreference(key, value) PREFERENCES_SET → localValues乐观更新 └─ PUT /me/preferences 成功 → PREFERENCES_RECEIVE PREFERENCES_SAVE_SUCCESS 失败 → PREFERENCES_SAVE_FAILURE仓库中的测试文件为上述行为提供了可执行的证据client/state/preferences/test/actions.js用useNock拦截public-api.wordpress.com/rest/v1.1/me/preferences验证fetchPreferences的FETCH/RECEIVE/SUCCESS/FAILURE全链路以及savePreference先发PREFERENCES_SET、成功后发PREFERENCES_RECEIVE与PREFERENCES_SAVE_SUCCESS含 403 失败分支client/state/preferences/test/reducer.js验证localValues的累积、去重、保存成功后移除remoteValues的整体替换与 undefined 兜底以及fetching状态翻转client/state/preferences/test/selectors.js验证getPreference的四级优先级与hasReceivedRemotePreferences的判定。适用建议优先在路由级统一渲染一次QueryPreferences避免每个子组件各自触发拉取QueryPreferences 内部已有防重机制但全局收敛更利于状态一致性区分会话级与持久级需求UI 折叠状态、本次会话内的临时标记用setPreference需要跨设备、跨登录保留的用户设置务必走savePreference新增 key 时同步补 schema持久化值没有 schema 约束会被withSchemaValidation拒之门外表现是组件拿到默认值而非你写入的值Hook 场景注意加载时序useAsyncPreference在远端未加载时返回none占位不要在此时执行依赖真实值的业务逻辑。赞分享前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载相关推荐DanmakuFactory开发者指南如何贡献代码与扩展功能的终极教程DanmakuFactory开发者指南如何贡献代码与扩展功能的终极教程 DanmakuFactory是一款强大的弹幕文件转换工具支持XML转ASS格式转换CLI桌面应用Nativefier窗口置顶状态保存用户偏好持久化Nativefier窗口置顶状态保存用户偏好持久化 问题背景与需求分析 在使用Nativefier将网页转换为桌面应用时许多用户需要频繁切换窗口置顶状态。当CLI桌面应用开发工具Verba前端状态持久化保存用户偏好设置Verba前端状态持久化保存用户偏好设置 为什么用户偏好持久化至关重要 你是否曾在使用Web应用时遇到过这样的困扰精心调整的主题颜色、字体大小和布局偏好人工智能大模型RAGAI 应用本地部署后端上一篇ramsey/uuid 版本 1 UUID 完全指南Gregorian 时间 UUID 的生成、节点自定义与时钟序列解析下一篇go.uber.org/multierr CHANGELOG 深度解读Go 多错误聚合库的 API 演进史与实现原理以 inngest 仓库为例创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考