类型与类型推断
当我们初学ts的时候,我们可能会写出这样的程序
let price: number = 123_456_789;//当数字太长,你可以使用下划线来分割以获取更好的阅读性
let item: string = "TypeSript";
let isNeed: boolean = true;
但实际上,ts编译器可以推断或者检测变量的类型
例如,我们已经将price
初始化为一个数字,那么ts编译器将能够检测到这是一个numebr类型,这时候我们声明类型就是多余的了。
当我们声明了却没有初始化变量时,ts会将其推断为any类型(any类型表示所有类型
,是所有类型的父类型,在ts中请尽量少的使用它)
这个时候我们可以给title
赋不同类型的值,这并不是我们想看到的,所以我们应该在声明没有初始化的变量时,定义变量的类型
在我们声明函数时,我们可能不知道属性的类型具体是什么,这时候ts会给我们这个提示
这时候我们有两种选择:
1.将属性的类型显式声明为any。
2.ctrl+P
搜索tsconfig.json文件,并找到"noImplicitAny": true,
将其去掉注释并改为false
(不建议,除非有大量的错误,但那样你可能会失去使用ts的意义)
Array
同样的,如果我们声明一个空数组
const arr = [];//它的类型为any[]
由前面的经验,我们明白,我们应该给arr声明类型
const arr: number[] = [];//类型为number[]
ts有一个特性:在使用某个确定类型的变量时,你能够得到相应类型方法的代码补全提示。
这也是我们为什么使用ts的一个重要原因
Tuples/元组
ts有一个名为Tuples/元组的新类型,这是一个拥有固定长度的数组,并且每个元素都有一个特定的类型。我们经常会在处理一对值时使用它。
let user: [number, string] = [20, "kevin"];//类型为[number, string] 注意,这里的类型与元素的位置是对应的
//let user: [number, string] = ["kevin", 20]; //错误:不能将类型“string”分配给类型“number”。
同时,当我们声明第三个元素时,将会报错,因为元组的元素长度是固定的。
let user: [number, string] = [20, "kevin", 0];
//错误:不能将类型“[number, string, number]”分配给类型“[number, string]”。源具有 3 个元素,但目标仅允许 2 个。ts(2322)
关于元组,你应该知道的是:在内部,使用的是普通的js数组,如果我们编译代码,那么得到的只是一个普通的js数组:
"use strict";
let user = [20, "kevin"];
//# sourceMappingURL=demo.js.map
关于元组,有一个需要注意的是,虽然元组是固定长度的数组,但你仍然可以使用push添加元素。这也许是一个漏洞,希望将来会得到解决。
let user: [number, string] = [20, "kevin"];
user.push("demo");//只要类型为 number|string 并不会报错
关于元组的最佳实践:当数组只有两个值的时候可以使用(如键值对),但当你有更多的元素,使用元组可能让你的代码的阅读性更差。
Enum/枚举
枚举与数组类似,在以前我们定义三个常量可能会这样做:
const small = 1;
const medium = 2;
const large = 3;
不过在ts中,我们可以使用枚举:
enum SizeList {
Small,//默认为0
Medium,//后面的值依次增长
Large,
}
当然,你可以显式的设置值
enum SizeList {
Small = 1,//定义为1
Medium,//值为2,以此类推
Large,
}
或者将值声明为其他类型,不过这时候,你需要声明每个常量的值:
enum SizeList {
Small = "s",
Medium = "m",
Large = "l",
}
使用枚举:
enum SizeList {
Small = 1,
Medium,
Large,
}
let mySize: SizeList = SizeList.Medium;
console.log(mySize);//2
我们看到编译后的js文件,代码似乎有些长:
"use strict";
var SizeList;
(function (SizeList) {
SizeList[SizeList["Small"] = 1] = "Small";
SizeList[SizeList["Medium"] = 2] = "Medium";
SizeList[SizeList["Large"] = 3] = "Large";
})(SizeList || (SizeList = {}));
let mySize = SizeList.Medium;
console.log(mySize);
//# sourceMappingURL=demo.js.map
我们可以通过一个小技巧,让ts编译器生成更加优化的代码:在enum前添加const
const enum SizeList {
Small = 1,
Medium,
Large,
}
let mySize: SizeList = SizeList.Medium;
console.log(mySize);
下面是编译后的js文件:
"use strict";
let mySize = 2;
console.log(mySize);
//# sourceMappingURL=demo.js.map
总结:我们可以通过枚举表示相关常量的列表,如果我们const声明enum,编译器将生成更加优化的代码。
function
在声明函数时,根据最佳实践,我们应该对函数的属性和返回值的类型进行声明:
function taxation(income: number) {}//它的类型为function taxation(income: number): void
//我们应该这样做:
function taxation(income: number): number {
return 0;
}//它的类型为function taxation(income: number): number
对于上面的函数,我们并没有在函数中使用到传入的参数,我们希望ts能够给我们警告,我们可以在tsconfig.json中配置它:
找到"noUnusedParameters": true,
并打开它
这时候我们就得到一个警告了
我们写这个函数:
function taxation(income: number): number {
if (income < 50_000) return income * 1.2;
}//函数缺少结束 return 语句,返回类型不包括 "undefined"。
出现这个报错的原因是,只有在符合条件时才会返回income*1.2
,而不符合则返回undefined
(在js中,未赋值的变量都为undefined),而undefined不为number类型。
我们可以去除函数的返回值类型
function taxation(income: number) {
if (income < 50_000) return income * 1.2;
}//function taxation(income: number): number | undefined
这样不会报错,但这样并不符合ts的使用标准,我们不希望会返回undefined,我们希望我们这样做的时候ts对我们进行警告
我们可以在tsconfig.json中找到"noImplicitReturns": true,
并打开它。
这样,你就得到想要的警告了。
对于上面的函数,我们应该这样写:
function taxation(income: number): number {
if (income < 50_000) return income * 1.2;
return income * 1.3;
}
在函数中,我们可能声明了变量/常量却没有使用,我们同样希望得到提示:
function taxation(income: number): number {
let a = 1;
if (income < 50_000) return income * 1.2;
return income * 1.3;
}
我们可以在tsconfig.json中找到"noUnusedLocals": true,
并打开它。
这时候我们将得到一个提示:已声明“a”,但从未读取其值。ts(6133)
现在我们的函数有两个参数,但我们在调用的时候传入了三个,这时候ts会给我们报错,而在js中并不会报错
function taxation(income: number, taxYear: number): number {
if (taxYear < 2022) return income * 1.2;
return income * 1.3;
}
taxation(300_000, 2023, 1);//报错:应有 2 个参数,但获得 3 个。ts(2554)
我们可能会想让taxYear
属性为可选属性,我们可以在冒号前使用?
来声明可选属性。
但通常情况下,ts会给我们报错:
function taxation(income: number, taxYear?: number): number {
if (taxYear < 2022) return income * 1.2;
return income * 1.3;
}//对象可能为“未定义”。ts(2532)
因为如果我们没有传入taxYear,那么taxYear在函数中就为undefined,但undefined转换为数字后为NAN。并不能进行比较
对此,我们有两个解决方法:
1.旧版写法(不推荐):
function taxation(income: number, taxYear?: number): number {
if ((taxYear || 2022) < 2022) return income * 1.2;
return income * 1.3;
}
2.es6写法(推荐):
function taxation(income: number, taxYear = 2023): number {
if (taxYear < 2022) return income * 1.2;
return income * 1.3;
}
taxation(300_000, 2024);//taxYear的默认值为2023,调用时传入2024,将会覆盖掉默认值
总结:作为最佳实践,我们应该始终正确的注释函数,参数类型,返回值类型。并且启用tsconfig.json中的
"noUnusedParameters": true,
(没有未使用的参数),"noUnusedLocals": true,
(没有未使用的变量),"noImplicitReturns": true,
(没有隐式返回)
Object
当我们像js那样使用对象时,ts会给我们报错
let man = { age: 22 };
man.name = "kevin";//报错:类型“{ age: number; }”上不存在属性“name”。ts(2339)
在ts中使用对象时,我们也应该像function那样,明确的声明对象的类型:
let man:{
name:string,
age:number
} = { age: 22 };//类型 "{ age: number; }" 中缺少属性 "name",但类型 "{ name: string; age: number; }" 中需要该属性。ts(2741)
man.name = "kevin";
这时候同样会报错,因为我们在声明对象时没有对对象的属性进行声明。也就是没有声明name属性。
我们可以使用ts的特性,让其属性为可选属性
let man: {
name?: string;
age: number;
} = { age: 22 };
man.name = "kevin";
但是,我们不应该这样使用。
我们不能盲目地使用ts的特性。我们在写代码的同时,也应该考虑代码的合理性,毕竟没有一个man的name为undefined
所以,我们还是应该这样使用:
let man: {
name: string;
age: number;
} = { age: 22, name: "kevin" };
只读属性
在声明对象时,我们可能希望某些属性不被更改。对此,我们可以使用readonly
关键字,它表示(只读的)
let man: {
readonly name: string;
age: number;
} = { age: 22, name: "kevin" };
man.name = "qian";//无法分配到 "name" ,因为它是只读属性。ts(2540)
对象的方法
在声明方法时,我们同样的要给函数声明函数签名(也叫类型签名,或方法签名,定义了函数或方法的输入与输出)
let man: {
readonly name: string;
age: number;
retire: (date: Date) => void;//函数签名
} = {
age: 22,
name: "kevin",
retire: (date: Date) => {
console.log(date);
},
};