大佬教程收集整理的这篇文章主要介绍了使用 MyClass.defaults(options) 设置必需的构造函数选项时,如何使它们成为可选的,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
假设我有一个带有构造函数的类 Base
,它需要一个对象参数至少有一个 version
键。 Base
类还有一个静态 .defaults()
方法,它可以为它返回的新构造函数的任何选项设置默认值。
在代码中,这是我想要的
const test = new Base({
// `version` should be typed as required for the `Base` constructor
version: "1.2.3"
})
const MyBaseWithDefaults = Base.defaults({
// `version` should be typed as optional for `.defaults()`
foo: "bar"
})
const MyBaseWithVersion = Base.defaults({
version: "1.2.3",foo: "bar"
})
const testWithDefaults = new MyBaseWithVersion({
// `version` should not be required to be set at all
})
// should be both typed as string
testWithDefaults.options.version
testWithDefaults.options.foo
额外问题:如果因为 options
是通过 version
设置而不需要任何键,是否可以将构造函数 .defaults()
参数设为可选?
这是我到目前为止的代码:
interface Options {
version: string;
[key: string]: unkNown;
}
type Constructor<T> = new (...args: any[]) => T;
class Base<toptions extends Options = Options> {
static defaults<
TDefaults extends Options,S extends Constructor<Base<TDefaults>>
>(
this: S,defaults: Partial<TDefaults>
): {
new (...args: any[]): {
options: TDefaults;
};
} & S {
return class extends this {
constructor(...args: any[]) {
super(Object.assign({},defaults,args[0] || {}));
}
};
}
constructor(options: toptions) {
this.options = options;
};
options: toptions;
}
TypeScript playground
7 月 5 日更新
我应该提到级联默认值应该有效:Base.defaults({ one: "" }).defaults({ two: "" })
让我们来看看这些要求,并制定一个关于如何实施它们的粗略计划。
可以设置默认值的静态.defaults()
方法
这里的想法是在原始构造函数(即子构造函数)“上方”创建一个结构。此结构将采用默认值和其余值,将它们组合成一个对象,并将其提供给原始构造函数。实际上,您在这方面非常接近。这个设置的类型肯定会包括泛型,但如果你熟悉泛型,这应该不是问题。
如果不需要任何键,则将构造函数 options
参数设为可选
这部分问题实际上比您想象的要棘手。要实现它,我们必须使用:
keyof
运算符在应用于没有属性的对象 ({}
) 时会产生 never
(“空对象永远没有属性,因此也没有键”) ;级联默认值:Base.defaults({ one: "" }).defaults({ two: "" })
因为 .defaults()
的结果是一个子类(见上文),它必须具有其父类的所有静态成员,包括 .defaults()
本身。所以,从纯 JavaScript 的角度来看,没有什么新的实现,它应该已经可以工作了。
然而,在 TypeScript 中,我们遇到了一个大问题。 .defaults()
方法必须能够访问当前类的默认值,以便为新的默认值生成类型,这些类型由旧对象和新对象组合而成。例如,给定标题中的情况,为了得到 { one: string } & { two: string }
,我们直接从参数推断 { two: string }
(新默认值),并从其他地方推断 { one: string }
(旧默认值)。最好的地方是类的类型参数(例如,class Base<Defaults extends Options>
),但这里是交易:static members cannot reference class type parameters。
有 a workaround for this,但是,它需要一些半合理的假设和一点点放弃 DRY。最重要的是,您不能再以声明方式定义类,您必须命令式(也称为“动态”)创建继承链的第一个“最顶层”成员(如 const Class = createClass();
),我个人觉得相当不幸(尽管它工作得很好)。
说了这么多,下面是结果(还有一个 playground;随意折叠/删除 <TRIAGE>
部分):
type WithOptional<
OriginalObject extends object,OptionalKey extends keyof OriginalObject = never,> = Omit<OriginalObject,OptionalKey> & Partial<Pick<OriginalObject,OptionalKey>>;
type KeyOfByValue<Obj extends object,Value> = {
[Key in keyof Obj]: Obj[Key] extends Value ? Key : never;
}[keyof Obj];
type RequiredKey<Obj extends object> =
Exclude<KeyOfByValue<Obj,Exclude<Obj[keyof Obj],undefined>>,undefined>;
type OptionalParamIfEmpty<Obj extends object> =
RequiredKey<Obj> extends never ? [ Obj? ] : [ Obj ];
function createClass<
Options extends object,OptionalKey extends keyof Options = never,>(
defaults?: Pick<Options,OptionalKey>,Parent: new(options: Options) => object = Object
) {
return class Class extends Parent {
static defaults<
OptionalKey2 extends keyof Options,>(
additionalDefaults: Pick<Options,OptionalKey2>,) {
const newDefaults = { ...defaults,...additionalDefaults } as Options;
return createClass<Options,OptionalKey | OptionalKey2>(newDefaults,this);
}
public options: Options;
constructor(
...[explicit]: OptionalParamIfEmpty<WithOptional<Options,OptionalKey>>
) {
const options = { ...defaults,...explicit } as Options;
super(options);
this.options = options;
}
}
}
分解:
createClass()
应该只被显式调用以创建继承链中的第一个类(后续子类通过 .defaults()
调用创建)。
createClass()
需要(全部 - 可选):
options
属性的类型定义;options
摘录(defaults
对象,函数的第一个值参数);Object
(所有对象的公共父类)。应该明确提供 Options
中的 createClass()
类型参数。
OptionalKey
中的 createClass()
类型参数是根据提供的 defaults
对象的类型自动推断的。
createClass()
返回一个具有更新类型 constructor()
的类,即 defaults
中已经存在的属性在 explicit
中不再需要。>
如果所有 options
属性都是可选的,则 explicit
参数本身变为可选。
由于返回类的整个定义都放在函数内部,所以它的 .defaults()
方法可以通过闭包直接访问上面的 defaults
对象。这允许该方法只需要额外的默认值;然后将两组默认值合并到一个对象中,并与当前类的定义一起传递给 createClass(defaults,Parent)
以创建一个具有预填充默认值的新子类。
由于返回的类需要在构造函数的某处调用 super()
,为了一致性,父类的构造函数被强制采用 options: Options
作为它的第一个参数。然而,构造函数完全有可能忽略这个参数;这就是为什么在 super()
调用之后,options
属性的值无论如何都会被显式设置。
interface Options {
version: string;
[key: string]: unknown;
}
type CalssType = abstract new (...args: any) => any;
// obtain parameters of constructor
type Params = ConstructorParameters<typeof Base>
// obtain instance type
type Instance = InstanceType<typeof Base>
// make first element of tuple Partial
type FstPartial<Tuple extends any[]> = Tuple extends [infer Fst,...infer Tail] ? [Partial<Fst>,...Tail] : never
// clone constructor type and replace first argument in rest parameters with partial
type GetConstructor<T extends CalssType> = new (...args: FstPartial<ConstructorParameters<T>>) => InstanceType<T>
type Constructor<T> = new (...args: any[]) => T;
class Base<TOptions extends Options = Options> {
static defaults<
TDefaults extends Options,S extends Constructor<Base<TDefaults>>
>(
this: S,defaults: Partial<TDefaults>
): {
new(...args: any[]): {
options: TDefaults;
};
} & GetConstructor<typeof Base> { // <--- change is here
return class extends this {
constructor(...args: any[]) {
super(Object.assign({},defaults,args[0] || {}));
}
};
}
constructor(options: TOptions) {
this.options = options;
};
options: TOptions;
}
const test = new Base({
// `version` should be typed as required for the `Base` constructor
version: "1.2.3"
})
const MyBaseWithDefaults = Base.defaults({
// `version` should be typed as optional for `.defaults()`
foo: "bar"
})
const MyBaseWithVersion = Base.defaults({
version: "1.2.3",foo: "bar"
})
const testWithDefaults = new MyBaseWithVersion({}) // ok
// should be both typed as string
testWithDefaults.options.version
testWithDefaults.options.foo
Playground
,在 Josh Goldberg 的帮助下,我们得出结论,无法使用当今的 TypeScript 键入无限可链接的 Base.defaults().defaults()...
API。
我们改用的是实现最多 3 个 .defaults()
调用的链接,这仍然会在实例上正确设置 .options
属性。
这是我们最终实现的完整类型声明文件。它有点复杂,因为它还包含 .plugin()
/.plugins
API,为了简单起见,我从最初的问题中省略了该 API。
export declare namespace Base {
interface Options {
version: string;
[key: string]: unknown;
}
}
declare type ApiExtension = {
[key: string]: unknown;
};
declare type Plugin = (
instance: Base,options: Base.Options
) => ApiExtension | void;
declare type Constructor<T> = new (...args: any[]) => T;
/**
* @author https://stackoverflow.com/users/2887218/jcalz
* @see https://stackoverflow.com/a/50375286/10325032
*/
declare type UnionToIntersection<Union> = (
Union extends any ? (argument: Union) => void : never
) extends (argument: infer Intersection) => void
? Intersection
: never;
declare type AnyFunction = (...args: any) => any;
declare type ReturnTypeOf<T extends AnyFunction | AnyFunction[]> =
T extends AnyFunction
? ReturnType<T>
: T extends AnyFunction[]
? UnionToIntersection<Exclude<ReturnType<T[number]>,void>>
: never;
type ClassWithPlugins = Constructor<any> & {
plugins: any[];
};
type ConstructorRequiringVersion<Class extends ClassWithPlugins,PredefinedOptions> = {
defaultOptions: PredefinedOptions;
} & (PredefinedOptions extends { version: string }
? {
new <NowProvided>(options?: NowProvided): Class & {
options: NowProvided & PredefinedOptions;
};
}
: {
new <NowProvided>(options: Base.Options & NowProvided): Class & {
options: NowProvided & PredefinedOptions;
};
});
export declare class Base<TOptions extends Base.Options = Base.Options> {
static plugins: Plugin[];
/**
* Pass one or multiple plugin functions to extend the `Base` class.
* The instance of the new class will be extended with any keys returned by the passed plugins.
* Pass one argument per plugin function.
*
* ```js
* export function helloWorld() {
* return {
* helloWorld () {
* console.log('Hello world!');
* }
* };
* }
*
* const MyBase = Base.plugin(helloWorld);
* const base = new MyBase();
* base.helloWorld(); // `base.helloWorld` is typed as function
* ```
*/
static plugin<
Class extends ClassWithPlugins,Plugins extends [Plugin,...Plugin[]],>(
this: Class,...plugins: Plugins,): Class & {
plugins: [...Class['plugins'],...Plugins];
} & Constructor<UnionToIntersection<ReturnTypeOf<Plugins>>>;
/**
* Set defaults for the constructor
*
* ```js
* const MyBase = Base.defaults({ version: '1.0.0',otherDefault: 'value' });
* const base = new MyBase({ option: 'value' }); // `version` option is not required
* base.options // typed as `{ version: string,otherDefault: string,option: string }`
* ```
* @remarks
* Ideally,we would want to make this infinitely recursive: allowing any number of
* .defaults({ ... }).defaults({ ... }).defaults({ ... }).defaults({ ... })...
* However,we don't see a clean way in today's TypeScript syntax to do so.
* We instead artificially limit accurate type inference to just three levels,* since real users are not likely to go past that.
* @see https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/pull/57
*/
static defaults<
PredefinedOptionsOne,ClassOne extends Constructor<Base<Base.Options & PredefinedOptionsOne>> & ClassWithPlugins
>(
this: ClassOne,defaults: PredefinedOptionsOne
): ConstructorRequiringVersion<ClassOne,PredefinedOptionsOne> & {
defaults<ClassTwo,PredefinedOptionsTwo>(
this: ClassTwo,defaults: PredefinedOptionsTwo
): ConstructorRequiringVersion<
ClassOne & ClassTwo,PredefinedOptionsOne & PredefinedOptionsTwo
> & {
defaults<ClassThree,PredefinedOptionsThree>(
this: ClassThree,defaults: PredefinedOptionsThree
): ConstructorRequiringVersion<
ClassOne & ClassTwo & ClassThree,PredefinedOptionsOne & PredefinedOptionsTwo & PredefinedOptionsThree
> & ClassOne & ClassTwo & ClassThree;
} & ClassOne & ClassTwo;
} & ClassOne;
static defaultOptions: {};
/**
* options passed to the constructor as constructor defaults
*/
options: TOptions;
constructor(options: TOptions);
}
export {};
这是将该功能添加到 javascript-plugin-architecture-with-typescript-definitions
模块的拉取请求。
https://github.com/gr2m/javascript-plugin-architecture-with-typescript-definitions/pull/59
以上是大佬教程为你收集整理的使用 MyClass.defaults(options) 设置必需的构造函数选项时,如何使它们成为可选的全部内容,希望文章能够帮你解决使用 MyClass.defaults(options) 设置必需的构造函数选项时,如何使它们成为可选的所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。