工厂缩小内部 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);

Typescript Playground

这个怎么样...


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 实例的类型:

TS Playground

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) */