我正在为 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 无法在类型级别区分属性是“自己的”还是“继承的”。因此,当您查看 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;
} */
断言
{}
的初始 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 代码链接