any,unknown,never, 数组,元组,对象(object)

any


有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。
一般情况下,不推荐定义 any 类型,通常在不得已的情况下,不应该首先考虑使用此类型, any 不作任何数据效验,

  1. let _any: any = 1;
  2. _any = "aaa";
  3. _any = false;
  4. _any = null;

unknown


unknown 是 TypeScript 3.0 引入了新类型,是 any 类型对应的安全类型。
unknown 和 any 的主要区别是 unknown 类型会更加严格:在对unknown类型的值执行大多数操作之前,我们必须进行某种形式的检查,而在对 any 类型的值执行操作之前,我们不必进行任何检查。
我们先看一下他跟 any 的共同点,它跟 any 一样,可以是任何类型:

  1. let value: any;
  2. value = true; // OK
  3. value = 1; // OK
  4. value = "Hello World"; // OK
  5. value = Symbol("type"); // OK
  6. value = {} // OK
  7. value = [] // OK
  8. let value1: unknown;
  9. value1 = true; // OK
  10. value1 = 1; // OK
  11. value1 = "Hello World"; // OK
  12. value1 = Symbol("type"); // OK
  13. value1 = {} // OK
  14. value1 = [] // OK

那我们看看它们的区别在哪里:

  1. let value: any;
  2. value.foo.bar; // OK
  3. value(); // OK
  4. new value(); // OK
  5. value[0][1]; // OK
  6. let value1: unknown;
  7. value1.foo.bar; // ERROR
  8. value1(); // ERROR
  9. new value1(); // ERROR
  10. value1[0][1]; // ERROR

我们看到,这就是 unknown 与 any 的不同之处,虽然它们都可以是任何类型,但是当 unknown 类型被确定是某个类型之前,它不能被进行任何操作比如实例化、getter、函数执行等等。
而 any 是可以的,这也是为什么说 unknown 是更安全的 any, any 由于过于灵活的设定,导致它与 JavaScript 没有太多区别,很容易产生低级错误,很多场景下我们可以选择 unknown 作为更好的替代品.
所以,unknown 比 any 更安全

  • unknown 也可以被使用与双重断言
  • 所以当你不知道一个变量的类型的时候,尽量使用 unknown 来代替 any
  • {} 这个类型包含来所有的值,除了 null 和 undefined
  • Object 类型包含了所有的非原始值类型,包含 null,array,objects 但是不包含 undefined

    never


never 类型表示的是那些永不存在的值的类型,never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了never本身之外)。

即使any也不可以赋值给never。

  1. // 空数组,而且永远是空的
  2. const empty: never[] = []
  3. // 返回never的函数必须存在无法达到的终点
  4. function error(message: string): never {
  5. throw new Error(message);
  6. }
  7. // 推断的返回值类型为never
  8. function fail() {
  9. return error("Something failed");
  10. }
  11. // 返回never的函数必须存在无法达到的终点
  12. function infiniteLoop(): never {
  13. while (true) {
  14. }
  15. }
  16. // 无法把其他类型赋给 never
  17. let obj_never: never
  18. // ERROR
  19. // bad Type 'string' is not assignable to type 'never'.
  20. obj_never = 'string'
  21. // ERROR
  22. // Type 'null' is not assignable to type 'never'.
  23. obj_never = null
  24. // ERROR
  25. // Type 'undefined' is not assignable to type 'never'.
  26. obj_never = undefined
  27. let obj_any: any = 'any'
  28. // ERROR
  29. // Type 'any' is not assignable to type 'never'.
  30. obj_never = obj_any

其它特性 在一个函数中调用了返回 never 的函数后,之后的代码都会变成 Unreachable code detected.

  1. function unReachable() {
  2. errorNever()
  3. // ⚠️Unreachable code detected.
  4. const someObject = 'someObject'
  5. console.log(someObject)
  6. }

数组


TypeScript 为数组提供了专用的类型语法,因此你可以很轻易的注解数组。它使用后缀 [] , 接着你可以根据需要补充任何有效的类型注解(如: :boolean[] )。它能让你安全的使用任何有关 数组的操作,而且它也能防止一些类似于赋值错误类型给成员的行为。如下所示:

数组有两种类型定义方式:

1.使用泛型

  1. const list: Array<number> = [1, 2, 3]

2.在元素类型后面接上 []:

  1. const boolArray: boolean[];
  2. boolArray = [true, false];
  3. console.log(boolArray[0]); // true
  4. console.log(boolArray.length); // 2
  5. boolArray[1] = true;
  6. boolArray = [false, false];
  7. boolArray[0] = 'false'; // Error
  8. boolArray = 'false'; // Error
  9. boolArray = [true, 'false']; // Error
  10. let arr1: string[] = ['1', '2'];
  11. let arr2: Array<string> = ['1', '2'];
  12. // 只能是 字符串类型的数组
  13. let arr3: (number | string)[] = [1, '1'];

元组(Tuple)


