TypeScript知识点聚集地

132 阅读6分钟

总结了一部分自己工作过程中收集到的不容易理解的概念。 参考都已经附在每个点后面

enum

先看例子

enum User {
  Tom,
  John,
}

用js实现就是

var User;
(function (User) {
    User[User["Tom"] = 0] = "Tom"; // 小问号,你是否有很多朋友
    User[User["John"] = 1] = "John";
})(User || (User = {}));

之前是看不明白翻译以后的 User[User["Tom"] = 0] = "Tom"; 是个啥操作,但是如果理解到 User["Tom"] = 0 的返回值是 0 就好办了。这一行的操作是让 User 内部生成

{
  "Tom": 0,
  0: "Tom",
}

这种反向映射的键值对,本来 key -> value 应该是Tom -> 0,又添加了 value -> key这种反向结构。相当于 console.log(User[0] === 'Tom') // true,查了一些资料说是便于调试,不过要记住这个只对数字枚举有效,对字符串枚举是无效的。

enum User {
  Tom = 'tom',
  John = 'john',
}

对应的js是

var User
(function(User) {
  User['Tom'] = 'tom';
  User['John'] = 'john';
})(User || User = {})

type 和 interface 的恩爱情仇

详情

  • 相同点
    1. 都可以描述一个对象或者函数
    // interface
    interface User {
      name: string
      age: number
    }
    
    interface SetUser {
      (name: string, age: number): void;
    }
    
    //type
    type User = {
      name: string
      age: number
    };
    
    type SetUser = (name: string, age: number) => void;
    
    1. 都允许拓展 白话就是都能extends,而且两者可以不相互独立,什么意思?就是 type 可以 extends interface,interface 可以 extends type。
    // interface ext interface
    interface Name { 
      name: string; 
    }
    interface User extends Name { 
      age: number; 
    }
    
    // type ext type
    type Name = {
      name: string;
    }
    //注意,这个是错的,哈哈上当了!要注意格式
    type User extends Name = {
      age: number;
    }
    // 上面都是错的!下面才是真的
    type User = Name & { age: number };
    
    // interface ext type
    type Name = {
      name: string;
    }
    // 可以简单理解为谁ext那就用谁的语法
    interface User extends Name {
      age: number;
    } 
    
    // type ext interface
    interface Name {
      name:string;
    }
    type User = Name & {
      age:number
    };
    
    /** 在写的时候发现的以前自己的盲点补充
     * 类型还可以定义具体的值,interface也一样
    **/
    type Name = 'Tom';
    const user:Name = 'lisa'; // 绝壁报错
    const user:Name = 'Tom'; // 有且只有这么一个值
    
    
    
  • 不同点
    1. type可以,interface不可以
    • type 可以声明基本类型别名,联合类型,元组等类型
    // 1. 基本类型别名
    type Name = string
    // 这么用的,也就是说用其他变量来代替基本类型,下面也可以写成 name:string
    const name:Name = 'hello';
    
    // 2. 联合类型
    interface Dog {
        wong();
    }
    interface Cat {
        miao();
    }
    type Pet = Dog | Cat
    
    // 3. 具体定义数组每个位置的类型
    type PetList = [Dog, Pet]
    
    
    • type 语句中还可以使用 typeof 获取实例的 类型进行赋值
    // 想获取某个变量的类型时,使用 typeof
    const Audio = {
      play: () => {},
      stop: () => {},
      reset: () => {},
    }
    type audio = typeof Audio;
    // 上面就相当于
    type audio = {
      play: () => void;
      stop: () => void;
      reset: () => void;
    }
    // 然后呢? 然后就直接拿来用啊!
    const a:audio = {
      play: () => { console.log('play') },
      stop: () => { console.log('stop') },
      reset: () => { console.log('reset') },
    };
    
    • 其他骚操作
    type StringOrNumber = string | number;  
    type Text = string | { text: string };  
    // 下面的就看不懂了,但是之后会补充
    type NameLookup = Dictionary<string, Person>;  
    type Callback<T> = (data: T) => void;  
    type Pair<T> = [T, T];  
    type Coordinates = Pair<number>;  
    type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
    
    
    1. type可以,interface不可以 interface 能够声明合并
    interface User {
      name: string
      age: number
    }
    
    interface User {
      sex: string
    }
    
    /*
    User 接口为 {
      name: string
      age: number
      sex: string 
    }
    */
    // 自动合并了
    

泛型

刚开始很难懂到最后写几遍就明白的概念。 从代码入手

function getVal(val) {
    retrurn val;
}
getVal(1) // 返回数字类型
getVal('1') // 返回字符串类型
getVal(['2']) // 返回数组类型

