gpt4 book ai didi

javascript - 我们如何键入一个类工厂来生成一个给定对象字面量的类?

转载 作者:行者123 更新时间:2023-11-30 14:32:55 26 4
gpt4 key购买 nike

例如,我制作了一个名为 lowclass 的 JavaScript 库我想知道如何让它在 TypeScript 类型系统中工作。

该库允许我们通过将对象文字传递到 API 来定义一个类,如下所示,我想知道如何让它返回一个与编写常规 类有效相同的类型{}:

import Class from 'lowclass'

const Animal = Class('Animal', {
constructor( sound ) {
this.sound = sound
},
makeSound() { console.log( this.sound ) }
})

const Dog = Class('Dog').extends(Animal, ({Super}) => ({
constructor( size ) {
if ( size === 'small' )
Super(this).constructor('woof')
if ( size === 'big' )
Super(this).constructor('WOOF')
},
bark() { this.makeSound() }
}))

const smallDog = new Dog('small')
smallDog.bark() // "woof"

const bigDog = new Dog('big')
bigDog.bark() // "WOOF"

如您所见,Class()Class().extends() API 接受用于定义类的对象文字。

我如何输入此 API,以便最终结果是 AnimalDog 在 TypeScript 中的行为就像我使用原生 class Animal { }class Dog extends Animal{} 语法?

也就是说,如果我要将代码库从 JavaScript 切换到 TypeScript,在这种情况下我应该如何输入 API,以便最终结果是使用我用 lowclass 制作的类的人可以像使用常规类一样使用它们?

EDIT1:这似乎是一种简单的方法来键入我用 lowclass 创建的类,方法是用 JavaScript 编写它们并在 .d.ts 类型中声明常规 class {} 定义定义文件。将我的低级代码库转换为 TypeScript 似乎更加困难,甚至可能,这样它就可以在定义类时自动输入,而不是为每个类创建 .d.ts 文件。

EDIT2:我想到的另一个想法是我可以保留 lowclass 不变(JavaScript 类型为 any),然后当我定义类时,我可以使用 as SomeType 来定义它们 其中 SomeType 可以是同一文件内的类型声明。这可能比让 lowclass 成为 TypeScript 库以便类型是自动的更不枯燥,因为我必须重新声明我在使用 lowclass API 时已经定义的方法和属性。

最佳答案

好吧,我们需要解决几个问题才能使它以与 Typescript 类类似的方式工作。在我们开始之前,我在 Typescript 中完成以下所有编码 strict模式,如果没有它,某些键入行为将无法工作,如果您对解决方案感兴趣,我们可以确定所需的特定选项。

类型和值

在 typescript 中,类占有特殊的位置,因为它们代表一个值(构造函数是一个 Javascript 值)和一个类型。 const您定义的仅代表值(构造函数)。具有 Dog 的类型例如我们需要显式定义 Dog 的实例类型让它稍后可用:

const Dog =  /* ... */
type Dog = InstanceType<typeof Dog>
const smallDog: Dog = new Dog('small') // We can now type a variable or a field

构造函数的函数

第二个问题是constructor是一个简单的函数,不是构造函数, typescript 不会让我们调用 new在一个简单的函数上(至少不是在严格模式下)。为了解决这个问题,我们可以使用条件类型在构造函数和原始函数之间进行映射。该方法类似于 here但为了简单起见,我将只为几个参数编写它,您可以添加更多参数:

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type FunctionToConstructor<T, TReturn> =
T extends (a: infer A, b: infer B) => void ?
IsValidArg<B> extends true ? new (p1: A, p2: B) => TReturn :
IsValidArg<A> extends true ? new (p1: A) => TReturn :
new () => TReturn :
never;

构建类型

使用上面的类型,我们现在可以创建简单的 Class函数将接受对象文字并构建一个看起来像声明的类的类型。如果这里没有constructor字段,我们将假设一个空的构造函数,我们必须删除 constructor根据我们将返回的新构造函数返回的类型,我们可以用 Pick<T, Exclude<keyof T, 'constructor'>> 来做到这一点.我们还将保留一个字段 __original拥有对象字面量的原始类型,稍后会有用:

function Class<T>(name: string, members: T): FunctionToConstructor<ConstructorOrDefault<T>, Pick<T, Exclude<keyof T, 'constructor'>>> & { __original: T  }