元组类型与数组类型非常相似,表示一个已知元素数量和类型的数组
各元素的类型不必相同,数组中的元素必须是相同的类型。

比如,你可以定义一对值分别为string和number类型的元组。

  1. let x: [string, number];
  2. x = ['hello', 10, false] // Error
  3. x = ['hello'] // Error

我们看到,这就是元组与数组的不同之处,元组的类型如果多出或者少于规定的类型是会报错的,必须严格跟事先声明的类型一致才不会报错。
那么有人会问,我们的类型完全一致,只是顺序错了有没有问题,比如上个例子中我们把 string、number 调换位置:

  1. let x: [string, number];
  2. x = ['hello', 10]; // OK
  3. x = [10, 'hello']; // Error

我们看到,元组非常严格,即使类型的顺序不一样也会报错。
元组中包含的元素,必须与声明的类型一致,而且不能多、不能少,甚至顺序不能不符。
我们可以把元组看成严格版的数组,比如[string, number]我们可以看成是:

  1. interface Tuple extends Array<string | number> {
  2. 0: string;
  3. 1: number;
  4. length: 2;
  5. }

元组继承于数组,但是比数组拥有更严格的类型检查。
此外,还有一个个元组越界问题,比如 Typescript 允许向元组中使用数组的push方法插入新元素:

  1. const tuple: [string, number] = ['a', 1];
  2. tuple.push(2); // ok
  3. console.log(tuple); // ["a", 1, 2] -> 正常打印出来

但是当我们访问新加入的元素时,会报错:

  1. console.log(tuple[2]); // Tuple type '[string, number]' of length '2' has no element at index '2'

Object


object 表示非原始类型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的类型。
object 表示任何非原始值类型,包括 对象、函数、枚举、数组、元组 通通都是 object 类型
当对object类型的变量赋予原始值时,TS编译器会报错

  1. let a: object;
  2. a = {};
  3. a = [1, 2, 3];
  4. a = [1, true, "abc"];
  5. a = () => 1;
  6. a = 11; // ERROR 不能将类型"number"分配给类型"object"

枚举类型


枚举类型是很多语言都拥有的类型,它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型

数字枚举


当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:

  1. enum NumberE {
  2. Up,
  3. Down,
  4. Left,
  5. Right
  6. }
  7. console.log(NumberE.Up, NumberE.Down, NumberE.Left, NumberE.Right); // 0 1 2 3

因此当我们把第一个值赋值后,后面也会根据第一个值进行累加:

  1. enum NumberE {
  2. Up = 10,
  3. Down,
  4. Left,
  5. Right
  6. }
  7. console.log(NumberE.Up, NumberE.Down, NumberE.Left, NumberE.Right); // 10 11 12 13

字符串枚举


枚举类型的值其实也可以是字符串类型:

  1. enum StringE {
  2. Up = 'Up',
  3. Down = 'Down',
  4. Left = 'Left',
  5. Right = 'Right'
  6. }
  7. console.log(StringE['Right'], StringE.Up); // Right Up

异构枚举


通常情况下我们很少会这样使用枚举,但是从技术的角度来说,它是可行的。

  1. enum OuterEnum {
  2. No = 0,
  3. Yes = "YES",
  4. }

反向映射


  1. enum Direction {
  2. Up,
  3. Down,
  4. Left,
  5. Right
  6. }
  7. console.log(Direction.Up); // 0
  8. console.log(Direction[0]); // up

常量枚举


  1. const enum Direction {
  2. Up = 'Up',
  3. Down = 'Down',
  4. Left = 'Left',
  5. Right = 'Right'
  6. }
  7. const a = Direction.Up;

编译为 JavaScript 后:

  1. let a = "Up";

联合枚举与枚举成员的类型


假设枚举的所有成员都是字面量类型的值,那么枚举的每个成员和枚举值本身都可以作为类型来使用,
字面量枚举

  1. // 数组字面量枚举
  2. const enum DirectionN {
  3. Up,
  4. Down,
  5. Left,
  6. Right
  7. }
  8. // 字符串字面量枚举
  9. const enum DirectionS {
  10. Up = 'Up',
  11. Down = 'Down',
  12. Left = 'Left',
  13. Right = 'Right'
  14. }
  15. // 应用了一元 - 符号的数字字面量
  16. enum DirectionY {
  17. Up = -1,
  18. Down = -2,
  19. Left = -3,
  20. Right = -4,
  21. }
  22. const a = 0
  23. console.log(a === DirectionN.Up) // true
  24. type c = 0
  25. // 使用declare var声明变量。
  26. // 如果变量是只读的,那么可以使用 declare const。
  27. // 你还可以使用 declare let如果变量拥有块级作用域。
  28. declare let b: c
  29. b = 1 // 不能将类型“1”分配给类型“0”
  30. b = DirectionN.Up // ok

