Typescript
我们先来看下如何为一个变量指定类型:为一个变量指定类型的语法是使用"变量: 类型"的形式,如下:
let num: number = 123
1.布尔类型
类型为布尔类型的变量的值只能是 true 或 false,如下:
let bool: boolean = false;
bool = true;
bool = 123; // error 不能将类型"123"分配给类型"boolean"
//当然了,赋给 bool 的值也可以是一个计算之后结果是布尔值的表达式,比如:
let bool: boolean = !!0
console.log(bool) // false
2.数值类型
TypeScript 和 JavaScript 一样,所有数字都是浮点数,所以只有一个number类型,而没有int或者float类型。而且 TypeScript 还支持 ES6 中新增的二进制和八进制数字字面量,所以 TypeScript 中共支持二、八、十和十六四种进制的数值。
let num: number;
num = 123;
num = "123"; // error 不能将类型"123"分配给类型"number"
num = 0b1111011; // 二进制的123
num = 0o173; // 八进制的123
num = 0x7b; // 十六进制的123
3.字符串
字符串类型中你可以使用单引号和双引号包裹内容,但是可能你使用的 tslint 规则会对引号进行检测,使用单引号还是双引号可以在 tslint 规则里配置。你还可以使用 ES6 语法——模板字符串,拼接变量和字符串更为方便。
let str: string = "Lison";
str = "Li";
const first = "Lison";
const last = "Li";
str = `${first} ${last}`;
console.log(str) // 打印结果为:Lison Li
另外还有个和字符串相关的类型:字符串字面量类型。即把一个字符串字面量作为一种类型,比如上面的字符串"Lison",当你把一个变量指定为这个字符串类型的时候,就不能再赋值为其他字符串值了,
如:
let str: 'Lison'
str = 'haha' // error 不能将类型“"haha"”分配给类型“"Lison"”
4.数组
在 TypeScript 中有两种定义数组的方式:
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
第一种形式通过number[]的形式来指定这个类型元素均为number类型的数组类型,这种写法是推荐的写法,当然你也可以使用第二种写法。注意,这两种写法中的number指定的是数组元素的类型,你也可以在这里将数组的元素指定为任意类型。如果你要指定一个数组里的元素既可以是数值也可以是字符串,那么你可以使用这种方式:number|string[],这种方式我们在后面学习联合类型的时候会讲到。
当你使用第二种形式定义时,tslint 可能会警告让你使用第一种形式定义,如果你就是想用第二种形式,可以通过在 tslint.json 的 rules 中加入"array-type": [false]关闭 tslint 对这条的检测。
5.null和undefined
因为在 JavaScript 中,undefined 和 null 是两个基本数据类型。在 TypeScript 中,这两者都有各自的类型即 undefined 和 null,也就是说它们既是实际的值,也是类型,来看实际例子:
let u: undefined = undefined;// 这里可能会报一个tslint的错误:Unnecessary initialization to 'undefined',就是不能给一个值赋undefined,但我们知道这是可以的,所以如果你的代码规范想让这种代码合理化,可以配置tslint,将"no-unnecessary-initializer"设为false即可
let n: null = null;
默认情况下 undefined 和 null 可以赋值给任意类型的值,也就是说你可以把 undefined 赋值给 void 类型,也可以赋值给 number 类型。当你在 tsconfig.json 的"compilerOptions"里设置了"strictNullChecks": true时,那必须严格对待。undefined 和 null 将只能赋值给它们自身和 void 类型,
6.Object
object 类型的变量存的是引用,看个简单的例子:
let strInit = "abc";
let strClone = strInit;
strClone = "efg";
console.log(strInit); // 'abc'
let objInit = { a: "aa" };
let objClone = objInit;
console.log(objClone) // {a:"aa"}
objInit.a = "bb";
console.log(objClone); // { a: 'bb' }
我们修改 objInit 时,objClone 也被修改了,是因为 objClone 保存的是 objInit 的引用,实际上 objInit 和 objClone 是同一个对象
当我们希望一个变量或者函数的参数的类型是一个对象的时候,使用这个类型,比如:
let obj: object
obj = { name: 'Lison' }
obj = 123 // error 不能将类型“123”分配给类型“object”
let obj: object
obj = { name: 'Lison' }
console.log(obj.name) // error 类型“object”上不存在属性“name”
上面报错的原因是object不存在属性name,这个需求需要用到我后面会说到的interface接口
当你希望一个值必须是对象而不是数值等类型时,比如我们定义一个函数,参数必须是对象,这个时候就用到object类型了
unction getKeys (obj: object) {
return Object.keys(obj) // 会以列表的形式返回obj中的值
}
getKeys({ a: 'a' }) // ['a']
getKeys(123) // error 类型“123”的参数不能赋给类型“object”的参数
7.元组
元组可以看做是数组的拓展,它表示已知元素数量和类型的数组。确切地说,是已知数组中每一个位置上的元素的类型,来看例子
let tuple: [string, number, boolean];
tuple = ["a", 2, false];
tuple = [2, "a", false]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。
tuple = ["a", 2]; // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]'
可以看到,上面我们定义了一个元组 tuple,它包含三个元素,且每个元素的类型是固定的。当我们为 tuple 赋值时:各个位置上的元素类型都要对应,元素个数也要一致。
我们还可以给单个元素赋值:
tuple[1] = 3;
这里我们给元组 tuple 的索引为 1 即第二个元素赋值为 3,第二个元素类型为 number,我们赋值给 3,所以没有问题。
tuple[0].split(":"); // right 类型"string"拥有属性"split"
tuple[1].split(":"); // error 类型“number”上不存在属性“split”
上面的例子中,我们访问的 tuple 的第二个元素的元素类型为 number,而数值没有 split 方法,所以会报错。
8.枚举
TypeScript 在 ES 原有类型基础上加入枚举类型,使我们在 TypeScript 中也可以给一组数值赋予名字,这样对开发者来说较为友好。比如我们要定义一组角色,每一个角色用一个数字代表,就可以使用枚举类型来定义:
enum Roles {
SUPER_ADMIN,
ADMIN,
USER
}
上面定义的枚举类型 Roles 里面有三个值,TypeScript 会为它们每个值分配编号,默认从 0 开始,依次排列,所以它们对应的值是:
enum Roles {
SUPER_ADMIN = 0,
ADMIN = 1,
USER = 2
}
当我们使用的时候,就可以使用名字而不需要记数字和名称的对照关系了:
const superAdmin = Roles.SUPER_ADMIN;
console.log(superAdmin); // 0
你也可以修改这个数值,比如你想让这个编码从 1 开始而不是 0,可以如下定义:
enum Roles {
SUPER_ADMIN = 1,
ADMIN,
USER
}
这样当你访问Roles.ADMIN时,它的值就是 2 了。
你也可以为每个值都赋予不同的、不按顺序排列的值:
enum Roles {
SUPER_ADMIN = 1,
ADMIN = 3,
USER = 7
}
通过名字 Roles.SUPER_ADMIN 可以获取到它对应的值 1,同时你也可以通过值获取到它的名字,以上面任意数值这个例子为前提:
console.log(Roles[3]); // 'ADMIN'
9.Any
JavaScript 的类型是灵活的,程序有时也是多变的。有时,我们在编写代码的时候,并不能清楚地知道一个值到底是什么类型,这时就需要用到 any 类型,即任意类型。我们来看例子:
let value: any;
value = 123;
value = "abc";
value = false;
你可以看到,我们定义变量 value,指定它的类型为 any,接下来赋予任何类型的值都是可以的。
我们还可以在定义数组类型时使用 any 来指定数组中的元素类型为任意类型:
const array: any[] = [1, "a", true];
但是请注意,不要滥用 any,如果任何值都指定为 any 类型,那么 TypeScript 将失去它的意义。
所以如果类型是未知的,更安全的做法是使用unknown类型
10.void
void 和 any 相反,any 是表示任意类型,而 void 是表示没有任意类型,就是什么类型都不是,这在我们定义函数,函数没有返回值时会用到
const consoleText = (text: string): void => {
console.log(text);
}
这个函数没有返回任何的值,所以它的返回类型为 void
void 类型的变量只能赋值为 undefined 和 null,其他类型不能赋值给 void 类型的变量。
11.never
never 类型指那些永不存在的值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型
const errorFunc = (message: string): never => {
throw new Error(message);
}
这个 errorFunc 函数总是会抛出异常,所以它的返回值类型时 never,用来表明它的返回值是永不存在的。
never 类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是 never 的子类型,所以除了它自身没有任何类型可以赋值给 never 类型,any 类型也不能赋值给 never 类型
let neverVariable = (() => {
while (true) {}
})();
neverVariable = 123; // error 不能将类型"number"分配给类型"never"
上面例子我们定义了一个立即执行函数,也就是"let neverVariable = "右边的内容。右边的函数体内是一个死循环,所以这个函数调用后的返回值类型为 never,所以赋值之后 neverVariable 的类型是 never 类型,当我们给 neverVariable 赋值 123 时,就会报错,因为除它自身外任何类型都不能赋值给 never 类型。
12.Unknown
unknown类型是TypeScript在3.0版本新增的类型,它表示未知的类型,这样看来它貌似和any很像,但是还是有区别的,也就是所谓的“unknown相对于any是安全的”。怎么理解呢?我们知道当一个值我们不能确定它的类型的时候,可以指定它是any类型;但是当指定了any类型之后,这个值基本上是“废”了,你可以随意对它进行属性方法的访问,不管有的还是没有的,可以把它当做任意类型的值来使用,这往往会产生问题,如下:
let value: any
console.log(value.name)
console.log(value.toFixed())
console.log(value.length)
TS中重要的高级类型
1.交叉类型
const merge = <T, U>(arg1: T, arg2: U): T & U => {
let res = <T & U>{}; // 这里指定返回值的类型兼备T和U两个类型变量代表的类型的特点
res = Object.assign(arg1, arg2); // 这里使用Object.assign方法,返回一个合并后的对象;
// 关于该方法,请在例子下面补充中学习
return res;
};
const info1 = {
name: "lison"
};
const info2 = {
age: 18
};
const lisonInfo = merge(info1, info2);// error 类型“{ name: string; } & { age: number; }”上不存在属性“address”
Object.assign方法可以合并多个对象,将多个对象的属性添加到一个对象中并返回,有一点要注意的是,如果属性值是对象或者数组这种保存的是内存引用的引用类型,会保持这个引用,也就是如果在Object.assign返回的的对象中修改某个对象属性值,原来用来合并的对象也会受到影响。
可以看到,传入的两个参数分别是带有属性 name 和 age 的两个对象,所以它俩的交叉类型要求返回的对象既有 name 属性又有 age 属性。
2.联合类型
联合类型实际是几个类型的结合,但是和交叉类型不同,联合类型是要求只要符合联合类型中任意一种类型即可,它使用 | 符号定义。当我们的程序具有多样性,元素类型不唯一时,即使用联合类型。
const getLength = (content: string | number): number => {
if (typeof content === "string") return content.length;
else return content.toString().length;
};
console.log(getLength("abc")); // 3
console.log(getLength(123)); // 3
这里我们指定参数既可以是字符串类型也可以是数值类型,这个getLength函数的定义中,其实还涉及到一个知识点,就是类型保护,就是typeof content === “string”
总结
布尔类型:boolean
数值类型:number
字符串类型:string
数组:Array或type[]
对象类型:object
Symbol类型:symbol
null和undefined:null 和 undefined,这个比较特殊,它们自身即是类型
六个TypeScript中新增的数据类型,它们是:元组、枚举、Any、void、never和unknown
两个简单的高级类型:联合类型和交叉类型