工厂缩小内部 switch 语句
Factory narrowing inside switch statement
是否可以通过文字类型缩小工厂创建方法的类型输出?
我已经设法通过 if 语句和有区别的联合缩小了一些范围,但这是创造性的方法,所以我不确定是否可行。
class Radio {
type: "RADIO"; // literal type
title: string = "A value";
selected: boolean = false;
constructor(radio?: Radio) {
}
}
class OptionFactory {
static create({
type,
price = 1.0,
title = "Option",
selected = false,
}: {
price: number;
title: string;
selected: boolean;
}) {
switch (type) {
case "RADIO":
return new Radio({
title,
selected,
// price,
});
case "CHECKBOX":
return new Checkbox({
title,
selected,
// price,
});
case "PRICEOPTION":
return new PriceOption({
title,
selected,
price,
});
}
}
}
let radioButtons = new Array<Radio>();
tags.push(OptionFactory.create({ type: "RADIO" })); //error ts(2345)
console.log(tags);
这个怎么样...
function createFrom({ type, price = 1.0, title = "Option", selected = false }: CreationOptions): FactoryReturn {
return factoryMap[type]({ price, title, selected });
}
const factoryMap: FactoryMap = {
"RADIO": ({ title, selected }: Titleable & Selectable) => {
return new Radio({ title, selected });
},
"CHECKBOX": ({ title, selected }: Titleable & Selectable) => {
return new Checkbox({ title, selected });
},
"PRICEOPTION": ({ price, title, selected }: Titleable & Selectable & Priceable) => {
return new PriceOption({ price, title, selected });
}
}
type Typeable = { type: keyof FactoryMap };
type Priceable = { price?: number };
type Titleable = { title?: string };
type Selectable = { selected?: boolean };
type FactorySelector = Extract<CreationOptions, Typeable>;
type FactoryReturn = ReturnType<FactoryMap[FactorySelector["type"]]>;
type CreationOptions = Typeable & Priceable & Titleable & Selectable;
type RadioConstructor = (option: Titleable & Selectable) => Radio;
type CheckboxConstructor = (option: Titleable & Selectable) => Checkbox;
type PriceOptionConstructor = (option: Titleable & Selectable & Priceable) => PriceOption;
type FactoryMap = {
"RADIO": RadioConstructor,
"CHECKBOX": CheckboxConstructor,
"PRICEOPTION": PriceOptionConstructor
}
class Radio {
constructor(option: Titleable & Selectable) {
console.log('[Radio]', '[constructor]', option);
}
}
class Checkbox {
constructor(option: Titleable & Selectable) {
console.log('[Checkbox]', '[constructor]', option);
}
}
class PriceOption {
constructor(option: Titleable & Selectable & Priceable) {
console.log('[PriceOption]', '[constructor]', option);
}
}
console.log(createFrom({ type: "RADIO" }));
console.log(createFrom({ type: "CHECKBOX" }));
console.log(createFrom({ type: "PRICEOPTION" }));
WYSIWYG
=> WHAT YOU SHOW IS WHAT YOU GET
这里有一个选项可以重新组织您的 classes 并提供一个工厂函数,该函数从初始化中提供的 "type"
属性 推断 class 实例的类型:
type BaseOptionInit = {
selected: boolean;
title: string;
};
class BaseOption<Type extends string> {
selected: boolean;
title: string;
readonly type: Type;
constructor (init: BaseOptionInit & { type: Type; }) {
this.selected = init.selected;
this.title = init.title;
this.type = init.type;
}
}
class Radio extends BaseOption<'RADIO'> {
constructor (init: BaseOptionInit) {
super({...init, type: 'RADIO'});
}
}
class Checkbox extends BaseOption<'CHECKBOX'> {
constructor (init: BaseOptionInit) {
super({...init, type: 'CHECKBOX'});
}
}
class PriceOption extends BaseOption<'PRICEOPTION'> {
price: number;
constructor (init: BaseOptionInit & { price: number; }) {
const {price, ...rest} = init;
super({...rest, type: 'PRICEOPTION'});
this.price = price;
}
}
type OptionType = 'CHECKBOX' | 'PRICEOPTION' | 'RADIO';
type OptionFactoryInit<T extends OptionType> = {
price?: number;
selected?: boolean;
title?: string;
type: T;
}
type OptionInstanceFromTypeName<T extends OptionType> = (
T extends 'CHECKBOX' ? Checkbox
: T extends 'PRICEOPTION' ? PriceOption
: T extends 'RADIO' ? Radio
: never
);
function createOption <T extends OptionType>(init: OptionFactoryInit<T>): OptionInstanceFromTypeName<T> {
const {price = 1, title = 'Option', selected = false} = init;
switch (init.type) {
case 'CHECKBOX': return new Checkbox({selected, title}) as any;
case 'PRICEOPTION': return new PriceOption({price, selected, title}) as any;
case 'RADIO': return new Radio({title, selected}) as any;
default: throw new Error('Invalid type');
}
}
// Example usage:
const priceOption = createOption({type: 'PRICEOPTION'});
priceOption.type // 'PRICEOPTION'
priceOption.price // number
const radios: Radio[] = [];
const radio = createOption({type: 'RADIO'});
radios.push(radio);
radio.type // 'RADIO'
radio.selected // string
radio.title // string
radio.price /* Expected error
~~~~~
Property 'price' does not exist on type 'Radio'.(2339) */
是否可以通过文字类型缩小工厂创建方法的类型输出? 我已经设法通过 if 语句和有区别的联合缩小了一些范围,但这是创造性的方法,所以我不确定是否可行。
class Radio {
type: "RADIO"; // literal type
title: string = "A value";
selected: boolean = false;
constructor(radio?: Radio) {
}
}
class OptionFactory {
static create({
type,
price = 1.0,
title = "Option",
selected = false,
}: {
price: number;
title: string;
selected: boolean;
}) {
switch (type) {
case "RADIO":
return new Radio({
title,
selected,
// price,
});
case "CHECKBOX":
return new Checkbox({
title,
selected,
// price,
});
case "PRICEOPTION":
return new PriceOption({
title,
selected,
price,
});
}
}
}
let radioButtons = new Array<Radio>();
tags.push(OptionFactory.create({ type: "RADIO" })); //error ts(2345)
console.log(tags);
这个怎么样...
function createFrom({ type, price = 1.0, title = "Option", selected = false }: CreationOptions): FactoryReturn {
return factoryMap[type]({ price, title, selected });
}
const factoryMap: FactoryMap = {
"RADIO": ({ title, selected }: Titleable & Selectable) => {
return new Radio({ title, selected });
},
"CHECKBOX": ({ title, selected }: Titleable & Selectable) => {
return new Checkbox({ title, selected });
},
"PRICEOPTION": ({ price, title, selected }: Titleable & Selectable & Priceable) => {
return new PriceOption({ price, title, selected });
}
}
type Typeable = { type: keyof FactoryMap };
type Priceable = { price?: number };
type Titleable = { title?: string };
type Selectable = { selected?: boolean };
type FactorySelector = Extract<CreationOptions, Typeable>;
type FactoryReturn = ReturnType<FactoryMap[FactorySelector["type"]]>;
type CreationOptions = Typeable & Priceable & Titleable & Selectable;
type RadioConstructor = (option: Titleable & Selectable) => Radio;
type CheckboxConstructor = (option: Titleable & Selectable) => Checkbox;
type PriceOptionConstructor = (option: Titleable & Selectable & Priceable) => PriceOption;
type FactoryMap = {
"RADIO": RadioConstructor,
"CHECKBOX": CheckboxConstructor,
"PRICEOPTION": PriceOptionConstructor
}
class Radio {
constructor(option: Titleable & Selectable) {
console.log('[Radio]', '[constructor]', option);
}
}
class Checkbox {
constructor(option: Titleable & Selectable) {
console.log('[Checkbox]', '[constructor]', option);
}
}
class PriceOption {
constructor(option: Titleable & Selectable & Priceable) {
console.log('[PriceOption]', '[constructor]', option);
}
}
console.log(createFrom({ type: "RADIO" }));
console.log(createFrom({ type: "CHECKBOX" }));
console.log(createFrom({ type: "PRICEOPTION" }));
WYSIWYG
=> WHAT YOU SHOW IS WHAT YOU GET
这里有一个选项可以重新组织您的 classes 并提供一个工厂函数,该函数从初始化中提供的 "type"
属性 推断 class 实例的类型:
type BaseOptionInit = {
selected: boolean;
title: string;
};
class BaseOption<Type extends string> {
selected: boolean;
title: string;
readonly type: Type;
constructor (init: BaseOptionInit & { type: Type; }) {
this.selected = init.selected;
this.title = init.title;
this.type = init.type;
}
}
class Radio extends BaseOption<'RADIO'> {
constructor (init: BaseOptionInit) {
super({...init, type: 'RADIO'});
}
}
class Checkbox extends BaseOption<'CHECKBOX'> {
constructor (init: BaseOptionInit) {
super({...init, type: 'CHECKBOX'});
}
}
class PriceOption extends BaseOption<'PRICEOPTION'> {
price: number;
constructor (init: BaseOptionInit & { price: number; }) {
const {price, ...rest} = init;
super({...rest, type: 'PRICEOPTION'});
this.price = price;
}
}
type OptionType = 'CHECKBOX' | 'PRICEOPTION' | 'RADIO';
type OptionFactoryInit<T extends OptionType> = {
price?: number;
selected?: boolean;
title?: string;
type: T;
}
type OptionInstanceFromTypeName<T extends OptionType> = (
T extends 'CHECKBOX' ? Checkbox
: T extends 'PRICEOPTION' ? PriceOption
: T extends 'RADIO' ? Radio
: never
);
function createOption <T extends OptionType>(init: OptionFactoryInit<T>): OptionInstanceFromTypeName<T> {
const {price = 1, title = 'Option', selected = false} = init;
switch (init.type) {
case 'CHECKBOX': return new Checkbox({selected, title}) as any;
case 'PRICEOPTION': return new PriceOption({price, selected, title}) as any;
case 'RADIO': return new Radio({title, selected}) as any;
default: throw new Error('Invalid type');
}
}
// Example usage:
const priceOption = createOption({type: 'PRICEOPTION'});
priceOption.type // 'PRICEOPTION'
priceOption.price // number
const radios: Radio[] = [];
const radio = createOption({type: 'RADIO'});
radios.push(radio);
radio.type // 'RADIO'
radio.selected // string
radio.title // string
radio.price /* Expected error
~~~~~
Property 'price' does not exist on type 'Radio'.(2339) */