ViewModel 实现
defineViewModel 是创建 ViewModel 的核心函数。它接收一个 Model 构造函数和一个属性定义函数,返回一个配置好的 ViewModel 类。
定义 ViewModel
ViewModel 有两种等价的定义方式:
// 1. 赋值给常量
const MyViewModel = defineViewModel(MyBuilder, (helper) => ({
// 属性定义
}));// 2. 继承 defineViewModel 的返回值
class MyViewModel extends defineViewModel(MyBuilder, (helper) => ({
// 属性定义
})) {}两种方式的运行时行为相同。局部使用或简单的 ViewModel 可以使用第一种写法。对于需要从包中导出的 ViewModel,推荐第二种写法:
export class MyViewModel extends defineViewModel(MyBuilder, (helper) => ({
// 属性定义
})) {}类声明会提供可直接引用的具名类型 MyViewModel。因此,其他导出类型和生成的 .d.ts 都能方便地引用它;常量形式的类型通常需要通过 typeof MyViewModel 或 InstanceType<typeof MyViewModel> 间接引用。
defineViewModel 签名
function defineViewModel<
ModelT,
const BlockDef extends PartialAttributeBlockDefinition,
CtorArgs extends any[] = [],
InitMeta = unknown,
>(
Ctor: new (...args: CtorArgs) => ModelT,
modelDefFn: (helper: AttributeDefHelper<ModelT>) => BlockDef,
initMeta?: InitMeta,
): IViewModel<ModelT, BlockDef & { "~meta": InitMeta }, CtorArgs>;Ctor— Model 类的构造函数;它可以声明 ViewModel 的parse()所需的构造参数modelDefFn— 属性定义函数,接收一个 helper,返回属性定义对象initMeta— 初始 Meta 类型(仅用于类型层面的状态追踪,不产生运行时行为)
完整示例:CharacterViewModel
参考 genius-invokation 的 core/gts/vm_impl/character.ts:
import { defineViewModel } from "@gi-tcg/gts-runtime";
class CharacterBuilder {
id: CharacterHandle = null!;
since: string = "";
tags: Tag[] = [];
health: number = 0;
energy: number = 0;
skills: CharacterSkillHandle[] = [];
getEntry(): CharacterEntry {
return {
type: "character",
id: this.id,
tags: this.tags,
health: this.health,
energy: this.energy,
skills: this.skills,
};
}
}
export class CharacterVM extends defineViewModel(
CharacterBuilder,
(helper) => ({
id: helper.attribute<{
(id: number): AR.Done;
required(): true;
as(): CharacterHandle;
}>(
(model, [id]: [number]) => {
model.id = id as CharacterHandle;
},
(_, [id]) => id as CharacterHandle,
),
since: helper.simpleAttribute()(function (version: string) {
this.since = version;
}),
tags: helper.simpleAttribute()(function (...tags: Tag[]) {
this.tags.push(...tags);
}),
health: helper.simpleAttribute()(function (value: number) {
this.health = value;
}),
energy: helper.simpleAttribute()(function (value: number) {
this.energy = value;
}),
skills: helper.simpleAttribute()(function (...handles: CharacterSkillHandle[]) {
this.skills.push(...handles);
}),
}),
{} as { varNames: never },
) {}属性定义的两种方式
helper.attribute<T>(action, binder?)
完全控制 action 和 binder:
helper.attribute<{
(id: number): AR.Done; // 类型签名(位置参数类型 + 返回值类型)
required(): true; // 标记为必需属性
as(): CharacterHandle; // binder 的返回类型
}>(
// action: 如何操作 Model
(model, [id]) => { model.id = id as CharacterHandle; },
// binder: 如何提取绑定值(as Name 时使用)
(_, [id]) => id as CharacterHandle,
)Action 签名:
type AttributeAction<ModelT> = (
model: ModelT,
positionals: unknown[],
namedView: View<...> | null,
) => unknown;model— Model 实例(this不是 Model,需要显式传参)positionals— 位置参数数组(如id 1201→[1201])namedView— 如果此属性有嵌套块(如on enter { ... }),则为嵌套的属性视图;否则为null
Binder 签名:
type AttributeBinder<ModelT> = (
model: ModelT,
positionals: unknown[],
namedView: View<...> | null,
) => unknown;Binder 的返回值会在 as Name 导出时被收集。
helper.simpleAttribute(options?)
更简洁的定义方式——直接在 this 上操作 Model:
helper.simpleAttribute(options?)(
actionWithThis, // this = ModelT
binderWithThis?, // this = ModelT
)示例:
helper.simpleAttribute()(function(value: number) {
this.health = value;
});选项:
helper.simpleAttribute({ required: true, uniqueKey: "myKey" })required— 标记为必需属性(未填写时触发类型错误)uniqueKey— 确保此属性在一个实体中只能出现一次
AR 返回类型
AttributeReturn (alias AR) 命名空间定义了属性的返回类型标注:
AR.Done; // 无嵌套块,不修改 meta
AR.This<TMeta>; // 访问当前 meta 类型
AR.EnableIf<Cond, T>; // 条件类型
AR.With<VMI, TMeta>; // 属性打开一个嵌套 VM 块
AR.DoneRewriteMeta<NewMeta>; // 属性重写 meta
AR.WithRewriteMeta<NewMeta, VMI>; // 打开嵌套 VM + 重写 meta这些类型在 TypeScript Interop 中用于为 IDE 提供精确的类型信息,不影响运行时逻辑。
Root ViewModel — 根调度
根 ViewModel (vm_impl/index.ts) 通过属性名 dispatch 到子 ViewModel:
const RootVM = defineViewModel(RootBuilder, (helper) => ({
character: helper.attribute<{
(): AR.With<CharacterVM>;
}>(
(_, __, view) => CharacterVM.parse(view),
),
skill: helper.attribute<{
(): AR.With<CharacterSkillVM>;
}>(
(_, __, view) => CharacterSkillVM.parse(view),
),
status: helper.attribute<{
(): AR.With<StatusVM>;
}>(
(_, __, view) => EntityVM("status").parse(view),
),
// ... 其他实体类型
}));完整的 ViewModel 实现参考见
genius-invokation/packages/core/src/gts/vm_impl/下的各文件。尤其是index.ts(RootVM)、character.ts(角色)和entity.ts(实体)是最完整的参考实现。