变量类型的那些事
1. 基本注解
类型注解使用: TypeAnnotation 语法. 类型声明空间中可用的任何内容都可以用作类型注解.
- const num: number = 123;
- function identity(num:number):number{
- return num;
- }
加入注解以后会报错的写法:
- const num: number = 123;
- function identity(num: number): number {
- const num1 = '123'
- // 返回的不是 number 报错
- return num1;
- }
- const num1 = '123'
- // 参数不是 number 报错
- identity(num1)
2. 原始类型
JavaScript 原始类型也同样适用于 TypeScript 的类型系统. 因此, string, number,boolean 也可以被用作类型注解:
- let num: number;
- let str: string;
- let bool: boolean;
- num = 123;
- num = 123.45;
- num = '123'; //Type '"123"' is not assignable to type 'number'
- str = '123';
- str = 123; //Type '123' is not assignable to type 'string'
- bool = true;
- bool = false;
- bool = 'false';//Type '"false"' is not assignable to type 'boolean'.
3. 数组
TypeScript 为数组提供了专用的类型语法, 因此你可以很轻易的注解数组. 它使用后缀[], 接着你可以根据需要补充任何有效的类型注解(如: boolean[]). 它能让你安全的使用任何有关数组的操作, 而且它能放置一些类似赋值错误类型给成员的行为.
你有两种定义数组的方式:
第一种, 你可以在元素类型后面接上[], 表示由此类型元素组成的一个数组:
let list:number[] = [1,2,3]
第二种方式是使用数组泛型, Array < 元素类型>:
let list: Array<number> = [1,2,3]
一个具体的栗子:
- const boolArray: boolean[];
- boolArray = [true, false];
- console.log(boolArray[0]); // true
- console.log(boolArray.length); // 2
- boolArray[1] = true;
- boolArray = [false, false];
- boolArray[0] = 'false'; // Error
- boolArray = 'false'; // Error
- boolArray = [true, 'false']; // Error
4. 元组
元组类型允许表示一个已知元素数量和类型的数组, 各元素的类型不必相同. 比如, 你可以定义一对值分别为 string 和 number 类型的元组.
- // 声明一个元组类型
- let x: [string, number];
- // 初始化赋值
- x = ['hello', 10]; // OK
- // 初始化错误
- x = [10, 'hello']; // Error
当访问一个已知索引的元素, 会得到正确的类型:
- console.log(x[0].substr(1)); // OK
- console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
????????? 当访问一个越界的元素, 会使用联合类型替代:
x[3] = 'world'; // OK, 字符串可以赋值给 (string|number) 类型
但是我在编辑器里使用的时候是报错的, Index '2' is out-of-bounds in tuple of length 2, 我也不知道为什么??
5. 枚举
enum 类型是对 JavaScript 标准数据类型的一个补充. 使用枚举类型可以为一组数值赋予友好的名字.
- enum Color {
- Red, Green, Blue
- }
- let c: Color = Color.Green;
默认情况下, 从 0 开始为元素编号. 你也可以手动的指定成员的编号. 例如, 我们将上面的栗子改成从 1 开始编号:
- enum Color {
- Red = 1, Green, Blue
- }
- let c: Color = Color.Green;
或者采用全部手动赋值:
- enum Color {
- Red = 1,Geeen = 2, Blue = 4
- }
- let c: Color = Color.Green
编译后的 JS
- var Color;
- (function (Color) {
- Color[Color["Red"] = 1] = "Red";
- Color[Color["Green"] = 2] = "Green";
- Color[Color["Blue"] = 4] = "Blue";
- })(Color || (Color = {}));
- var c = Color.Green;
所有的表达式都有返回值, 它的返回值就是等号右边的赋值.
枚举类型提供的一个遍历是你可以由枚举的值得到它的名字. 例如, 我们知道数值为 2, 但是不确定它映射到 Color 里的哪个名字, 我们可以查找相应的名字:
- enum Color {
- Red = 1, Green, Blue
- }
- let colorName: string = Color[2];
- console.log(colorName); // 显示'Green'因为上面代码里它的值是 2
这个试了一下不给数值 number, 发现没办法通过这个取值
- enum Color {
- Red = 'r', Green = 'g', Blue = 'b'
- }
- let c: Color = Color.Green;
- console.log(c) // g
- let colorName:string = Color[2]
- console.log(colorName) // undefined
我们看一下编译后的 JavaScript 代码就明白了:
- var Color;
- (function (Color) {
- Color["Red"] = "r";
- Color["Green"] = "g";
- Color["Blue"] = "b";
- })(Color || (Color = {}));
- var c = Color.Green;
- console.log(c);
- var colorName = Color[2];
- console.log(colorName);
6. 特殊类型
6.1 any
any 类型在 TypeScript 类型系统中占有特殊的地位. 它提供给你一个类型系统的[后门] ,TypeScript 将会把类型检查关闭. 在类型系统里 any 能够兼容所有的类型(包括它自己). 因此, 所有的类型都能够被赋值给它. 它也能被赋值给其他任何类型.
- let power: any;
- // 赋值任意类型
- power = '123'
- power = 123
- let num:number;
- num = power;
- power = num;
6.2 null 和 undefined
在类型系统中, JavaScript 中的 null 和 undefined 字面量和其他被标注了 any 类型的变量一样, 都能被赋值给任意类型的变量, 如下例子所示:
- let num: numer;
- let str: string;
- // 这些类型能被赋予
- num = null;
- str = undefined;
6.3 void
使用 :void 来表示一个函数没有一个返回值
- function log(message:string):void{
- console.log(message)
- }
声明一个 void 类型的变量没有什么大用, 因为你只能为它赋予 undefined 和 null:
let unusable:void = undefined
7. 泛型
软件工程中, 我们不仅要创建一致的定义良好的 API, 同时也要考虑可重用性.
不仅能够支持当前的数据类型, 同时也能支持未来的数据类型, 这在创建大型系统时为你提供了十分灵活的功能.
举一个简单的演变例子:
当不实用泛型的时候, 你的代码可能是像这样:
- function identity(arg:number):number{
- return arg;
- }
这个函数可以接收 number 类型, 返回的也是 number 类型, 考虑到这个函数的可重用性, 或者, 我们可以使用 any 类型来定义函数像这样:
- function identity(arg:any):any{
- return arg;
- }
使用 any 类型会导致这个函数可以接受任何类型的 arg 参数, 且任何类型的值都可能被返回.
我们需要一种方法使返回值的类型与传入参数的类型是相同的. 接下来我们可以把代码像下面这样:
- function identity<T>(arg:T):T{
- return arg
- }
我们给 identity 添加了类型变量 T. 如果传入的类型(比如: number), 我们就可以知道返回的类型也是 number. 现在我们就可以知道参数类型与返回值类型是相同的了. 这有助于我们跟踪函数里使用的类型的信息.
我们把这个版本的 identity 函数叫做 泛型. 它可以使用与多个类型, 不同于使用 any, 的不确定性, 还能保持像第一个例子一样的准确性, 参数类型与返回值类型是相同的.
7.1 泛型的使用
我们定义了泛型函数之火, 可以使用两种方法使用.
第一种是传入所有的参数, 包含类型参数:
let output = identity<string>('myString');//type of output will be 'string'
这里我们明确的指定了 T 是 string 类型, 并做为一个参数传给函数, 并且确定 output 的类型是 string.
注意: 使用 <> 不是()
第二种方法, 利用类型推论 -- 即编译器会根据传入的参数自动的帮助我们确定 T 的类型.
let output = identity("myString");// type of output will be 'string'
这里并没有使用 (<>) 来明确地传入类型; 编译器可以查看 myString 的值, 然后把 T 设置为它的类型.
7.2 使用泛型变量
使用泛型创建像 identity 这样的泛型函数时, 编译器要求你在函数体必须正确的使用这个通用的类型. 换句话说, 你必须把这些参数当作时任意或所有类型.
看下我们之前写的泛型例子:
- function identity<T>(arg:T):T{
- return arg
- }
当我们试图在函数内输出 arg.length 的时候, 编译器就会报错类型 "T" 上不存在属性 "length"
- function idenfity<T>(arg:T):T{
- console.log(arg.length) // Error: T doesn't have .length
- return arg
- }
注意:
类型变量 T 代表的时任意类型, 所以使用这个函数的人可能传入的是个数字, 而数字时没有. length 属性的.
然后我们假设想操作 T 类型的数组而不直接是 T, 由于我们操作的是数组, 此时. length 的属性应该是存在.
代码像这样:
- function loggingIdentity<T>(arg: T[]):T[]{
- console.log(arg.length) // Array has a .length,so no more error
- return arg
- }
我们可以这样理解 loggingIdentity 的类型: 泛型函数 loggingIdentity, 接收类型参数 T 和参数 arg, 它是个元素类型是 T 的数组, 并返回元素类型 T 的数组. 如果我们传入数字数组, 将返回一个数字数组, 因为此时 T 的类型为 number. 这可以让我们把泛型变量 T 当作类型的一部分使用, 而不是整个类型, 增加了灵活性.
我们还可以这样实现上面的例子:
- function loggingIdentity<T>(arg:Array<T>):Array<T>{
- console.log(arg.length)
- return arg
- }
在计算机科学中, 许多算法和数据结构并不会依赖于对象的实际类型. 然而, 你仍然会想在每个变量里强制提供约束.
例如: 在一个函数中, 它接受一个列表, 并且返回这个列表的反向排序, 这里的约束是指传入至函数的参数与函数的返回值:
- function reverse<T>(items:T[]):T[]{
- const toreturn = []
- for(let i = items.length - 1;i>=0;i--){
- toreturn.push(items[i])
- }
- return toreturn
- }
- const sample = [1,2,3]
- let reversed = reverse(sample)
- console.log(reversed)
- reversed[0] = '1'; // Error
- reversed = ['1', '2']; // Error
- reversed[0] = 1; // ok
- reversed = [1, 2]; // ok
这个栗子中, 函数 reverse 接受一个类型为 T 的数组 items:T[], 返回值为类型 T 的一个数组 (注意: T[]), 函数 reverse 的返回值类型与它接受的参数类型一样. 当你传入 var sample = [1, 2, 3] 时, TypeScript 能推断出 reverse 为 number[]类型, 从而能给你类型安全. 于此相似, 当你传递一个 string[]类型的数组时, TypeScript 能推断出为 string[]类型, 如:
- const strArr = ['1','2']
- let reversedStrs = reverse(strArr)
- reversedStrs = [1, 2]; // Error
如果你的数组是 const sample = [1,false,3]等同于 const sample: (number | boolean)[], 所以下面的代码也能可以的, 如果是数组里有两种甚至三种类型的时候, 它是能够推断出 (number | boolean) 这种或的类型的. 也就是下面所说的联合类型.
- function reverse<T>(items:T[]):T[]{
- const toreturn = []
- for(let i = items.length - 1;i>=0;i--){
- toreturn.push(items[i])
- }
- return toreturn
- }
- const sample = [1,false,3]
- let reversed = reverse(sample)
- console.log(reversed)
- reversed[0] = true; // OK
- reversed = [3, true]; // OK
- reversed[0] = 1; // ok
- reversed = [1, 2]; // ok
8. 联合类型
联合类型表示取值可以为多种类型中的一种.
8.1 简单的例子
- let myFavoriteNumber: string | number;
- myFavoriteNumber = 'seven'
- myFavoriteNumber = 7
- let myFavoriteNumber: string | number;
- myFavoriteNumber = true;
- // index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
- // Type 'boolean' is not assignable to type 'number'
联合类型使用 | 分隔每个类型.
这里的 let myFavoriteNumber: string | number; 的含义是, 允许 myFavoriteNumber 的类型是 string 或者 number, 但是不能是其他类型.
8.2 访问联合类型的属性或方法
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候, 我们只能访问此联合类型的所有类型里的共有的属性或方法.
- function getLength(something:string|number):number{
- return something.length
- }
- // Error 类型 "string | number" 上不存在属性 "length".
类型 "number" 上不存在属性 "length".
上例中, length 不是 string 和 number 的共有属性, 所以会报错.
访问 string 和 number 的共有属性是没问题的:
- function getLength(something:string|number):string{
- return something.toString()
- }
联合类型的变量在被赋值的时候, 会根据类型推论的规则推断出一个类型:
- let myFavoriteNumber:string|number;
- myFavoriteNumber = 'seven';
- console.log(myFavoriteNumber.length)
- myFavoriteNumber = 7
- console.log(myFavoriteNumber.length) // Rrror 类型 "number" 上不存在属性 "length".
上例中, 第二行的 myFavoriteNumber 被推断成了 string, 访问它的 length 属性不会报错. 而第四行的 myFavoriteNumber 被推断成了 number, 访问它的 length 属性时就报错了.
一个常见的用例是一个可以接受单个对象或者对象数组的函数:
- function formatCommandline(command: string[] | string) {
- let line = '';
- if (typeof command === 'string') {
- line = command.trim();
- } else {
- line = command.join(' ').trim();
- }
- // Do stuff with line: string
- }
来源: https://juejin.im/post/5c3eca17f265da61461e6707