基本类型&&变量声明
类型定义
typescript中可以如下定义变量:
let a: string = '你好啊'
上述的: string是对a变量的类型定义
在我们并不对变量进行类型定义时,typescript也可以根据变量的类型进行推断
let a = '你好啊'
a = 2 // ❌ 不能将类型“2”分配给类型“string”
由此看出,在初始化时,若没有主动声明变量类型,typescript也会自行推断,若在被赋值时与初始化类型不同,则编译时会报错
数组定义
两种方式:
let arr: number[] = [1,2,3] // 类型[]
let arr: Array = [1,2,3] // Array<类型>
元组定义
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,
// 下面定义一个类型数组长度为3,类型分别为string,number,object的数组
interface Type {
a: number;
b: string;
}
let x: [string, number, Type]; // x为长度为3的数组
x = ['hello', 735, { a: 23, b: '你好'}]
x[3] = '23';// 不能将类型“"23"”分配给类型“undefined”。Tuple type '[string, number, Type]' of length '3' has no element at index '3'
any
在编程阶段有可能存在还不清楚变量类型的情况,这种情况下,我们不希望类型检查器对这些值进行检查,而是直接让他们通过编译检查,就用到了any,在变量赋值时,any与Object相似,下文会讲到
// 下面代码不会出现类型错误的提示
let a: any = 4;
a = "你好啊";
a = true;
Object与object
在javascript中万物皆对象Object,因此,在赋值方向,Object与any有这类似的作用,Object类型的变量允许给它赋任意值,但与any相比,它不能在上面调用任意方法如下:
let A: any = 4;
A = '123';
A.ifItExists(); // 实际上在A上没有ifItExists函数,但是因为跳过编译检查,所以不会检查这一步,因此不会报错
A.toFixed();
let B: Object = 4;
B = '123'; // javascript中,万物皆对象
B.toFixed(); // ❌类型“Object”上不存在属性“toFixed”
let C: object = 3; // ❌不能将类型“3”分配给类型“object”
C = '123'; // object单纯的指类型为对象
let D: {} = 2; // {} 与Object作用相同
D = '13';
D.toFixed(); // ❌类型“{}”上不存在属性“toFixed”
这里面any和Object的区别就在于,any是跳过编译检查,既然已经跳过了,那么在对这个变量做任何处理都不会报错,Object类型的变量由number被赋值成了字符串成功,因为javascript中,万物皆对象,都从对象继承过来的,所以赋值会成功,但是toFixed()函数在Object中不存在,因此不能直接调用,{}作用与Object相同。Object的所有效果在{}都能表现出来。
object则单纯指类型为对象,表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。
Void
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void,用void声明一个变量一般没有什么意义,因为他只能赋值undefined,一般void用来声明函数的返回值,当没有返回值时,使用void
let A: void = undefined;
let B: void = null; // ❌不能将类型“null”分配给类型“void”
function fun1(): void {
console.log('你好啊');
}
Never
never类型表示的是那些永不存在的值的类型。一般不会有实际应用,再次不做过多介绍。
类型断言
一个变量可能有多种变量类型,有时可能需要当变量为一种类型时执行一种操作,为另一种类型时,执行另一个操作,这时就需要用到类型断言,通过类型断言,我们可以准确的告诉编译器我们想要做什么,这个动作仅在编译时起作用。
类型断言两种方式:尖括号和as关键字
type Str = string | number;
let str: Str = '你好啊';
let strLen = (str).length // <类型>变量
type Str = string | number;
let str: Str = '你好啊';
let strLen = (str as string).length // 变量 as 类型
若当前的类型并不等于断言的类型,则该条语句不被执行
type Str = string | number;
let str: Str = 2;
let strLen = (str as string).length
console.log('strLen', strLen) // undefined
在JSX文件中TypeScript的类型断言可以使用as,不允许使用尖括号方式
泛型
目的:用于提升代码的重用性
泛型函数
泛型函数的定义与使用
// 普通函数形式
function hello (arg: T): T { // hello :定义的泛型变量,arg: T传入参数类型,(arg: T): T返回值类型
return arg;
};
// ES6箭头函数形式(泛型变量只有一个时,eg:T) 正确
const hello = (arg: T): T => {
return arg;
};
// ES6箭头函数形式(泛型变量只有一个时,eg:T) 错误
const hello = (arg: T): T => {
return arg; // JSX element 'T' has no corresponding closing tag.ts
};
// ES6箭头函数形式(泛型变量两个及已上时) 错误
const hello = (arg: T): T => {
return arg; // JSX element 'T' has no corresponding closing tag.ts
};
// 使用
let str = hello('hello');
ES6箭头函数使用泛型,且泛型变量只有一个时,必须使用
泛型变量
创建上述的泛型函数,编译器要求在函数体中正确使用类型,换句话说,你必须吧这些参数当成时任意类型。比如上述函数中想要console.log(arg.length)就会立即报错,因为存在一些类型并没有length属性,这时可以优化上面的函数
function hello (args: T[]): T[] { // hello :定义的泛型变量,arg: T传入参数类型,(arg: T): T返回值类型
console.log(args.length);
return arg;
};
这样泛型变量T就作为了数组的一部分属性,而不是作为整体类型,增加了灵活性,此时传入的参数会发生变化,由eg:string -->string[];
枚举
TypeScript支持数字枚举和字符串枚举
数字枚举
数字枚举,后面的枚举变量时递增的,第一个枚举变量默认值为0,后续依次递增,若重新定义了第一个枚举变量,则后续的枚举变量在已定义的变量之上递增,
存在反向映射,可以通过值value拿到命名的key
enum Type {
a, // 0
b, // 1
c = 8, // 8
d // 9
}
反向映射:
const value = Type.a; // 0
const key = Type[0]; // a
字符串枚举
字符串枚举没有递增的含义,每个枚举成员必须手动初始化,不存在反向映射
enum Type {
a = 'a', // a
b = 'b', // b
c = 'c', // c
}
可以存在数字枚举与字符串枚举共存的情况,但是前提是字符串枚举必须放在下面
enum Type {
a,
b,
c=8,
d='ddd',
e='eee'
}
enum 类型的变量不能在.d.ts文件中导出,会报Cannot find module './data'错误
高级类型
interface
描述对象的结构,对字典(数据结构)进行类型约束
interface Type {
a: number,
b: string,
c: number[]
}
let type : Type;
type.a = 1;
type.b = 'hello';
type.c = ['a','b'] // ❌ 报错 不能将类型“string”分配给类型“number”
交叉类型与联合类型
交叉类型 &
指将多个字典类型合并为一个新的字典类型,既为...又为...
interface A {
a: string,
b: number
}
interface B {
b: number,
c: number
}
type Type = A & B;
let t: Type = {a: '12', b: 1, c: 1}; // t变量必须包含a、b、c三个key值
一般交叉类型只适用于对象,举个例子,
type Type = number & string;
let t : Type = 1 // ❌ 不能将类型“1”分配给类型“never”
因为number和string类型没有交集的情况。所以number & string后不会有能包含两种的类型的值存在。所以上文说一般交叉类型只适用于对象
还存在另一种情况,上面代码中的A、B中都有相同的b,他们的类型必须相同,否则,以此交叉类型为类型的变量会报错
interface A {
a: string,
b: number
}
interface B {
b: string,
c: number
}
type Type = A & B;
let t: Type = {a: '12', b: 1, c: 1}; // ❌ 不能将类型“number”分配给类型“never”
代码中的key值b,会进行类似下面的操作:b:number & string;因此这样的类型还是不存在,因此代码中会报错
总结一下:交叉类型就是将不同类型叠加成为新的类型,并且包含了所有类型(除非里面的类型为可选的eg:b?:string,这样可以不用写b)
联合类型 |
表示一个变量可以是几种类型之一(或的关系),可以是...也可以是...
type Type = number | string;
let t: Type = 1;
类型保护
要实现类型保护,只需要简单地定义一个函数就可以,但返回值必须是一个主谓宾语句
function isTeacher(person: Teacher | Student): person is Teacher {
return (person as Teacher).teach !== undefined;
}
person is Teacher是类型保护语句,说明参数必须来自于当前函数签名(定义了函数或方法的输入输出)里的一个参数名
类型别名 type
使用type关键字来描述类型变量,使用类型别名或者类型集合创建一个新名字,类型别名可以是泛型,也可以是用类型别名在属性里引用自己,听起来比较像递归
// 普通
type Age = number;
// 泛型
type Person = {
age: T;
}
// 类型别名在属性里引用自己
type Person = {
age: T;
mother: Person
father: Person
}
字面量类型
字面量类型通常结合联合类型使用
// 最简单的字面量类型
type Profession = 'teacher';
// 结合联合类型
type Profession = 'teacher' | 'doctor' | 'student';
let person: Profession // person的值在 teacher、 doctor、student中
类型推导
在没有明确指出类型的地方,TypeScript编译器会自己推测出当前变量的类型,TypeScript里的类型兼容性是基于结构子类型的,只要满足了子类型的描述,那么就可以通过编译时检查,TypeScript的设计思想比不是满足正确的类型,而是满足能正确通过编译的类型,这就造成了运行时和编译时可能存在类型偏差。以下类型是可以通过编译的:
interface Person { // Person相当于A
age: number;
}
class Father { // Father相当于B
age: number;
name: string;
}
let person: Person;
person = new Father();
也就是说TypeScript结构化类型系统的基本规则如下:如果A想要兼容y,那么B至少具有与x相同的属性,
eg: A = B,将B赋值给A,要看A里的每个参数是否能在B中找到相对应的参数,即A的属性个数<=B,从属性上来讲,B包含A,A⊂B
原始类型和对象类型赋值
- 变量 = 值:遵循值类型与变量定义的类型相同原则
- 变量 = 变量:遵循变量定义的类型与变量定义的类型比较原则,详情参见下方
拿一个赋值的例子解释一下赋值时的过程
// 对象赋值
interface Person { // 编译通过
age: number;
}
let person: Person;
const alice = { name: '123', age: 11}
person = alice;
// 检查函数参数
function Test (person: Person){} // 编译通过
Test(alice)
alice在赋值给person时,编译器首先查看person中的每个属性,看是否能在alice中找到所有person应该有的属性,在上面中发现person有的属性age,aliceu 也有,因此就判断赋值合理
这套赋值检查的程序,在检查函数参数时同样奏效,编译给通过
解释一下常见的问题,a赋值给b可以,再这个操作之后将b赋值给a就ts报错
interface A {
m: number;
n: string;
}
interface B {
m: number;
}
// 状态一
let a: A = { m: 1, n:'1' };
let b: B = { m: 2 };
b = a;
a = b; // Property 'n' is missing in type 'B' but required in type 'A'.
// 状态二
let a = { m: 1, n:'1' };
let b: B = { m: 2 };
b = a;
a = b; // Property 'n' is missing in type 'B' but required in type 'A'.
原因是:按照上文解释,b中的的所有属性,a中都有,所以可以将a赋值给b,但是也只是ts判断赋值合理,虽然此刻b的值为{ m: 1,n:'1' },但是实际上他的值的类型还是B类型即interface B { m: number;},这个是定义变量时已经确定了的,即便是值被更改,但是这个变量的类型也不会被更改
interface B {
m: number;
}
// 状态一
let b: B;
b = { m: 3, n: 2 }; // 不能将类型“{ m: number; n: number; }”分配给类型“B”。对象文字可以只指定已知属性,并且“n”不在类型“B”中。
// 状态二
let b: { m:2 };
b = { m: 3, n: 2 }; // 不能将类型“{ m: number; n: number; }”分配给类型“B”。对象文字可以只指定已知属性,并且“n”不在类型“B”中
上述状态一和状态二的原理其实是一样的,b是被直接赋给了值,而不是付给一个变量,编译器就不会去像之前一样对比a、b两个变量的属性类型,那么他就会直接去对比当前B的类型,发现B类型interface B { m: number; }中并不包含n这个属性,ts校验就会报错
注意:上述所有说的报错,仅指ts校验报错,而并非不能使用,即使ts校验报错,也可以赋值成功,与javascript特性有关(弱类型语言)
函数赋值
在判断两个函数是否能够赋值时,TypeScript对比的是函数签名(输入参数类型、输出的数值类型),输入参数的名字、输出的值,是否相同无所谓,只看参数类型
eg:A = B,将B赋值给A,要看B里的每个参数是否能在A中找到相对应的参数,并且位置从第一个参数开始就类型对应,即A的属性个数>=B,从属性上来讲,A包含B,A⊃B ,参照下图
let fun1 = (a: number) => 0;
let fun2 = (b: number, c: string) => 1;
let fun3 = (c: number, d: string) => 0;
let fun4 = (d: string) => 2;
let fun5 = (d: number) => '2';
let fun6 = (b: number, c?: string) => 1;
// 输入参数类型不同
fun1 = fun4; // ❌ 不能将类型“(d: string) => number”分配给类型“(a: number) => number”。参数“d”和“a” 的类型不兼容。不能将类型“number”分配给类型“string”。
// 输出值类型不同
fun4 = fun5; // ❌ 不能将类型“(d: number) => string”分配给类型“(d: string) => number”。参数“d”和“d” 的类型不兼容。不能将类型“string”分配给类型“number”。
// A = B,A包含B,但是B中的参数位置,与A中参数的为值,不能做到从第一位开始的位置映射
fun2 = fun4; // ❌ 不能将类型“(d: string) => number”分配给类型“(b: number, c: string) => number”。参数“d”和“b” 的类型不兼容。不能将类型“number”分配给类型“string”。
// A = B,B的参数多
fun1 = fun2; // ❌ 不能将类型“(b: number, c: string) => number”分配给类型“(a: number) => number”。
// A = B,B的参数多,类型不一致的参数为可选
fun1 = fun6;
// A = B,A包含B
fun2 = fun1;
// 输入参数名不同,输出参数值不同,类型相同
fun2 = fun3;
参上,可赋值形式为:
- A = B,B的参数多,B中与A类型不一致的参数为可选
- A = B,A包含B,并且类型参数位置相同
- A = B,输入参数名不同,输出参数值不同(满足两个的其中一个就行),类型相同
不可赋值形式为:
- A = B,函数输入参数类型不同
- A = B,函数输出值类型不同
- A = B,A包含B,但是B中的参数位置,与A中参数的为值,不能做到从第一位开始的位置映射
- A = B,B的输入参数比A的多,或者A不能包含B即B中有部分参数不在A中