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; // OK
value = 1; // OK
value = "Hello World"; // OK
value = Symbol("type"); // OK
value = {} // OK
value = [] // OK
let value1: unknown;
value1 = true; // OK
value1 = 1; // OK
value1 = "Hello World"; // OK
value1 = Symbol("type"); // OK
value1 = {} // OK
value1 = [] // OK
那我们看看它们的区别在哪里:
let value: any;
value.foo.bar; // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
let value1: unknown;
value1.foo.bar; // ERROR
value1(); // ERROR
new value1(); // ERROR
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。
// 空数组,而且永远是空的
const empty: never[] = []
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
// 无法把其他类型赋给 never
let 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 = undefined
let 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]); // true
console.log(boolArray.length); // 2
boolArray[1] = true;
boolArray = [false, false];
boolArray[0] = 'false'; // Error
boolArray = 'false'; // Error
boolArray = [true, 'false']; // Error
let 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] // Error
x = ['hello'] // Error
我们看到,这就是元组与数组的不同之处,元组的类型如果多出或者少于规定的类型是会报错的,必须严格跟事先声明的类型一致才不会报错。
那么有人会问,我们的类型完全一致,只是顺序错了有没有问题,比如上个例子中我们把 string、number 调换位置:
let x: [string, number];
x = ['hello', 10]; // OK
x = [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); // ok
console.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); // 0
console.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 = 0
console.log(a === DirectionN.Up) // true
type c = 0
// 使用declare var声明变量。
// 如果变量是只读的,那么可以使用 declare const。
// 你还可以使用 declare let如果变量拥有块级作用域。
declare let b: c
b = 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) // 0
console.log(List.B) // 2
console.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); // X
console.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: string
age?: number
isMale: boolean
}
只读属性
interface User {
name: string
age?: number
readonly isMale: boolean
}
函数类型
// user 含有一个函数
user.say = function(words: string) {
return 'hello world'
}
// 1.
interface User {
name: string
age?: number
readonly isMale: boolean
say: (words: string) => string
}
// 2.
interface Say {
(words: string) : string
}
interface User {
name: string
age?: number
readonly isMale: boolean
say: 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 ,它会在接下来的部分提到)是一个不错的注意。