定义feature
feature 是 Oak 前端运行时中一个非常重要、但又很容易被忽视的概念。
简单地说,feature 就是被多个组件共享的前端状态与服务对象。它通常用来封装:
- 某类前端状态;
- 一组对 cache / localStorage / token / aspect 的组合调用;
- 某种可跨页面复用的业务工具能力。
如果说 aspect 更像“后端业务服务入口”,那么 feature 更像“前端业务服务对象”。
feature 不是业务一致性层
首先要建立一个很重要的边界:
feature 只运行在前端,不承担系统底层一致性职责。
这意味着:
- 需要保证正确性的约束,仍然应该写在
checker/trigger/ 权限体系里; - feature 负责的是前端组织、状态复用和调用便利性。
Oak 自带的基础 features
oak-frontend-base/src/initialize.ts 负责创建基础 features。当前 Oak 生成的 src/initialize.server.ts 再按依赖组合结果合入依赖 features,最后调用项目 src/features/index.ts 的 create(...) 并合入项目 features。旧的 initialize.frontend.ts 只用于历史 DebugConnector 场景。
这些基础 features 里,最重要的包括:
cacherunningTreelocalStoragelocalesmessagenotificationnavigatorportlocationenvironmentstylethemegeocontextMenuFactorysubscribersocket
因此,很多业务 feature 实际上就是在这些基础能力之上再做一层更贴近业务语义的封装。
自定义 feature 写在哪里
通常有两个关键文件:
src/features/index.tssrc/initializeFeatures.ts
src/features/index.ts
这里负责创建 feature 实例。当前 oak-general-business/template/featuresIndex.ts 给出的项目模板是:
export function create(features: BasicFeatures<EntityDict> & Ogb0FeatureDict<EntityDict>) {
const { cache, localStorage, token } = features;
const sample = new Sample(cache);
const console = new Console(cache, localStorage, token, () => ({
id: 1,
name: 1,
}));
const aspect = createService<EntityDict, MergeAspectDict>(cache);
return {
sample,
console,
aspect: aspect as typeof aspect & Feature,
};
}
从这个例子中可以看到,自定义 feature 的常见依赖来源主要是:
cachelocalStoragetoken- 依赖模块提供的 feature
src/initializeFeatures.ts
这里负责初始化 feature。当前 general-business 项目模板中会初始化依赖 feature,并加载项目 locale:
export default async function initialize(features: FeatureDict & BasicFeatures<EntityDict> & Ogb0FeatureDict<EntityDict>) {
await initializeOgb0Features(features, accessConfiguration, undefined, [Qiniu]);
features.locales.loadServerData(['projectName-l-common', 'projectName-l-error', 'projectName-l-menu']);
}
这个文件的职责通常包括:
- 调用依赖模块的
initialize(...); - 注册 selection / operation rewriter;
- 加载服务端 i18n 数据;
- 注册文件存储、SDK、全局配置等前端启动逻辑。
什么时候应该抽一个 feature
下面这些情况,通常值得单独抽 feature:
- 一段逻辑会被多个页面复用;
- 这段逻辑依赖多个基础 feature 组合调用;
- 这段逻辑有自己的状态,需要跨组件共享;
- 你不希望把复杂调用链直接堆在组件里。
例如 oak-general-business/src/features/index.ts 中,就把 token、application、extraFile、wechatSdk、humanVerify、invite 等能力封装成了 feature。theme 则是 oak-frontend-base 的基础 feature,不属于 general-business。组件层只需要使用合并后的 feature 字典,不需要知道内部具体如何操作 cache 或 localStorage。
在组件里如何使用 feature
OakComponent 本身就支持声明依赖的 features,并且组件实例上可以通过 this.features.xxx 访问。
同时,在 formData 中也可以拿到:
formData({ data, features }) {
return {
rows: data,
currentUserId: features.token.getCurrentUserId(),
};
}
因此,feature 是 Oak 组件和运行时能力之间最自然的桥梁。
一个很实用的经验
当你发现一个组件里开始出现下面这种代码味道时:
- 同时操作
cache、localStorage、token; - 同样一段查询和转换在多个页面里重复;
- 一大段“为了页面方便”而写的业务工具函数堆在组件文件里;
这通常就意味着,你应该把它抽成一个 feature 了。
feature 和 aspect 的关系
二者最常见的搭配方式是:
aspect负责后端业务入口;feature负责前端如何组织和调用这些入口。
这是一种非常自然的分层:
- 后端复杂动作写成 aspect;
- 前端再把这些 aspect 包装成更顺手的业务对象。
这样组件代码会轻很多,也更容易维护。