枚举成员使用 常量枚举表达式初始化。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
一个枚举表达式字面量(主要是字符串字面量或数字字面量)
一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
带括号的常量枚举表达式
一元运算符 +, -, ~其中之一应用在了常量枚举表达式
常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN

  1. // 应用了一元 - 符号的数字字面量
  2. enum DirectionY {
  3. Up = -1,
  4. Down = -2,
  5. Left = -3,
  6. Right = -4,
  7. }
  8. const getValue = () => {
  9. return 0
  10. }
  11. enum List {
  12. A = getValue(),
  13. B = 2, // 此处必须要初始化值,不然编译不通过
  14. C
  15. }
  16. console.log(List.A) // 0
  17. console.log(List.B) // 2
  18. console.log(List.C) // 3

或 Infinity,则会在编译阶段报错。

枚举合并


  1. enum Direction {
  2. Up = 'Up',
  3. Down = 'Down',
  4. Left = 'Left',
  5. Right = 'Right'
  6. }
  7. enum Direction {
  8. Center = 1
  9. }
  10. console.log(DirectionN.Up,Direction.Center) // Up,1

为枚举添加静态方法


  1. enum Mode {
  2. X,
  3. Y
  4. }
  5. // namespace 命名空间
  6. namespace Mode {
  7. export function toString(mode: Mode): string {
  8. return Mode[mode];
  9. }
  10. export function parse(mode: any): tring {
  11. return Mode[mode];
  12. }
  13. }
  14. const mode = Mode.X;
  15. const str = Mode.toString(mode);
  16. console.log(str); // X
  17. console.log m = Mode.parse(str);
  18. alert(m); // 0

接口(interface)


接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明:

  1. interface Name {
  2. first: string;
  3. second: string;
  4. }
  5. let _name1: Name;
  6. let _name2: Name;
  7. let _name3: Name;
  8. _name1 = {
  9. first: 'AAA',
  10. second: 'BBB',
  11. };
  12. _name2 = {
  13. // Error: 'Second is missing'
  14. first:'AAA',
  15. }
  16. _name3 = {
  17. first: 'AAA',
  18. second: 11111, // // Error: 'Second is the wrong type'
  19. };

在这里,我们把类型注解: first: string + second: string 合并到了一个新的类型注解里 Name ,这样能强制对每个成员进行类型检查。接口在 TypeScript 拥有强大的力量,在稍后,我 们将会用整个章节来阐述如何更好的使用它。

可选属性


  1. interface User {
  2. name: string
  3. age?: number
  4. isMale: boolean
  5. }

只读属性


  1. interface User {
  2. name: string
  3. age?: number
  4. readonly isMale: boolean
  5. }

函数类型


  1. // user 含有一个函数
  2. user.say = function(words: string) {
  3. return 'hello world'
  4. }
  5. // 1.
  6. interface User {
  7. name: string
  8. age?: number
  9. readonly isMale: boolean
  10. say: (words: string) => string
  11. }
  12. // 2.
  13. interface Say {
  14. (words: string) : string
  15. }
  16. interface User {
  17. name: string
  18. age?: number
  19. readonly isMale: boolean
  20. say: Say
  21. }

属性检查


假设我们有一个 Config 接口如下

  1. interface Config {
  2. width?: number;
  3. }
  4. function CalculateAreas(config: Config): { area: number} {
  5. let square = 100;
  6. if (config.width) {
  7. square = config.width * config.width;
  8. }
  9. return {area: square};
  10. }
  11. let mySquare = CalculateAreas({ widdth: 5 });
  12. // 注意我们传入的参数是 widdth,并不是 width。
  13. // ERROR: 'widdth' not expected in type 'Config'

目前官网推荐了三种主流的解决办法:

  1. // 1.第一种使用类型断言:
  2. let mySquare = CalculateAreas({ widdth: 5 } as Config);
  3. // 2.
  4. interface Config {
  5. width?: number;
  6. [propName: string]: any;
  7. }
  8. // 这样Config可以有任意数量的属性,并且只要不是width,那么就无所谓他们的类型是什么了
  9. // 3.第三种将字面量赋值给另外一个变量:
  10. let options: any = { widdth: 5 };
  11. let mySquare = CalculateAreas(options);
  12. // 本质上是转化为 any 类型,除非有万不得已的情况,不建议采用上述方法。

内联类型


与创建一个接口不同,你可以使用内联注解语法注解任何内容: :{ /Structure/ } :

  1. let name: {
  2. first: string;
  3. second: string;
  4. };
  5. name = {
  6. first: 'John',
  7. second: 'Doe'
  8. };
  9. name = {
  10. // Error: 'Second is missing'
  11. first: 'John'
  12. };
  13. name = {
  14. // Error: 'Second is the wrong type'
  15. first: 'John',
  16. second: 1337
  17. };

内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦(你可能会使用一个很 糟糕的名称)。然而,如果你发现需要多次使用相同的内联注解时,考虑把它重构为一个接口(或者是 type alias ,它会在接下来的部分提到)是一个不错的注意。