any,unknown,never, 数组,元组,对象(object)
any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。
一般情况下,不推荐定义 any 类型,通常在不得已的情况下,不应该首先考虑使用此类型, any 不作任何数据效验,
let _any: any = 1;_any = "aaa";_any = false;_any = null;
unknown
unknown 是 TypeScript 3.0 引入了新类型,是 any 类型对应的安全类型。
unknown 和 any 的主要区别是 unknown 类型会更加严格:在对unknown类型的值执行大多数操作之前,我们必须进行某种形式的检查,而在对 any 类型的值执行操作之前,我们不必进行任何检查。
我们先看一下他跟 any 的共同点,它跟 any 一样,可以是任何类型:
let value: any;value = true; // OKvalue = 1; // OKvalue = "Hello World"; // OKvalue = Symbol("type"); // OKvalue = {} // OKvalue = [] // OKlet value1: unknown;value1 = true; // OKvalue1 = 1; // OKvalue1 = "Hello World"; // OKvalue1 = Symbol("type"); // OKvalue1 = {} // OKvalue1 = [] // OK
那我们看看它们的区别在哪里:
let value: any;value.foo.bar; // OKvalue(); // OKnew value(); // OKvalue[0][1]; // OKlet value1: unknown;value1.foo.bar; // ERRORvalue1(); // ERRORnew value1(); // ERRORvalue1[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。
// 空数组,而且永远是空的const empty: never[] = []// 返回never的函数必须存在无法达到的终点function error(message: string): never {throw new Error(message);}// 推断的返回值类型为neverfunction fail() {return error("Something failed");}// 返回never的函数必须存在无法达到的终点function infiniteLoop(): never {while (true) {}}// 无法把其他类型赋给 neverlet obj_never: never// ERROR// bad Type 'string' is not assignable to type 'never'.obj_never = 'string'// ERROR// Type 'null' is not assignable to type 'never'.obj_never = null// ERROR// Type 'undefined' is not assignable to type 'never'.obj_never = undefinedlet obj_any: any = 'any'// ERROR// Type 'any' is not assignable to type 'never'.obj_never = obj_any
其它特性 在一个函数中调用了返回 never 的函数后,之后的代码都会变成 Unreachable code detected.
function unReachable() {errorNever()// ⚠️Unreachable code detected.const someObject = 'someObject'console.log(someObject)}
数组
TypeScript 为数组提供了专用的类型语法,因此你可以很轻易的注解数组。它使用后缀 [] , 接着你可以根据需要补充任何有效的类型注解(如: :boolean[] )。它能让你安全的使用任何有关 数组的操作,而且它也能防止一些类似于赋值错误类型给成员的行为。如下所示:
数组有两种类型定义方式:
1.使用泛型
const list: Array<number> = [1, 2, 3]
2.在元素类型后面接上 []:
const boolArray: boolean[];boolArray = [true, false];console.log(boolArray[0]); // trueconsole.log(boolArray.length); // 2boolArray[1] = true;boolArray = [false, false];boolArray[0] = 'false'; // ErrorboolArray = 'false'; // ErrorboolArray = [true, 'false']; // Errorlet arr1: string[] = ['1', '2'];let arr2: Array<string> = ['1', '2'];// 只能是 字符串类型的数组let arr3: (number | string)[] = [1, '1'];
元组(Tuple)
元组类型与数组类型非常相似,表示一个已知元素数量和类型的数组
各元素的类型不必相同,数组中的元素必须是相同的类型。
比如,你可以定义一对值分别为string和number类型的元组。
let x: [string, number];x = ['hello', 10, false] // Errorx = ['hello'] // Error
我们看到,这就是元组与数组的不同之处,元组的类型如果多出或者少于规定的类型是会报错的,必须严格跟事先声明的类型一致才不会报错。
那么有人会问,我们的类型完全一致,只是顺序错了有没有问题,比如上个例子中我们把 string、number 调换位置:
let x: [string, number];x = ['hello', 10]; // OKx = [10, 'hello']; // Error
我们看到,元组非常严格,即使类型的顺序不一样也会报错。
元组中包含的元素,必须与声明的类型一致,而且不能多、不能少,甚至顺序不能不符。
我们可以把元组看成严格版的数组,比如[string, number]我们可以看成是:
interface Tuple extends Array<string | number> {0: string;1: number;length: 2;}
元组继承于数组,但是比数组拥有更严格的类型检查。
此外,还有一个个元组越界问题,比如 Typescript 允许向元组中使用数组的push方法插入新元素:
const tuple: [string, number] = ['a', 1];tuple.push(2); // okconsole.log(tuple); // ["a", 1, 2] -> 正常打印出来
但是当我们访问新加入的元素时,会报错:
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编译器会报错
let a: object;a = {};a = [1, 2, 3];a = [1, true, "abc"];a = () => 1;a = 11; // ERROR 不能将类型"number"分配给类型"object"
枚举类型
枚举类型是很多语言都拥有的类型,它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型
数字枚举
当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:
enum NumberE {Up,Down,Left,Right}console.log(NumberE.Up, NumberE.Down, NumberE.Left, NumberE.Right); // 0 1 2 3
因此当我们把第一个值赋值后,后面也会根据第一个值进行累加:
enum NumberE {Up = 10,Down,Left,Right}console.log(NumberE.Up, NumberE.Down, NumberE.Left, NumberE.Right); // 10 11 12 13
字符串枚举
枚举类型的值其实也可以是字符串类型:
enum StringE {Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right'}console.log(StringE['Right'], StringE.Up); // Right Up
异构枚举
通常情况下我们很少会这样使用枚举,但是从技术的角度来说,它是可行的。
enum OuterEnum {No = 0,Yes = "YES",}
反向映射
enum Direction {Up,Down,Left,Right}console.log(Direction.Up); // 0console.log(Direction[0]); // up
常量枚举
const enum Direction {Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right'}const a = Direction.Up;
编译为 JavaScript 后:
let a = "Up";
联合枚举与枚举成员的类型
假设枚举的所有成员都是字面量类型的值,那么枚举的每个成员和枚举值本身都可以作为类型来使用,
字面量枚举
// 数组字面量枚举const enum DirectionN {Up,Down,Left,Right}// 字符串字面量枚举const enum DirectionS {Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right'}// 应用了一元 - 符号的数字字面量enum DirectionY {Up = -1,Down = -2,Left = -3,Right = -4,}const a = 0console.log(a === DirectionN.Up) // truetype c = 0// 使用declare var声明变量。// 如果变量是只读的,那么可以使用 declare const。// 你还可以使用 declare let如果变量拥有块级作用域。declare let b: cb = 1 // 不能将类型“1”分配给类型“0”b = DirectionN.Up // ok
枚举成员使用 常量枚举表达式初始化。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
一个枚举表达式字面量(主要是字符串字面量或数字字面量)
一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
带括号的常量枚举表达式
一元运算符 +, -, ~其中之一应用在了常量枚举表达式
常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN
// 应用了一元 - 符号的数字字面量enum DirectionY {Up = -1,Down = -2,Left = -3,Right = -4,}const getValue = () => {return 0}enum List {A = getValue(),B = 2, // 此处必须要初始化值,不然编译不通过C}console.log(List.A) // 0console.log(List.B) // 2console.log(List.C) // 3
或 Infinity,则会在编译阶段报错。
枚举合并
enum Direction {Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right'}enum Direction {Center = 1}console.log(DirectionN.Up,Direction.Center) // Up,1
为枚举添加静态方法
enum Mode {X,Y}// namespace 命名空间namespace Mode {export function toString(mode: Mode): string {return Mode[mode];}export function parse(mode: any): tring {return Mode[mode];}}const mode = Mode.X;const str = Mode.toString(mode);console.log(str); // Xconsole.log m = Mode.parse(str);alert(m); // 0
接口(interface)
接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明:
interface Name {first: string;second: string;}let _name1: Name;let _name2: Name;let _name3: Name;_name1 = {first: 'AAA',second: 'BBB',};_name2 = {// Error: 'Second is missing'first:'AAA',}_name3 = {first: 'AAA',second: 11111, // // Error: 'Second is the wrong type'};
在这里,我们把类型注解: first: string + second: string 合并到了一个新的类型注解里 Name ,这样能强制对每个成员进行类型检查。接口在 TypeScript 拥有强大的力量,在稍后,我 们将会用整个章节来阐述如何更好的使用它。
可选属性
interface User {name: stringage?: numberisMale: boolean}
只读属性
interface User {name: stringage?: numberreadonly isMale: boolean}
函数类型
// user 含有一个函数user.say = function(words: string) {return 'hello world'}// 1.interface User {name: stringage?: numberreadonly isMale: booleansay: (words: string) => string}// 2.interface Say {(words: string) : string}interface User {name: stringage?: numberreadonly isMale: booleansay: Say}
属性检查
假设我们有一个 Config 接口如下
interface Config {width?: number;}function CalculateAreas(config: Config): { area: number} {let square = 100;if (config.width) {square = config.width * config.width;}return {area: square};}let mySquare = CalculateAreas({ widdth: 5 });// 注意我们传入的参数是 widdth,并不是 width。// ERROR: 'widdth' not expected in type 'Config'
目前官网推荐了三种主流的解决办法:
// 1.第一种使用类型断言:let mySquare = CalculateAreas({ widdth: 5 } as Config);// 2.interface Config {width?: number;[propName: string]: any;}// 这样Config可以有任意数量的属性,并且只要不是width,那么就无所谓他们的类型是什么了// 3.第三种将字面量赋值给另外一个变量:let options: any = { widdth: 5 };let mySquare = CalculateAreas(options);// 本质上是转化为 any 类型,除非有万不得已的情况,不建议采用上述方法。
内联类型
与创建一个接口不同,你可以使用内联注解语法注解任何内容: :{ /Structure/ } :
let name: {first: string;second: string;};name = {first: 'John',second: 'Doe'};name = {// Error: 'Second is missing'first: 'John'};name = {// Error: 'Second is the wrong type'first: 'John',second: 1337};
内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦(你可能会使用一个很 糟糕的名称)。然而,如果你发现需要多次使用相同的内联注解时,考虑把它重构为一个接口(或者是 type alias ,它会在接下来的部分提到)是一个不错的注意。
