总结了一部分自己工作过程中收集到的不容易理解的概念。 参考都已经附在每个点后面
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 的恩爱情仇
- 相同点
- 都可以描述一个对象或者函数
// 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;- 都允许拓展 白话就是都能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'; // 有且只有这么一个值 - 不同点
- 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> };- 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可以支持三种类型的输入?
提供了两个方法:
- 函数重载 (其实这个概念可以扩充)
function getVal(val:number):number;
function getVal(val:string):string;
function getVal(val:any):any {
return val;
}
// 注意到了前两行写法不是写函数,你要写函数直接那下面岂不是覆盖掉上面的了
- 联合类型
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在这里当作一个类型变量(重点词汇变量),它是可以塞入值的变量,可以塞 string、number等等类型,你塞进去的时候,后面的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 的高阶用法