如何将绑定到实例的类方法复制到打字稿中的普通对象?

问题描述 投票:0回答:1

我正在为 Electron 编写一个预加载脚本,在预加载脚本中我们导入一个类,创建一个实例并希望在渲染中公开它。根据 Electron 文档,我们无法通过上下文桥复制类。

结果,我创建了一个对象,其键与类的方法名称匹配,值作为类函数绑定到类的实例,如下所示:

// What I am given
class Calculator {
  add(a: number, b: number) { return a + b }
}

const instance = new Calculator();

// What I am trying to create programmatically
const calcObj = {
  add: Calculator.prototype.add.bind(instance)
}

问题是我想要公开的真实类有很多方法,并且有很多类,所以我希望我可以以编程方式创建 calcObj 并保留打字稿类型

尝试以编程方式执行此操作时,这是我能想到的“最好”的事情:

class Calculator {
  add(a: number, b: number) {
    return a + b;
  }
  subtract(a: number, b: number) {
    return a - b;
  }
  multiply(a: number, b: number) {
    return a * b;
  }
  divide(a: number, b: number) {
    return a / b;
  }
  stringify() {
    return this.randomValue.toString();
  }

  randomValue = 0;
}

// Incorrect type, as it includes class variables (randomValue). Willing to live with this though
type ResultObject = {
  [K in keyof Calculator]: Calculator[K];
};

const instance = new Calculator();

// Maybe there is a better way to get class function names in a list?
const protoKeys = Object.getOwnPropertyNames(Calculator.prototype) as (keyof Calculator)[];

export const objCapture = protoKeys.reduce((acc, key) => {
  const fn = Calculator.prototype[key];
  if (typeof fn !== 'function') {
    return acc;
  }
  console.log('Copying function', key);
  // Typescript ERROR here, the result of fn.bind can be any 
  // of the function types on the class, which cannot be assigned to any of the acc[key]
  acc[key] = fn.bind(instance);
  return acc;
}, {} as ResultObject);

console.log(objCapture);

通过这个实现,我在执行

acc[key] = fn.bind(instance)
时遇到类型错误,其中 fn 是
Calculator.prototype[key]
,因为它不知道 fn 是哪个函数(可以是 add、stringify 等中的任何一个),并且它不知道我们要使用 acc 的哪个键正在使用。此外,我的 ResultObject 从技术上讲也不准确,因为它包含类变量(例如 randomValue)。

是否有更好的方法来创建绑定到类实例的对象,例如

typescript types electron
1个回答
0
投票

TypeScript 无法在类型级别区分属性是“自己的”还是“继承的”。因此,当您查看 Calculator.prototype 时,TypeScript 只是为其提供类型 Calculator,这并不完全准确,因为它包含像

randomValue()
这样的实例属性。但每当提出这个问题时(例如,
microsoft/TypeScript#55904
),TS 团队通常只是说它就是这样,并且可能不会改变。
如果你假设类实例的所有非函数属性都直接属于该实例,并且类的所有函数属性都是属于原型的方法,那么你可以使用 key remapping 过滤掉所有非函数属性函数属性以获得原型类型的近似值:

type ResultObject = { [K in keyof Calculator as Calculator[K] extends Function ? K : never]: Calculator[K]; }; /* type ResultObject = { add: (a: number, b: number) => number; subtract: (a: number, b: number) => number; multiply: (a: number, b: number) => number; divide: (a: number, b: number) => number; stringify: () => string; } */

在此之后,我认为尝试让 TypeScript 相信你所做的事情是安全的没有多大意义。您已经
断言

{}

的初始 reduce() 对象属于

ResultObject
类型,因此您不妨断言
fn.bind(instance)
是像
any
 这样的松散类型,然后继续前进:
const objCapture = protoKeys.reduce((acc, key) => {
  const fn = Calculator.prototype[key];
  if (typeof fn !== 'function') {
    return acc;
  }
  console.log('Copying function', key);
  acc[key] = fn.bind(instance) as any; // <-- might as well
  return acc;
}, {} as ResultObject);
您可以

try
以这样的方式编写您的
reduce()

回调,以便编译器可以看到acc[key]

fn.bind(instance)
之间的关系,使用
microsoft/TypeScript#47109
中列出的技术远离
联合
并走向泛型,但解释起来很乏味且复杂: type ResultMethod<K extends keyof ResultObject> = (...args: Parameters<ResultObject[K]>) => ReturnType<ResultObject[K]> const objCapture = protoKeys.reduce(<K extends keyof ResultObject>( acc: { [P in K]: ResultMethod<P> }, key: K ) => { const proto: { [P in keyof ResultObject]: ResultMethod<P> } = Calculator.prototype; const fn: ResultMethod<K> = proto[key]; if (typeof fn !== 'function') { return acc; } acc[key] = fn.bind(instance); return acc; }, {} as ResultObject); 这确实有效,但是为什么要麻烦呢?看起来,除非您的用例非常非典型,否则通过让编译器验证

fn.bind(instance)
acc[key]

兼容而获得的 递减回报 不值得如此复杂。我不想花几个段落来解释它是如何在这里工作的(你可以阅读 ms/TS#47109),并且我不希望任何人维护上面的代码,而不是仅仅使用

as any
并小心。
Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.