Typescript实用技巧

82 阅读3分钟
  1. 使用注释,方便开发者理解接口/类型的含义
/** person information */

interface PersonInfo {

  name: string;

  age: number;

}



const person: PersonInfo = {

  name: 'test',

  age: 18,

};
  1. 巧用typeof
1.类型反推

const person = {

  name: 'test',

  age: 18,

};

type PersonInfo = typeof person;

2.类型判断

const getPadding = (a: number | string) => {

  if (typeof a === 'number') {

    return a;

  } else {

    return Number(a);

  }

};

3.结合函数获取返回类型

const dialog: ReturnType<typeof useDialog> = useDialog();

4.获取组件实例

const messageProviderRef = ref<InstanceType<typeof NMessageProvider> | null>(null);
  1. 使用keyof查找key值
1.获取interface里的key

export interface IEvents {

  trigger: Trigger;

  action: Action;

}

const events: { [key in keyof IEvents]: IEvents[key] }[] = [];

2.泛型+keyof

interface API {

  '/user': { name: string },

  '/menu': { foods: Food[] },

}

const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {

  return fetch(url).then(res => res.json())

}
  1. 使用类型查找
interface PersonInfo {

  name: string;

  age: number;

}

const name: PersonInfo['name'] = 'test';

// naiveui使用主题

type DataTableThemeOverrides = NonNullable<DataTableProps['themeOverrides']>;
  1. 类型体操进行接口复用
1.使用Omit复用接口

interface TrafficLight {

  center: LatLng;

  lanes?: string[];

  light_id: string;

  signals: TrafficLightSignal[];

}

type TrafficLightObject = Omit<TrafficLight, 'center'>;

2.结合Record

interface TrafficLight {

  center: LatLng;

  lanes?: string[];

  light_id: string;

  signals: TrafficLightSignal[];

}

type TrafficLights = Record<TrafficLight['light_id'], Omit<TrafficLight, 'light_id'>>;
  1. 适当时可用Record
type AnimalType = 'cat' | 'dog' | 'frog';

interface AnimalDescription { name: string, icon: string }

const AnimalMap: Record<AnimalType, AnimalDescription> = {

  cat: { name: '猫', icon: ' '},

  dog: { name: '狗', icon: ' ' },

  forg: { name: '蛙', icon: ' ' }, // Hey!

};
  1. 巧用as const 参考const-assertions
1.定值的时候可用

export const COMMON = {

  groupOptions: ['planning', 'perception', 'integration', 'map'] as const,

  testingStages: [

    'basic',

    'active',

    'advanced',

    'super',

  ] as const

};

2.[a, b] as const

const a = [1, 'test'] // (number | string)[]

const b = [1, 'test'] as const // readonly [1, "test"]

[1, ''] 会被推导为类型 (number | string)[],而加 as const 可推断为元组,可用于解决watch多个type错误问题

watch(

  () => [routeRef.value, activeEntityCenterRef.value] as const,

  ([{ id, type, name }]) => {

    // todo

  },

  {

    immediate: true,

  },

);

3.Vue组件修改props问题

 type NumberOrNumberString = PropType<string | number | undefined>;

 const props = {

  height: [Number, String] as NumberOrNumberString,

  // others

} as const;

 export default defineComponent({

 props,

 setup() {

 // xxxxx

 const changeHeight = () => {

     console.log(props.height); // ok

     // Unexpected mutation of "height" prop.eslint(vue/no-mutating-props)

     // Cannot assign to 'height' because it is a read-only property.ts(2540)

     props.height = 120;

   } 

 }

 })

 至于setup语法糖则默认帮我们置为readonlyconst props = defineProps({

  foo: String,

  age: Number,

});

or

const props = defineProps<{

  foo: string;

  age: number;

}>();

// Cannot assign to 'foo' because it is a read-only property

props.foo = '123';
  1. 类型收窄
1.使用as,当你知道类型时可用使用as进行类型收窄

interface Cat {

  name: string;

  run(): void;

}

interface Fish {

  name: string;

  swim(): void;

}



const canRun = (data: Cat | Fish) => {

  (data as Cat).run();

};

更好的断言

const isCat = (animal: Cat | Fish): animal is Cat => {

  return typeof (animal as Cat).run === 'function';

};

2.类型守卫

利用typeof

type Foo = string | number;

const judgeType = (data: Foo) => {

  if (typeof data === 'string') {

    return data;

  } else if (typeof data === 'number') {

    return data + '';

  } else {

    // never类型

    return '123';

  }

};

instanceof判断类型

class Man {

  name = 'John';

}



class Woman {

  age = 20;

}



const getName = (human: Man | Woman) => {

  if (human instanceof Man) {

    console.log(human.name);

  } else {

    // 此处一定为Woman

    console.log(human.age);

  }

};

利用in判断

interface A {

  a: string;

}

interface B {

  b: number;

}

function getVar(data: A | B) {

  if ('a' in data) {

    console.log(data.a);

  } else {

    console.log(data.b);

  }

}

3.双重断言(慎用,编译器会相信你的断言,如果类型有问题是检测不出来的)

const handleEvent = (event: Event) => {

  const element = event as unknown as HTMLElement;

};
  1. 没有合适的类型时自己写类型,这里查看官方类型
比如你传入的类型可能是数组也可能是普通类型

type MaybeArray<T> = T | T[];

const getMeters = (meters: MaybeArray<number>) => {

  // todo

};

比如你想要进行camelize

interface Obj {

  lane_id: string;

  name: string;

}

type CamelizeKey<T> = T extends `${infer Left}_${infer F}${infer Right}`

  ? F extends Uppercase<F>

    ? `${Left}_${CamelizeString<`${F}${Right}`>}`

    : `${Left}${Uppercase<F>}${CamelizeString<Right>}`

  : T;

type CamelizeObj<T extends Object> = {

  [Key in keyof T as CamelizeKey<Key>]: T[Key];

};



// {

//     laneId: string;

//     name: string;

// }

type UpperObj = CamelizeObj<Obj>;
  1. 学会使用infer(类型推断),这里写的还不错
infer表示在 extends 条件语句中待推断的类型变量

type ParamType<T> = T extends (...args: infer P) => any ? P : T;

在这个条件语句 T extends (...args: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。



整句表示为:如果 T 能赋值给 (...args: infer P) => any,则结果是 (...args: infer P) => any 类型中的参数 P,否则返回为 T。

example:

interface User {

  name: string;

  age: number;

}



type Func = (user: User) => void;



type Param = ParamType<Func>; // Param = User

type AA = ParamType<string>; // string