这是我参与「第四届青训营 」笔记创作活动的第6天
1、什么是TypeScript
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
1.1 TypeScript发展史
- 2012-10:微软发布了TypeScript第一个版本(0.8)
- 2014-10:A尼古拉人发布了基于TypeScript的2.0版本
- 2015-04:微软发布了Visual Studio Code
- 2016-05:@types/react发布,TypeScript可开发React
- 2020-09:Vue发布了3.0版本,官方支持TypeScript
- 2021-11:v4.5版本发布
2、TypeScript的优点
-
代码的可读性和可维护性:举个🌰看后端某个接口返回值,一般需要去network看or去看接口文档,才知道返回数据结构,而正确用了ts后,编辑器会提醒接口返回值的类型,这点相当实用。 -
在
编译阶段就发现大部分错误,避免了很多线上bug -
增强了编辑器和 IDE 的功能,包括
代码补全、接口提示、跳转到定义、重构等
2.1 TypeScript的缺点
-
有一定的
学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念 -
会增加一些
开发成本,当然这是前期的,后期维护更简单了 -
一些JavaScript库需要
兼容,提供声明文件,像vue2,底层对ts的兼容就不是很好。 -
ts编译是需要
时间的,这就意味着项目大了以后,开发环境启动和生产环境打包的速度就成了考验
3、TypeScript的语法
3.1 基础数据类型
// 可以直接使用字面量进行声明,就好像是一个常量,声明之后的值是不可改变的
let a: 10;
a = 12;
// | 表示 或 ,也就是说 b 的值 可以是 men 也可以是 women
let b: 'man' | 'women'
b = 'menwomen'
// 既可以是字符串也可以是布尔值
let c: string | boolean
c = 'hello'
c = true
// any 表示任意类型
let d: any
d = true
// unknow 表示未知类型
let e: unknown
e = 'hello'
let s: string
// any 可以赋值给任意对象 ,这时的s就会被any盖掉
s = d
// 而使用 unknow 进行赋值就会报错
// s = e
// 而我们需要将e的值赋给s,我们需要进行类型判断
if (typeof e === "string") {
s = e
}
// 或者使用类型断言,将e强行表示为string类型
s = e as string
s = <string>e
// void 表示函数没有返回值 可以不return,但是可以return null underfind
function fn(num): void {
console.log("void")
}
// 而使用 nerve 表示没有返回值 返回为null和underfind也会报错
function fun(): never {
return null
}
// 对象
let f: Object
f = {}
// 而使用 {} 就可以对对象的属性进行限制,并且对象的属性也必须相同,而我们在对属性名后面加上一个? 表示属性可选
let g: { name: string, age?: number }
g = { name: "a" }
g = { sex: "men" }
g = { name: "b", age: 18 }
// 通过【abb:string】:any 表示我们的后续属性可以有任意属性
let h: { name: string, [abb: string]: any }
h = { name: 'yueyue', age: 18, sex: "man" }
let i: (a: number, b: number) => number
i = function (n1, n2): number {
return n1 + n2
}
// arr 数组 字符串数组
let j: string[]
j = ['a', 'b', 'c']
let k: Array<number>
k = [1, 2, 4, 5]
// 元组表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let l:[string, number]
l = ['12', 5]
// 枚举
enum sex {
men = 1,
women = 0,
}
let m: { name: string, sex: sex }
m = { name: 'yueyue', sex: sex.women }
console.log(m)
3.2 对象类型
简单理解interface 和 type 的区别:type 更强大,interface 可以进行声明合并,type 不行;
const bytedancer:IBytedancer = {
jobId:9303245,
name:'Lin',
sex:'man',
age:28,
hobby:'swimming',
}
interface IBytedancer{
/*只读属性:约束属性不可在对象初始化外赋值*/
readonly jobId: number;
name: string;
sex: 'man' | 'woman' | 'other';
age: number;
/*可选属性:定义该属性可以不存在*/
hobby?: string;
/*任意属性:约束所有对象属性都必须是该属性的子类型*/
[key: string]: any;
}
/*报错:无法匹配到"jobId",因为它是只读属性*/
bytedancer.jobId = 12345;
/*成功:任意属性标注下可以添加任意属性*/
bytedancer.platfor = 'data';
/*报错:缺少属性"name",hobby可缺省*/
const bytedancer2: IBytedancer = {
jobId: 89757,
sex: 'man',
age: 18,
}
type Hero = {
name: string,
age: number,
skill: string,
skinNum?: number,
};
3.3 函数类型
function add(x,y){
return x+y;
}
const mult = (x,y) => x*y;
interface IMult{
(x: number,y: number): number;
}
const mult: IMult = (x,y) => x*y
function add(x: number,y: number): number {
return x+y;
}
const mult: (x: number,y: number) => number = (x,y) => x*y;
3.4 函数重载
/*对getDate函数进行重载,timestamp为可缺省参数*/
function getDate(type: 'string',timestamp?: string): string;
function getDate(type: 'date',timestamp?: string): Date;
function getDate(type: 'string' | 'date',timestamp?: string): Date | string{
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString(): date;
}
const x = getDate('date');
const y = getDate('string','2018-01-10');
interface IGetDate{
(type: 'string',timestamp?: string): string;
(type: 'date',timestamp?: string): date;
(type: 'string' | 'date',timestamp?: string): Date | string;
}
/*不能将类型“(type: 'string' | 'date',timestamp?: string): Date | string;”分配给类型“IGetDate”*/
/*不能将类型“string | Date;”分配给类型“string”*/
/*不能将类型“Date”分配给类型“string”*/
const getDate2: IGetDate = (type,timestamp) => {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
}
3.5 数组类型
/*【类型 + 方括号】表示*/
type IArr1 = number[];
/*泛型表示*/
type IArr2 = Array<string | number | Record<string,number>>;
/*元祖表示*/
type IArr3 = [number,number,string,string];
/*接口表示*/
interface IArr4{
[key: number]: any;
}
const arr1:IArr1 = [1,2,3,4,5,6];
const arr2:IArr2 = [1,2,'3','4',{a: 1}];
const arr3:IArr3 = [1,2,'3','4'];
const arr4:IArr4 = ['string',() => null,{},[]];
3.6 补充类型
3.6.1 枚举类型
提高代码可维护性,统一维护某些枚举值,避免 JiShi === 1这种魔法数字。JiShi === JiShiEnum.BLUEJ这样写,老板一眼就知道我想找谁。
// 初始值默认为 0
enum JiShiEnum {
REDJ,
BLUEJ,
GREENJ,
}
// 设置初始值
enum JiShiEnum {
REDJ = 8,
BLUEJ,
GREENJ,
}
const jishi: JiShiEnum = JiShiENUM.BLUE
console.log(jishi) // 9
// 字符串枚举,每个都需要声明
enum JiShiEnum {
REDJ = "8号",
BLUEJ = "9号",
GREENJ = "10号",
}
3.6.2 泛型
// T 自定义名称
function myFun<T>(params: T[]) {
return params;
}
myFun <string> (["123", "456"]);
// 定义多个泛型
function join<T, P>(first: T, second: P) {
return `${first}${second}`;
}
join <number, string> (1, "2");
function getRepeatArr(target){
return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
//不预先指定具体类型,而在使用的时候在指定类型的一种特性
type IGetRepeatArrR = <T>(target: T) =>[T];
//泛型接口 & 多泛型
interface IX<T,U>{
key: T;
val: U;
}
//泛型类
class IMan<T>{
instance: T;
}
//泛型别名
type ITypeArr<T> = Array<T>;
//泛型约束:限制泛型必须符合字符串
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getStrArr: IGetRepeateStringArr = target => new Array(100).fill(target);
//报错:类型“number”的参数不能赋给类型“string”的参数
getStrArr(123);
//泛型参数默认类型
type IGetRepeatArr = <T = number>(target: T) => T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
//报错:类型“string”的参数不能赋给类型“number”的参数
getRepeatArr('123')
3.6.3 类型别名 & 类型断言
//通过type关键字定义了IObjArr的别名类型
type IObjArr = Array<{
key: string;
[objKey: string]: any;
}>
function keyBy<T extends IObjArr>(objArr: Array<T>){
//未指定类型时,result类型为{}
const result = objArr.reduce((res,vla,key)=>{
res[key]=val;
return res;
},{});
//通过as关键字,断言result类型为正确类型
return result as Record<string, T>;
}
3.7 高级类型
3.7.1 联合/交叉类型
- 联合类型:IA | IB,表示一个值可以是几种类型之一
- 交叉类型:IA & IB,多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
const booklist = [{
author: 'xiaoming',
type: 'history',
range: '2001-2021',
},{
author: 'xioaming',
type: 'story',
theme: 'love',
}]
type IBookList = Array<{
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
})>
3.7.2 类型保护 & 类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:
**in 关键字**
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
**typeof关键字**
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
**instanceof关键字**
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
**自定义类型保护的类型谓词**
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}