const Animal = Class('Animal', {
sound: '', // class field
constructor(sound: string) {
this.sound = sound;
},
makeSound() { console.log(this.sound) // this typed correctly }
})

这个类型的方法

Animal以上声明,this在类型的方法中正确键入,这很好并且适用于对象文字。对于对象文字 this将具有对象文字中定义的函数中当前对象的类型。问题是我们需要指定 this 的类型扩展现有类型时,如 this将具有当前对象文字的成员加上基类型的成员。幸运的是,typescript 让我们可以使用 ThisType<T> 来做到这一点编译器使用并描述的标记类型 here

创建扩展

现在使用上下文 this,我们可以创建 extends功能,唯一要解决的问题是我们需要查看派生类是否有自己的构造函数,或者我们可以使用基类构造函数,用新类型替换实例类型。

type ReplaceCtorReturn<T, TReturn> =
T extends new (a: infer A, b: infer B) => void ?
IsValidArg<B> extends true ? new (p1: A, p2: B) => TReturn :
IsValidArg<A> extends true ? new (p1: A) => TReturn :
new () => TReturn :
never;
function Class(name: string): {
extends<TBase extends {
new(...args: any[]): any,
__original: any
}, T>(base: TBase, members: (b: { Super : (t: any) => TBase['__original'] }) => T & ThisType<T & InstanceType<TBase>>):
T extends { constructor: infer TCtor } ?
FunctionToConstructor<ConstructorOrDefault<T>, InstanceType<TBase> & Pick<T, Exclude<keyof T, 'constructor'>>>
:
ReplaceCtorReturn<TBase, InstanceType<TBase> & Pick<T, Exclude<keyof T, 'constructor'>>>
}

综合起来:

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type FunctionToConstructor<T, TReturn> =
T extends (a: infer A, b: infer B) => void ?
IsValidArg<B> extends true ? new (p1: A, p2: B) => TReturn :
IsValidArg<A> extends true ? new (p1: A) => TReturn :
new () => TReturn :
never;

type ReplaceCtorReturn<T, TReturn> =
T extends new (a: infer A, b: infer B) => void ?
IsValidArg<B> extends true ? new (p1: A, p2: B) => TReturn :
IsValidArg<A> extends true ? new (p1: A) => TReturn :
new () => TReturn :
never;

type ConstructorOrDefault<T> = T extends { constructor: infer TCtor } ? TCtor : () => void;

function Class(name: string): {
extends<TBase extends {
new(...args: any[]): any,
__original: any
}, T>(base: TBase, members: (b: { Super: (t: any) => TBase['__original'] }) => T & ThisType<T & InstanceType<TBase>>):
T extends { constructor: infer TCtor } ?
FunctionToConstructor<ConstructorOrDefault<T>, InstanceType<TBase> & Pick<T, Exclude<keyof T, 'constructor'>>>
:
ReplaceCtorReturn<TBase, InstanceType<TBase> & Pick<T, Exclude<keyof T, 'constructor'>>>
}
function Class<T>(name: string, members: T & ThisType<T>): FunctionToConstructor<ConstructorOrDefault<T>, Pick<T, Exclude<keyof T, 'constructor'>>> & { __original: T }
function Class(): any {
return null as any;
}

const Animal = Class('Animal', {
sound: '',
constructor(sound: string) {
this.sound = sound;
},
makeSound() { console.log(this.sound) }
})

new Animal('').makeSound();

const Dog = Class('Dog').extends(Animal, ({ Super }) => ({
constructor(size: 'small' | 'big') {
if (size === 'small')
Super(this).constructor('woof')
if (size === 'big')
Super(this).constructor('WOOF')
},

makeSound(d: number) { console.log(this.sound) },
bark() { this.makeSound() },
other() {
this.bark();
}
}))
type Dog = InstanceType<typeof Dog>

const smallDog: Dog = new Dog('small')
smallDog.bark() // "woof"

const bigDog = new Dog('big')
bigDog.bark() // "WOOF"

bigDog.bark();
bigDog.makeSound();

希望这有帮助,如果我能提供更多帮助,请告诉我 :)

Playground link

关于javascript - 我们如何键入一个类工厂来生成一个给定对象字面量的类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50899400/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com