ts 是什么
TS是TypeScript的缩写,由微软开发的一种开源的编程语言
以前官网说“ts 是 js 超级”,现在改为: TypeScript是具有类型语法的JavaScript。
目前 TypeScript 5.4 已经发布(2024-03) —— ts 官网
Tip:ts缺点:开发更费麻烦,要多写东西了,看个人取舍。
环境
基于笔者博文《vue3 入门》,就像这样:
<template> <section> </section> </template> <script lang="ts" setup name="App"> // ts </script> <style> </style>
也可以直接在ts在线运行环境进行。
推导类型和显示注解类型
TS = 类型
+ javascript
ts 编译过程:
- TypeScript源码 -> TypeScript AST
类型检查器
检查AST- TypeScript AST -> JavaScript 源码
显示注解类型,语法:value:type
告诉类型检查器,这个 value 类型是 type。请看示例:
<template> <p>{{ a }}</p> <p>{{ b }}</p> <p>{{ c }}</p> </template> <script lang="ts" setup name="App"> // 显示注解类型 let a: number = 1 // a 是数字 let b: string = 'hello' // b 是字符串 let c: boolean[] = [true, false]; // 布尔类型数组 </script>
如果将 a 写成 let a: number = '3'
,vscode 中 a 就会出现红色波浪,移上去会看到提示:不能将类型“string”分配给类型“number”。
如果想让 typescript 推到类型,就去掉注解,让 ts 自动推导。就像这样:
// 推导类型 let a = 1 // a 是数字 let b = 'hello' // b 是字符串 let c = [true, false]; // 布尔类型数组
去掉注解后,类型并没有变。并且如果尝试修改 a 的类型,ts 也会报错。就像这样:
let a = 1 // a 是数字 // 尝试替换成字符串,vscode 会提示:不能将类型“boolean”分配给类型“number”。 a = true
Tip:有人说“最好让 ts 推导类型,少数情况才需要显示注解类型”。
另外虽然大量错误 ts 在编译时无法捕获,例如堆栈溢出、网络断连,这些属于运行时异常。ts 能做的是将 js 运行时的报错提到编译时。比如以下代码:
const obj = { width: 10, height: 15 }; // 提示 heigth 属性写错了 const area = obj.width * obj.heigth; let a = 1 + 2 let b = a + 3 // 鼠标以上 c,可以看到 c 对应的类型 let c = { apple: a, banana: b }
类型断言
请看示例:
let arr = [1, 2, 3] // r 为3 const r = arr.find(item => item > 2) // “r”可能为“未定义”。ts(18048) // const r: number | undefined r * 5 // {1}
行 {1} 处的 r 会错误提示,说 r 可能会是 undefined。
要解决这个问题,可以使用类型断言
:用来告诉编译器一个值的具体类型,当开发者比编译器更了解某个值的具体类型时,可以使用类型断言来告诉编译器应该将该值视为特定的类型。
类型断言有两种形式,分别是尖括号语法
和 as 语法
。这里说一下 as 语法:value as type
。请看示例:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
上述示例改成这样,r 就不会报错了。
// 告诉编译器,r 一定是一个 number const r = arr.find(item => item > 2) as number r * 5
Tip:由于需要人为干预,所以使用起来要谨慎
基础类型
js 基本类型大概有这些:
let num1 = 10; let str1 = 'Hello'; let isTrue = true; let undefinedVar; let nullVar = null; const symbol1 = Symbol('description'); const bigIntNum = 9007199254740991n; let notANumber = NaN; let infinite = Infinity; console.log(typeof num1); // number console.log(typeof str1,); // string console.log(typeof isTrue); // boolean console.log(typeof undefinedVar); // undefined console.log(typeof nullVar); // object console.log(typeof symbol1); // symbol console.log(typeof bigIntNum); // bigint console.log(typeof notANumber); // number console.log(typeof infinite); // number
ts 中基本类型有:
// let v1: String = 'a' - 大写 String 也可以 let v1: string = 'a' let v2: number = 1 let v3: boolean = true let v4: null = null let v5: undefined = undefined // 字符串或者null let v6: string | null = null // 错误:不能将类型“5”分配给类型“1 | 2 | 3” let v7: 1 | 2 | 3 = 5 // 正确 let v8: 1 | 2 | 3 = 2
联合类型
数组
ts 数组有两种方法,看个人喜好即可。请看示例:
// 方式一 // 定义一个由数字组成的数组 let arr1: number[] = [2, 3, 4] // 报错:不能将类型“string”分配给类型“number” let arr2: number[] = [2, 3, 4, ''] // 方式二 let arr3: Array<string> = ['a', 'b', 'c'] // 报错:不能将类型“number”分配给类型“string”。 let arr4: Array<string> = ['a', 'b', 'c', 4]
元组
在 TypeScript 中,元组(Tuple)是一种特殊的数组类型,它允许您指定一个固定长度和对应类型的数组
let arr5:[string, number, string] = ['a', 1, 'b'] // 报错:不能将类型“[string, number]”分配给类型“[string, number, string]”。源具有 2 个元素,但目标需要 3 个。 let arr6:[string, number, string] = ['a', 1] // 正确 arr6[0] = 'a2' // 错误:不能将类型“number”分配给类型“string”。 arr6[0] = 1 // 第三个添加 ? 表明可选,这样只传入 2 个数也不会报错 let arr7:[string, number, string?] = ['a', 1, 'b']
枚举
枚举需要使用关键字 enum。请看示例:
// 就像定义对象,不过不需要 = enum TestEnum { a, b, c, } // 1 console.log(TestEnum.b); // b console.log(TestEnum[1]); // string console.log(typeof TestEnum[1]);
ts 可以自动为枚举类型中的各成员推导对应数字。上面示例推导结果:
enum TestEnum { a = 0, b = 1, c = 2, }
也可以自己手动设置:
enum TestEnum2 { a = 3, b = 13, c = 23, } // 13 console.log(TestEnum2.b);
比如这个,c 就是 b 的下一个数字:
enum TestEnum3 { a, b = 13, c, } // 14 console.log(TestEnum3.c);
使用场景
:比如你之前根据订单状态写了如下代码,可以用枚举来增加可读性。
if(obj.state === 0){ }else if(obj.state === 1){ }else if(obj.state === 2){ }else if(obj.state === 3){ }
// 优化后 enum 订单状态{ 取消, 上线, 发送, 退回, ... } if(obj.state === 订单状态.取消){ }else if(obj.state === 订单状态.上线){ }else if(obj.state === 订单状态.发送){ }else if(obj.state === 订单状态.退回){ }
函数
定义一个函数,参数报错:
// 参数 a 和 b报错。例如:a - 参数“a”隐式具有“any”类型。 function fn1(a, b){ return a + b }
定义参数类型:
function fn2(a: number, b : number){ return a + b }
定义参数 b 可选,返回值是 number类型。请看示例:
// b是可选。 // 必选的放左侧,可选的放后侧 function fn5(a: number, b?: number): number{ return 10 } // 应有 1-2 个参数,但获得 0 个。 fn5()
定义参数 a 的默认值,rest是一个字符串数组:
// a 有一个默认值 10 function fn7(a = 10, b?: number, ...rest:string[]): number{ return 10 } fn7(1,2, 'a', 'b')
void
通常用于函数,表示没有 return 的函数。
function fn3(a: number, b : number):void{ // 不能将类型“number”分配给类型“void”。 return a + b } function fn4(a: number, b : number): void{ }
接口
通常用于对象的定义。请看示例:
interface Person{ name: string, age: number } const p: Person = { name: 'peng', age: 18 } // 报错:类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。ts(2741) const p2: Person = { name: 'peng', }
类型别名
比如定义了一个变量 v1,其类型可以是 number 或 string,但是好多地方都是这个类型:
let v1: number | string = 3
我们可以通过 type
定义一个别名
。就像这样:
// 定义别名 Message type Message = number | string let v2: Message = 'hello' // 报错:不能将类型“boolean”分配给类型“Message” let v3: Message = true
泛型
比如定义如下一个处理 number 的函数:
function fn1(a: number, b:number): number[]{ return [a, b] }
假如以后想把这个函数作为一个通用函数,除了可以处理 number,还可以处理 string 等其他类型,比如:
function fn1(a: string, b:string): string[]{ return [a, b] }
a: string | number
又交叉了。就像这样:
function fn1(a: string | number, b:string | number): string[]{ return [a, b] }
这里可以使用泛型
,请看示例:
// 定义一个变量,比如 T function fn1<T>(a: T, b:T): T[]{ return [a, b] } fn1<number>(11, 11) fn1<string>('a', 'a') // 正确,ts 会自动推导 fn1('a', 'a')
再看一个泛型示例:
// 参数 arr 是 T 类型的数组 // 返回 T 类型或 undefined function firstElement<T>(arr: T[]): T | undefined { return arr[0]; } firstElement(['a', 'b'])
函数重载
java 中函数重载是定义多个方法,调用时根据参数类型
和数量
的不同执行不同的方法。例如下面定义两个 add:
// 方法重载示例:两个参数的相加 public int add(int a, int b) { return a + b; } // 方法重载示例:三个参数的相加 public int add(int a, int b, int c) { return a + b + c; }
ts 这里重载和 java 中的有些不同,可以称之为函数重载申明
。
比如首先我们写了一个数字相加
或字符串相加
的方法:
// 数字相加 // 字符串相加 function combine(x: number | string, y: number | string): number | string { if (typeof x === 'number' && typeof y === 'number') { return x + y; } else if (typeof x === 'string' && typeof y === 'string') { return x + y; } // 处理其他情况 return 'Invalid input'; } console.log(combine(1, 2)); // 输出:3 console.log(combine('hello', 'world')); // 输出:helloworld
这里有两个问题:
// 问题一:鼠标移动到 combine 显示: // function combine(x: number | string, y: number | string): number | string console.log(combine(1, 2)); console.log(combine('hello', 'world')); // 问题二:传入 number和 string 不合法,但不报错。鼠标移动到 combine 显示: // function combine(x: number | string, y: number | string): number | string console.log(combine(1, 'two')); // 输出:Invalid input
现在加上函数重载申明
,就能解决上述两个问题。请看示例:
// 函数重载 function combine(x: number, y: number): number; // 变量名可以不是x、y function combine(x2: string, y2: string): string; function combine(x: number | string, y: number | string): number | string { // 不变 } // function combine(x: number, y: number): number (+1 overload) console.log(combine(1, 2)); // function combine(x: string, y: string): string (+1 overload) console.log(combine('hello', 'world')); // 报错:没有与此调用匹配的重载。 // 第 1 个重载(共 2 个),“(x: number, y: number): number”,出现以下错误。 // 第 2 个重载(共 2 个),“(x: string, y: string): string”,出现以下错误。ts(2769) console.log(combine(1, 'two'))
接口继承
直接看示例:
interface Person{ name: string, age: number } // Student 继承 Person interface Student extends Person{ school: string } // 提示p缺少3个属性 // 类型“{}”缺少类型“Student”中的以下属性: school, name, agets(2739) const p: Student = { }
Student 继承 Person,有了3个属性。
类的修饰符
类的修饰符有:public、private、protected、static、readonly...。用法请看下文:
比如有这样一段正常的js代码:
class People{ constructor(name){ this.name =name; } // 不需要逗号 sayName(){ console.log(this.name) } } let people = new People('aaron') people.sayName() // aaron
放在 ts(比如 ts在线运行环境) 中会报错如下:
Parameter 'name' implicitly has an 'any' type. Property 'name' does not exist on type 'People'. Property 'name' does not exist on type 'People'.
需要修改如下两处即可消除所有错误:
class People{ - constructor(name){ + // 消除ts报错:类型“People”上不存在属性“name” + name: string + constructor(name: string){ this.name =name; } // 不需要逗号
其中 name: string
的作用:声明 People 类有个必填属性。实例化 People 类的时候,必须传入一个 string 类型的 name 属性。
接着加一个可选属性 age:
// 通过?将 age 改成可选。解决:属性“age”没有初始化表达式,且未在构造函数中明确赋值。 age?: number
可以设置默认值:
// 根据默认值推断类型,而且是必选属性 money = 100
Tip:稍后我们会看到对应的 js 是什么样子。
属性默认是 public
,自身可以用,继承的子类中也可以使用。public 还可以这么写,效果和上例等价:
constructor(name){ - name: string - constructor(name: string){ + constructor(public name: string){ this.name =name; }
另外还有 private
表明只能在类中使用。protected
只能在类和子类中使用。请看示例:
class People{ ... // 属性默认是 public,自身可以用、继承也能用 public money2 = 200 private money3 = 300 protected money4 = 400 constructor(name: string){ this.name =name; } sayName(){ console.log(this.name) } } let people = new People('aaron') console.log(people.money); // 属性“money3”为私有属性,只能在类“People”中访问。ts(2341) console.log('people.money3: ', people.money3); // 300 // 属性“money4”受保护,只能在类“People”及其子类中访问。ts(2445) console.log('people.money4: ', people.money4); // 400
注
:虽然 vscode 报错,但浏览器控制台还是输出了。或许 ts 只是静态编译,对应的js 没有做特殊处理
,比如 private 声明 money4,实际上并没有实现。请看ts在线运行环境 ts 对应的 js:
// ts class People{ name: string age?: number money = 100 public money2 = 200 private money3 = 300 protected money4 = 400 constructor(name: string){ this.name =name; } sayName(){ console.log(this.name) } } let people = new People('aaron') console.log('people.money3: ', people.money3); console.log('people.money4: ', people.money4);
// 对应的js "use strict"; class People { constructor(name) { this.money = 100; this.money2 = 200; this.money3 = 300; this.money4 = 400; this.name = name; } sayName() { console.log(this.name); } } let people = new People('aaron'); console.log('people.money3: ', people.money3); console.log('people.money4: ', people.money4);
js 中静态属性
使用如下:
protected money4 = 400 // 静态属性 + static flag = 110 console.log('People.flag: ', People.flag);
例如将静态属性设置成私有,只能在类中使用。请看示例:
// 静态属性 private static flag = 110 // 报错:属性“flag”为私有属性,只能在类“People”中访问。 console.log('People.flag: ', People.flag);
多个修饰符
可以一起使用,但有时候需要注意顺序,vscode 也会给出提示。就像这样:
// “static”修饰符必须位于“readonly”修饰符之前。ts(1029) readonly static flag = 110
比如定义一个静态只读属性:
static readonly flag2 = 110 // 报错:无法为“flag2”赋值,因为它是只读属性。ts(2540) People.flag2 = 111
类的存取器
感觉就是 js 的 get 和 set。比如下面就是一个 js 的get、set示例:
class People { constructor(name) { this.name = name; } get name() { return 'apple'; } set name(v) { console.log('set', v); } } let people = new People('aaron') // set aaron people.name = 'jia' // set jia console.log(people.name); // apple
对应 ts 中的存取器就是这样:
class People{ constructor(name: string){ this.name = name; } get name(){ return 'apple' } set name(v){ console.log('set', v) } } let people = new People('aaron') // set aaron people.name = 'jia' // set jia console.log(people.name); // apple
注:这个例子很可能会栈溢出,就像这样:
class People{ constructor(name: string){ this.name = name; } get name(){ return 'apple' } set name(v){ console.log('v: ', v); // 栈溢出 // 报错:VM47:10 Uncaught RangeError: Maximum call stack size exceeded this.name = v } } let people = new People('aaron')
所以可以这么写:
class People { private _name: string = '' get name(): string{ return 'peng' } set name(val: string){ this._name = val } } let people = new People() people.name // 报错:属性“_name”为私有属性,只能在类“People”中访问。ts(2341) people._name
不写类型,ts 也会自动推导,比如去除类型后也可以。就像这样:
// 自动推导类型 class People { private _name = 'peng' get name(){ return 'peng' } set name(val){ this._name = val } } let people = new People()
抽象类
抽象类(abstract),不允许被实例化,抽象属性和抽象方法必须被子类实现
。更像一个规范。请看示例
abstract class People { // 可以有抽象属性和方法 abstract name: string abstract eat(): void // 也可以有普通属性和方法 say() { console.log('hello: ' + this.name) } } // 如果不实现 name 和 eat 方法则报错 class Student extends People{ name: string = '学生' // 既然没报错 - 抽象类中返回是 void,这里返回string eat(){ return 'eat apple' } } const s1 = new Student() s1.say() console.log(s1.eat()); // eat apple
抽象类定义了一个抽象属性、一个抽象方法,一个具体方法
。子类必须实现抽象属性和抽象方法,子类实例可以直接访问抽象类中具体的方法。请看对应的 js 代码,你就能很明白。
class People { say() { console.log('hello: ' + this.name); } } class Student extends People { constructor() { super(...arguments); this.name = '学生'; } eat() { return 'eat apple'; } } const s1 = new Student(); s1.say(); console.log(s1.eat());
类实现接口
前面我们用接口定义了一个类型:
interface Person{ name: string, age: number } const p: Person = { name: 'peng', age: 18 }
抽象类如果只写抽象方法和属性,那么就和接口很相同了。另外接口用 interface
关键字定义,子类可以实现 implements
(注意这个单词是复数
) 多个接口(不能同时继承多个)。请看示例:
interface People { name: string eat(): void } interface A{ age: number } // 实现两个接口,所有属性和方法都需要实现 class Student implements People, A{ name: string = '学生' age = 100 // 既然没报错 eat(){ return 'eat apple' } } const s1 = new Student() console.log(s1.eat()); // eat apple
泛型类
使用类时,除了可以使用接口来规范行为,还可以将类和泛型结合,称为泛型类
。
比如现在 deal 是处理 string 的方法:
class People { value: string; constructor(value: string) { this.value = value; } deal(): string { return this.value; } } const p1 = new People('peng') p1.deal()
后面我需要 deal 又能处理 number,这样就可以使用泛型。就像这样:
class People<T> { value: T; constructor(value: T) { this.value = value; } deal(): T { return this.value; } } const p1 = new People('peng') p1.deal() const p2 = new People(18) p2.deal()
多个泛型写法如下:
class Pair<T, U> { private first: T; private second: U; constructor(first: T, second: U) { this.first = first; this.second = second; } public getFirst(): T { return this.first; } public getSecond(): U { return this.second; } } // 使用带有多个泛型类型参数的泛型类 let pair1 = new Pair<number, string>(1, "apple"); console.log(pair1.getFirst()); // 1 console.log(pair1.getSecond()); // apple let pair2 = new Pair<string, boolean>("banana", true); console.log(pair2.getFirst()); // banana console.log(pair2.getSecond()); // true
其他
Error Lens
:提供了一种更直观的方式来展示代码中的问题,如错误、警告和建议,以帮助开发者更快速地识别和解决问题。
vscode 直接安装后,会将红色错误提示直接显示出来,无需将鼠标移到红色波浪线才能看到错误提示。
标签:Typescript,console,入门,number,let,log,string,name From: https://www.cnblogs.com/lgx5/p/18104401