如何写getVal的规范,使得getVal可以支持三种类型的输入? 提供了两个方法:

  1. 函数重载 (其实这个概念可以扩充)
function getVal(val:number):number;
function getVal(val:string):string;
function getVal(val:any):any {
  return val;
}
// 注意到了前两行写法不是写函数,你要写函数直接那下面岂不是覆盖掉上面的了
  1. 联合类型
function getVal(val:number|string|any[]):number|string|any[] {
  return val;
}

第一种先不说好坏,光第二种明显很容易出bug,这也没有规定输入number就一定返回number!如果输入number返回string那还是没法检测到!所以这个类型是动态的:我知道你是啥,我才能确保我是啥 所以泛型惊喜登场!

function getVal<T>(value:T):T {
  return value;
}

翻译翻译,什么叫TMD惊喜! 惊喜就是T在这里当作一个类型变量(重点词汇变量),它是可以塞入值的变量,可以塞 stringnumber等等类型,你塞进去的时候,后面的T就能直接用了,跟变量是一样一样的。 所以上面的泛型其实就是变成了

function getVal(val:number):number {
  return value;
};
function getVal(val:string):string; {
  return value;
}
// …… 等等等等的集合体,大概就是这个意思

但是你一看不行啊,我只要那三种类型的,怎么给我了这么多?我没想塞进去对象怎么办? 这就用到了 泛型约束

type Params=  string | number | any[];
function getVal<T extends Params>(val: T): T {
    return val;
}
getVal(1);
getVal('2');
getVal(['222']);
getVal<number>('3'); // 跟泛型指定的类型不一致, 报错
getVal({}); // 不是 Param 类型, 报错

关键字 extends,在学习之前,一直把这个关键字当作了 class的那种,然后就一直不理解。这里就简单理解为 T 只可能是 extends 后那个type中的类型,其他的不可能了。 另外注意到一点,在调用getVal的时候,还能手动塞入T的类型来确保函数参数乱入!具体看上面的例子

泛型语法,简单了解一下用处

class Person<T>{} // 一个尖括号跟在类名后面
function Person<T> {}  // 一个尖括号跟在函数名后面
interface Person<T> {}  // 一个尖括号跟在接口名后面

如果是多个变量,多个泛型呢,可以这么搞

function getName<T,U>(name: T, id: U): [T, U] {
  return [name, id]
}
getName('peen', 1);
getName('peen', '222'); // 正常
getName<string, number>('peen', '22'); // 报错: '22'不是number类型

实际应用直接用的作者的

interface IApiSourceParams {
  GetList: IGetList;
  PostList: IPostList;
}
interface IGetList {
  id: number;
}
interface IPostList {
  content: string;
}
export function fetchApi<T extends keyof IApiSourceParams>(action: T, params: IApiSourceParams[T]) {
  return ajax({
    url: action,
    method: 'POST',
    body: params
  })
}
fetchApi('GetList', { id: 2 });
fetchApi('GetList', { id: '33' }); // 报错, id 应该是number 类型

其实看到这个例子还是啃了一段时间的,一遍一遍过 <T extends keyof IApiSourceParams> 先解释keyof,这个关键字是提取IApiSourceParams中的所有 ,在这里就是 'GetList'|'PostList',所以 T 就是'GetList'|'PostList',然后后面(action: T, params: IApiSourceParams[T])一旦确定了T是谁,那么action就是那个字符串(GetList或者PostList),params就是对应的类型规范。太好用了~

fetchApi<'GetList'>(***) 
// 就好比你只在调用方法时候尖括号写入GetList
// 得了,函数的action和params都已经确定了。
// 在调用其他乱七八糟不合逻辑的就报错

扩充一下keyof 它叫做索引类型查询操作符,例子

interface Person {
  name: string;
  age: number;
}
let personProps: keyof Person; // 'name' | 'age'

还有个 索引访问操作符T[K],还是上面那个请求的例子IApiSourceParams[T],就是获取了对应key的接口,要么是IGetList,要么是IPostList。 看下react的类型规范有种焕然大悟的感觉

class Component<P, S> {
  static contextType?: Context<any>;
  context: any;
  constructor(props: Readonly<P>);
  constructor(props: P, context?: any);
  setState<K extends keyof S>(
      state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
      callback?: () => void
  ): void;
  forceUpdate(callBack?: () => void): void;
  render(): ReactNode;
  readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>;
  state: Readonly<S>;
  refs: {
      [key: string]: ReactInstance
  };
}

《四》大话 Typescript泛型 深入探索 Typescript 的高阶用法

联合类型