You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript如何在MVVM模式下实现仅对View公开的类字段?

方案1:TypeScript 编译时校验(零运行时开销)

这个方案通过类型拆分实现权限控制,完全符合你期望的语法效果,开发阶段就能发现非法调用:
首先定义类型工具和view高阶函数的类型:

// 定义装饰器,标记仅View可访问的属性
const publicForView = (_target: any, _propertyKey: string) => {};

// 拆分ViewModel的两种对外接口
type PublicVM<T> = Omit<T, keyof { [K in keyof T as T[K] extends `${string}doSomethingInViewModel` ? K : never]: any }>;
type ViewAccessibleVM<T> = PublicVM<T> & Pick<T, { [K in keyof T as T[K] extends `${string}doSomethingInView` ? K : never][number] }>;

// view高阶函数的类型定义,仅这里会传入ViewAccessibleVM类型的实例
declare function view<T extends new (...args: any[]) => any>(
  VM: T,
  render: (props: { viewModel: ViewAccessibleVM<InstanceType<T>> }) => JSX.Element
): () => JSX.Element;

业务代码可以直接写成你期望的格式:

class ViewModel1 {
  // 公开方法,所有位置可调用
  doSomething = () => { console.log('doSomething') };

  // 仅View可访问的方法
  @publicForView
  doSomethingInView = () => { console.log('doSomethingInView') };

  // 类私有方法,仅内部可调用
  private doSomethingInViewModel = () => { console.log('doSomethingInViewModel') };
}

const View1 = view(ViewModel1, ({ viewModel }) => {
  viewModel.doSomethingInView(); // 正常调用
  viewModel.doSomething(); // 正常调用
  viewModel.doSomethingInViewModel(); // TS直接报错,无法调用
  return <div/>;
});

class ViewModel2 {
  private viewModel1: PublicVM<ViewModel1> = new ViewModel1();

  constructor() {
    this.viewModel1.doSomethingInView(); // TS直接报错,无法调用
    this.viewModel1.doSomething(); // 正常调用
    // @ts-expect-error 私有方法不可访问
    this.viewModel1.doSomethingInViewModel(); // 报错
  }
}

这个方案的优势是没有额外运行时代码,所有校验在编译阶段完成,符合常规前端开发习惯。

方案2:运行时权限拦截

如果需要防止编译绕过后的非法调用,可以在view高阶函数中用Proxy做运行时拦截:

// 存储每个VM实例对应的仅View可访问的属性名
const viewAccessibleProps = new WeakMap();
const publicForView = (target, propertyKey) => {
  if (!viewAccessibleProps.has(target.constructor)) {
    viewAccessibleProps.set(target.constructor, new Set());
  }
  viewAccessibleProps.get(target.constructor).add(propertyKey);
};

function view(VM, render) {
  return () => {
    const vm = new VM();
    // 只有在view高阶函数内部才返回带访问权限的Proxy
    const vmForView = new Proxy(vm, {
      get(target, prop) {
        // 私有方法直接不可访问
        if (prop.startsWith('#') || prop === 'doSomethingInViewModel') {
          throw new Error(`私有属性${String(prop)}不可访问`);
        }
        return target[prop];
      }
    });
    return render({ viewModel: vmForView });
  };
}

// 其他位置获取VM实例时,返回仅暴露公共方法的Proxy
function createPublicVM(VM) {
  const vm = new VM();
  const viewProps = viewAccessibleProps.get(VM) || new Set();
  return new Proxy(vm, {
    get(target, prop) {
      if (viewProps.has(prop) || prop.startsWith('#') || prop === 'doSomethingInViewModel') {
        throw new Error(`属性${String(prop)}不可访问`);
      }
      return target[prop];
    }
  });
}

使用时其他ViewModel获取的是公共VM实例,仅View组件能拿到带view专属权限的实例,运行时非法调用会直接抛出错误。

内容的提问来源于stack exchange,提问作者Yoskutik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 11:06:03