[Toc]javascript
弱类型
的,动态类型检查
的语言。这两个特性在带来灵活与便捷的同时,也天生有一些缺陷。const someFunc = (string) => {
return string.split('');
}
someFunc(3);
// Uncaught SyntaxError: string.split is not a function
复制代码
const isTrue: bollean: true;
复制代码
let num: number = 2333;
num = 'abc'; // Error: 不能将类型“"abc"”分配给类型“number”
复制代码
let str: string = '嘿嘿嘿';
str = 0; // Error: 不能将类型“0”分配给类型“string”。
复制代码
在非严格空检查模式下,null 和 undefined 是全部类型的子类型,能够做为任何类型变量的值;
在严格空检查模式(strictNullChecks)下,其是独立的类型。java
非严格空检查模式下:如下三种状况都不会报错:
严格空检查模式下:如下三种状况都会报错:git
let str: string = undefined;
let obj: object = undefined;
let num: number = 2;
num = null;
复制代码
void 表示空类型,void 类型只能赋值为 null || undefined。也能够在函数中表示没有返回值。github
let v: void = null;
let func = (): void => {
alert('没有返回值');
}
复制代码
never 表示其有没法达到的重点,never 是任何类型的子类型,但没有任何类型是 never 的子类型;typescript
const error = (message: string): never => {
throw new Error(message);
}
// 虽然这个函数规定必须有 string 类型的返回值,可是因为 never 是任何类型的子类型,因此这里不会报错
const error = (message: string): string => {
throw new Error(message);
}
复制代码
any 表示该值是任意类型,编辑时将跳过对他的类型检查。应在代码中尽可能避免 any 类型的出现,由于这会失去 ts 的大部分做用 -- 类型检查。其使用的场景在于接受的数据时动态不肯定的类型,或者用来在第三方库的
module.d.ts
声明文件文件里跳过对第三方库的编译。后端
// 例如在 React 的声明文件里,由于不肯定传入的nextProps 与 nextContext 是什么类型,因此使用了any
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
复制代码
object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined以外的类型。其意义在于,在不知道传入的对象长什么样子的状况下,更容易表示Obiect的api,例如
hasOwnProperty
api
const func = (arg: object): void => {
console.log(arg.hasOwnProperty('name')); // => true
}
func({name: 'liuneng'})
复制代码
1.9 数组与元组数组
有两种定义数组类型的方式,一种是直接在类型后面加上[], 表示元素为该类型的数组bash
let arr: number[] = [];
arr.push(1);
arr.push('2'); // Error: 类型“"2"”的参数不能赋给类型“number”的参数
复制代码
第二种是使用数组泛型, 这种方式能够在不想在外边声明类型时候使用dom
let list: Array<1 | 2> = [];
list.push(3); // Error: 类型“3”的参数不能赋给类型“1 | 2”的参数
复制代码
let tuple: [string, number];
tuple = [1, 'a']; // Error: 不能将类型“[number, string]”分配给类型“[string, number]”
tuple = ['a', 1];
tuple.push(true); // 类型"true"的参数不能赋给类型“string | number”的参数。
复制代码
2.0 类型断言
其表示在不肯定该变量类型时,指定其类型,表示明确知道他的类型,不用去检查了
// 虽然 param 是any,但“我”保证传入的必定是个 string 类型的参数
const func = (param: any) => {
return (param as string).length;
};
复制代码
使用枚举类型能够为一组数值赋予有意义的名字
const param = {
filterType: 0,
};
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res);
});
复制代码
上面的这行代码,用眼睛看根本不知道请求的是什么类型的列表,只能经过注释 与 文档来判断它的意义
enum filterMap {
All = 0,
Men = 1,
Women = 2,
}
const param = {
filterType: filterMap.Men,
};
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res);
});
复制代码
上面这段代码,用枚举列出了全部过滤条件的选项,使用时直接像使用对象同样枚举,从语义上很容易理解这段代码想要获取的是男性列表,代码便是文档。尤为是当作常量使用更加统一与方便理解。
enum AbcMap {
A,
B = 1,
C,
D = 2,
E = 2,
}
console.log(AbcMap.A); // => 0 由于后面的 B 是1,因此自动 -1
console.log(AbcMap.C); // => 2 由于前面的 B 是1,因此自动 +1
console.log(AbcMap.[2]); // => E 有三个2,取最后一个的 key
复制代码
interface 是对对象形状的描述,其规定这个类型的对象应该长什么样子,编译的时候回去检查以他为描述的对象符不符合其结构
interface IPerson {
name: string;
readonly isMan: boolean; // 只读属性,建立后不能够改写
age?: number; // 可选属性,实现的时候能够没有这个属性
}
const xiaohong: IPerson = {
name: '小红',
isMan: false,
};
xiaohong.isMan = true; // Error: isMan 是常数或只读属性。
xiaohong.love = '周杰伦'; // Error: “love”不在类型“IPerson”中。
复制代码
上面给小红添加 love 属性的时候报错了,由于 IPerson 中没有规定这个属性
可是有时候咱们不肯定在 interface 外有没有别的属性,这时候可使用索引签名。可是此时已肯定的类型必须是他的子类型
interface IPerson {
[key: string]: string;
}
const xiaoming: IPerson = {
name: '小红',
love: '周杰伦'
};
复制代码
ts 能够给函数的参数 与 返回值指定类型。使用时候不能使用多余参数
如今定义一个加法的函数表达式
const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
sum(1, 2); // => 3
复制代码
上面的代码看起来可能有点很差理解,左边是给 sum 定义类型,右半部分是 一个具体函数,这是 ts 函数完整的写法。经过 ts 类型推论的特性,能够把左半部分省略掉;也能够给变量定义类型而省略右边
const sum = (x: number, y: number): number => x + y;
复制代码
上面的代码看起来就比较好理解了,可是若是咱们有一个乘法的方法,还有减法的方法等等等等,其输入类型和输出的类型都是 number,这个时候若是感受在每一个方法上都去定义参数与返回值的类型会以为有点麻烦。此时,能够单独抽出一种函数类型,在函数表达式中使用。
type INumFunc = (x: number, y: number) => number ;
const sum: INumFunc = (x, y) => x + y;
const sub: INumFunc = (x, y) => x - y;
const multip: INumFunc = (x, y) => x * y;
复制代码
上面的代码定义了一个函数类型,要求输入输出都为 number;此时 ts 会自动给右边的函数体肯定函数类型。若是右边函数体与左边类型声明不一致就会报错。
// 可选参数
const sub = (x: number, y: number = 5, y?: number): number => {
if (y) {
return x - y - z;
} else {
return 0;
}
};
sub(10, 1, 1) // -> 8
sub(10, 1) // -> 0
// 默认参数
const sum = (x: number, y: number = 5): number => x + y;
sum(1, 2); // -> 3
sum(1); // -> 6
sum(1, null); // -> 6
复制代码
js 里有 arguments 的存在,因此咱们能够给一个函数传任意个参数。在 ts 里,不肯定参数的个数的话,可使用剩余参数,将多出的参数放入一个数组, 其和 ES6 的剩余参数使用方法一致
const sum = (x: number, ...y: number[]): number => {
console.log(y);
let sum = x;
if (y && y.length > 0) {
for (let index = 0; index < y.length; index++) {
sum = sum + y[index];
}
}
return sub;
};
sum(1, 2, 3); // res -> 6 , log -> [2, 3]
复制代码
ts 的类 与 ES6 中的类大致相同,不过 class 中的属性能够添加修饰符
static 静态属性,其是这个类的属性,而不是实例的属性 public: 访问该成员的时候没有限制;
protected: 在派生类中能够访问该属性,可是不能再外部访问;
private: 私有成员,只能本身访问
readonly: 只读属性
abstract: 用于修饰抽象类或属性,必须在派生类中方实现它,本身不能实现。
class Person {
static desc() {
console.log('It's a class of "person");
}
protected name: string;
private age: number = '8';
readonly sex: string = 'boy';
constructor (theName: string) {
this.name = theName;
}
public like() {
console.log('footbal');
}
abstract eat(): void; // 必须在派生类中实现它
}
class kids extends Person {
constructor(name) {
super(name);
}
sayName() {
console.log(this.name);
}
eat() {
console.log('面包');
}
}
const xiaohong = new kids('小红');
Person.desc(); // 静态成员直接使用 class 访问,不用实例
xiaohong.like(); // -> 'footbal' public 属性访问没限制
console.log(xiaohong.name); // Error: 小红是 protected 属性,只能在基类与派生类里面访问
xiaohong.sayName(); // -> '小红' 小红的内部方法里能够访问 protected 属性
console.log(xiaohong.age) // age 是 私有属性,不能在外部访问
console.log(xiaohong.sex); // -> boy
xiaohong.sex = 'girl'; // Error: sex 是只读属性,不能修改
复制代码
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
let str = 'string';
str = 1;
// Error: Type 'number' is not assignable to type 'string'.
// str 在声明的时候并无指定类型,可是 ts 自动推断为 string, 因此在给它赋值为 number 的时候报错了
let someVar;
someVar = 'string';
someVar = 3;
// 若是在声明一个变量的时候并无给它赋值,ts 自动给它推断为 any 类型,因此这里跳过了类型检查,没有报错。
复制代码
const sum = (x: number, y: number) => x + y;
sum(1, 2);
// 上面函数,没有并无给其指定 return 的类型,但这是被容许的,由于 ts 能够自动推断出其返回值的类型。
复制代码
const obj = {
a: 1,
b: 2,
};
obj.a = 'str'; // Error: 不能将类型“"str"”分配给类型“number”
// 虽然 obj 在声明的时候并无指定类型,可是 ts 自动将其推断为 {a: number, b: number} 因此报错
// 解构也是同样的
let { a } = obj;
a = 'str'; // Error: 不能将类型“"str"”分配给类型“number”
复制代码
下面的代码将 arr 推断为了
Array<string | number>
const arr = ['a', 'b', 1];
arr[0] = true; // Error: 不能将类型“true”分配给类型“string | number”
复制代码
ts 甚至能根据某些代码特征进行推断出正确的类型范围。
下面的 if 代码块中,param 被推断为类型 string
const func = (arg: number | string) => {
if (typeof arg === 'string') {
console.log(arg.split('')); // OK
}
console.log(arg.split('')); // Error: 类型“number”上不存在属性“split”。
}
复制代码
下面的代码能够根据 instanceof 推断出其参数类型,甚至能够自动推断出 else 代码块中的类型
class A {
public name: string = 'hehe';
}
class B {
public age: number = 8;
}
const func = (arg: A | B) => {
if (arg instanceof A) {
console.log(arg.name); // OK
console.log(arg.age); // Error: 类型“A”上不存在属性“age”。
} else {
console.log(arg.name); // Error: 类型“B”上不存在属性“name”。
console.log(arg.age); // OK
}
}
复制代码
有时候,当时用一个组件的时候,并不能肯定其数据类型是什么样子,或者说为了达到复用组件的目的,可使用泛型来建立可重用的组件。
例如,如今须要一个函数,其要求能够输出任意类型的参数,可是输入与输出必须是同一类型。若是不使用泛型的话,只能使用联合类型,或者 any 来实现。使用泛型能够这样作:
function identity<T>(arg: T): T {
return arg;
}
identity<string>('str'); // -> 'str'
复制代码
function objToArr<T>(arg: T): T[] {
return [arg];
}
objToArr({a: 1}); // -> [{a: 1}]
复制代码
上面的代码表示输入 T 类型的参数时,返回一个 T 类型成员的数组
// 建立一个接口,其属性 list 的类型在使用前并不肯定
interface IData<T> {
list: T[];
status: number;
}
const numItemData: IData<number> = {
list: [1, 2, 3],
status: 1,
};
const strItemData: IData<number> = {
list: ['a', 'b', 'c'], // Error: 不能将类型“string[]”分配给类型“number[]”。
status: 1,
};
复制代码
上面的例子建立了接口 IData
,其在使用的时候,传入类型约束, 这样能够最大程度的复用 IData
接口。由于 strItemData 的赋值与泛型传入的类型不一致因此报错
// 咱们与后端约定, response 的格式以下,但 data 部分依具体使用场景而定, 泛型能够给个默认值 - any
interface IResponse<T = any> {
status: number;
message: string;
data: T;
}
复制代码
// 咱们封装了一个 fetch API,里面对请求进行了处理,例如header、toaster
import fetchData from 'XXX/fetchData';
// 引入上面定义的通用的 response 接口
import { IResponse } from 'XXX/response';
export const getUser<T> = (param: IInput): Promise<IResponse<T>> => {
return fetchData('xxx/getData').then((res: IResponse<T>) => {
return res;
});
};
复制代码
使用的时候:
import getUser from 'XXX/getUser';
// 定义 response 中 data 的类型
interface IData {
name: string;
age: number;
}
// 将 data 的类型约束传入泛型
const userInfo = getUser<IData>();
userInfo.data.name = '小刚'; // Right
userInfo.data.name = 666; // Error
// ts 推断出 data.name 是 string 类型,因此在赋值为 666 的时候报错了
复制代码
class Person<TName, TAge> {
name: TName;
age: TAge;
}
let xiaoming = new Person<string, number>();
xiaoming.name = '小明'; // Right
xiaoming.age = '8'; // Error: [ts] 不能将类型“8”分配给类型“number”。
复制代码
上面代码由于在建立 xiaoming 的时候规定了 age 类型必须为 number,因此报错了
interface IData {
a: number;
}
function objToArr<T extends IData>(arg: T): T[] {
console.log(arg.a);
return [arg];
}
objToArr({a: 1, b: 2}); // -> [{a: 1, b: 2}]
objToArr({b: 2}); // Error: 类型“{ b: number; }”的参数不能赋给类型“IData”的参数。
复制代码
TS 为咱们的 javascript 的内置对象提供了类型,而且在使用内置对象的时候自动为咱们进行类型检测 例如:
let body: HTMLElement = document.body;
let div: HTMLDivElement = document.createElement('div');
document.addEventListener('click', function(e: MouseEvent) {
console.log('MouseEvent');
});
Math.round('3.3'); // 类型“"3.3"”的参数不能赋给类型“number”的参数
// 由于 Math 对象 round 须要接受一个 number 类型的参数因此报错了,
// 下面是TS核心库定义文件中对 Math 对象的定义
/**
interface Math {
pow(x: number, y: number): number;
random(): number;
round(x: number): number;
sin(x: number): number;
// ......
}
declare const Math: Math;
**/
复制代码
TS 也定义了
Number,String,Boolean, Object
, 可是并不推荐区用这些类型,而是应该使用number, string, bollean, obiect
let str: String; // Don't use 'String' as a type. Avoid using the `String` type. Did you mean `string`
复制代码
能够给类型起个名字
type str = 'a';
type num = 1;
const ab: str = 'ab'; // Error: 不能将类型“"ab"”分配给类型“"a"”。
const someNum: num = 2; // Error: 不能将类型“2”分配给类型“1”。
复制代码
虽然使用方式相似,可是类型别名并不能被继承、导出等操做。只能做为
type Person = {
name: string;
age: number;
};
const xiaoming: Person = {
name: '小明',
age: 18,
};
复制代码
type Person<T> = {
name: string;
like: <T>[];
};
const xiaohong: Person<string> = {
name: '小红',
like: ['dance', 'football'],
};
复制代码
当咱们想要比那里一个对象的时候,须要指定每一项元素的 key 的索引签名,不然会报错,好比像下面这样
const obj = {a: 1, b: 2};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
}
// Error: 元素隐式具备 "any" 类型,由于类型“{ a: number; b: number; }” 没有索引签名。
复制代码
可使用 类型别名 + 索引类型来避免该问题
const obj = {a: 1, b: 2};
type ObjKey = keyof typeof obj; // => 'a' | 'b'
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key as ObjKey]);
}
}
复制代码
声明文件是对某个库的环境声明,文件内对库向外暴露的 API 进行类型注解,使其在使用的时候能够享受到 TS 类型系统带来的检查
// 声明变量
declare var foo: number;
// 声明函数
declare function add(x: number, y: number): number; 复制代码
// 直接将 interface 导出就行
复制代码
// 1. 使用命名空间的方式
declare namespace person {
let name: string;
let age: number;
}
// 2. 使用 interface
interface IPerson {
name: string;
age: number;
}
declare const person: IPerson;
复制代码
declare class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`I'm ${this.name}`);
}
}
复制代码