TypeScript学习笔记
TypeScript概述和环境
TypeScript(简称:TS)是JavaScript的超集
(JS有的TS都有)。
TypeScript = Type + JavaScript(为JS添加了类型系统
)
安装解析TS的工具包typescript
安装步骤:
-
打开VSCode终端。
-
输入安装命令:npm i -g typescript 敲回车,来安装(注意:需要联网)。
typescript
:就是用来解析TS的工具包。提供了tsc命令,实现了TS —> JS的转化。
npm
:用来安装前端开发中用到的包,是安装Node.js时自动安装的。
i
(install):表示安装。
-g
(–global):全局标识,可以在任意目录中使用该工具。
- 安装查看: tsc -v / tsc --version
安装简化执行TS的工具包 ts-node
- 这个工具包内部会先用tsc命令, 把TS文件转为JS文件
- 内部再使用命令ts-node,用来执行生成的JS代码。
安装命令:npm i -g ts-node
使用方式:ts-node hello.ts
- 后期在脚手架中使用ts就不会这么麻烦, 初学语法暂时使用这个方式
入门代码
创建 hello.ts 文件,console.log(“hello TS”)
在终端进行执行:tsc hello.ts
会生成一个 hello.js 文件
TS中的注释
// 单行
/* 多行 */
JS原有类型
- 限定了数据类型后,后续的赋值必须符合属性类型,否则编译报错!
- 基础类型如果首字母大写, 赋值的时候可以采用实例化的形式
number
数值类型- number(整数和浮点数)
除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
string
- string 单引号(推荐)或双引号或``都可以
let name: string = "bob";
name = "smith";
boolean
- 布尔值 boolean true/false值
let isDone: boolean = false;
undefined
- 表示此处应该有值, 但是现在没有值
- 申明了一个变量, 但是未赋值, 这个变量就是undefined
- 在TS中, 可以给已有类型的基本数据类型变量, 重新赋值undefined
let u:undefined = undefined
null
- 表示此处不应该有值, 没有值了
- 在TS中, 可以给已有类型的引用数据类型变量, 重新赋值null
let n: null = null
object
对象类型基本使用
-
js中属于对象的太多,添加object后等于是没有添加类型限制,开发很少用
-
示例1:
// {}也表示对象,表示变量o必须是一个对象类型,在其中可以指定属性类型 // 以下示例表示对象o中只能有指定的属性名称和类型,以及个数 // 在属性名后面加?,表示该属性可选 let o: { name: string, age: number, sex?: string }; o = {name: '孙悟空',age: 18} // ok o = {name: '孙悟空'} // 报错,少了一个age属性 o = {name: '孙悟空',age: 18,sex: '男'} // ok
示例2:
// 需求:在一个对象中,我只有一个属性是有要求的,其他的属性名称、类型、个数都未知 let o: { name: string, [propertyName: string]: any } /* * 以上代码,[propertyName: string] 表示属性名是string(对象的属性名都是字符串类型) * [propertyName: string]: any 表示该对象中的属性是任意类型, * 如果any是string,表示该对象中的属性都必须是string类型 */
实例3:
function getObj(obj: object):object { // do something return { name:"卡卡西" } } console.log(getObj({name:"佐助"})) console.log(getObj(new String('字符串')))
对象类型中的函数写法
指定对象中, 函数的类型
-
写法一:
sayHi(name:string):void
const user: { name:string sayHi(name:string):void } user = { name:'张三' sayHi(name) { console.log('你好,我叫' + name) } }
-
写法二:
add(n1:number, n2:number) => number
const user: { name:string add(n1:number, n2:number) => number } user = { name:'李四' add: function(a, b){ return a+b } }
array
-
数组; 开发中, 数组中的元素为同一类型
-
语法1:
类型[]
// 表示数值数组 let list: number[]; let list: Array<number>; let list: number[] = [1, 2, 3];
-
语法2:使用泛型写法,
Array<元素类型>
:// 表示字符串数组 let list: string[]; let list: Array<string> let list: Array<number> = ['a', 'b', 'c'];
扩展: 申明二维数组
let arr:number[][] = [ [1,2,3],[7,8,9] ]
function
函数类型的定义
- 函数主要是限制出入参的类型及个数, 并无固定的针对性语法, 实现方式多种
-
单独限制出入参类型
- 箭头函数的写法常用
// 箭头函数 let addFunc = (num1:number, num2:number):number => { return num1 + num2 } addFunc = function(n1, n2): number{ return n1 + n2; } console.log(addFunc(2, 6)) // 8 *************************************************** // 函数声明 function add(num1:number, num2:number):number { return num1 + num2 } console.log(add(2,2)) // 4
-
使用类型别名
type AddFun = (num1:number, num2:number):number const add:AddFun = (num1, num2) => { return num1 + num2 }
-
注意: 只适合函数表达式
// 函数表达式 let fun1 = function(){} // 函数声明 function fun2(){}
-
-
使用接口
- 为了使用接口表示函数类型,需要给接口定义一个调用签名
interface ISearchFunc { // 定义调用签名 (source:string, subString:string): boolean } // 定义一个函数,该类型即为上面的接口 const searchString: ISearchFunc = function (source:string, subString:string): boolean { return source.search(subString) > -1 } // 调用 console.log(searchString('道阻且长,行则将至','长')) // true
扩展 - 函数的参数
默认值
function(name: string, age: number = 18){
}
可选参数
- 添加
?
,接口也适用(表示属性可省略) - 在入参变量名后面添加
?
, 注意可选参数必须放在最后, 可以是一个或多个可选参数 - 函数体中对可选参数需使用非空校验相关逻辑
// 表示入参age, sex,可省略
function(name: string, age?: number, sex?: string){
}
TS新增类型
any
-
任意类型(any可略) , 尽量不要使用any
-
设置为该类型后等于对变量关闭了TS的类型检测, 直接让它们通过编译阶段的检查
-
可以赋值给任意类型变量
-
// eg1:多次赋值均不报错 let a: any = 123 a = "str"a = true // eg1:它可以赋值给其他任意类型,使其他类型也称为any类型 // 简单理解就是b是any类型,经过下面赋值,使a也成了any类型 let b:number = 123 b = a
unknown
- 未知类型的值; 编码中,尽量使用unknown代替any
-
和any的异同点
- 同:都是可以多次赋值不同类型的值
- 异:any可以赋值给任意任意类型的值,使其也称为any的值,但是unknown不能直接赋值其他类型的值
-
一般来说,这个类型并不是开发者手写的,是网络传来的,需要配和断言使用(在使用的时候需要明确这个变量的类型,可以多次指定类型)
type A = {name:string} type B = {age:number} // 模拟ajax传递过来的数据 let c: unknown = JSON.parse("{'name':"Tom"}") let var1 = (c as A).name let var2 = (c as B).age
void
- 空值 , 用于函数返回值 表示函数没有返回值(常用)
- 实际编码中,其实可以return null或者return undefined,但是没有意义,应该是语法上的兼容而已
function fn(): void{
// do something
return; // 或者不写return
}
never
- 永不存在的值的类型
- 开发中使用较少,一般用于抛出异常、无限循环的函数返回类型
- 出现该类型的时候,注意检查代码是否有问题
语法:
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
eg:
type Code = 1|2|3|undefined
let dir:Code // 表示dir的取值只能是”1,2,3,undefined“ 四者之一
switch(dir){
case 1:
break;
case 2:
break;
case 3:
break;
case undefined:
break;
default:
console.log('如果进入该分支,表示dir的值不在”1,2,3,undefined“中, 即为never类型')}
元组
-
固定长度的数组(元素类型可以不一致)
-
结合数组理解, 最大的区别的是元组的长度固定, 且每个索引对应的元素类型固定
-
对于值, 可以get, set, update, 注意变更的值只能是规定的类型, update可以用arr[0] = newValue, set可以用arr.push(newValue)
// 表示数组中只能有2个string的元素,长度不可变 let arr1: [string, string] let arr2: [number, string]
enum
- 枚举; 默认枚举值有编号,类似数组索引,从0开始
eg1:
enum Gender {
Male,
Female
}
let tom: Gender = Gender.Male
console.log(tom) // 0
console.log(Gender.Male, Gender.Female) // 0 1
- 特殊使用方式:
如果枚举值是数字值, 可以用以下写法
enum Color {
Red = 6,
Green = 7,
Blue = 8
}
let c:string = Color[6] // Red
联合类型
- 把多个类型联合为一个类型 (表示取值可以为多种类型中的一种)
- 用
|
隔开
// 表示入参param可以是number或者string类型
// 出参为string类型
function getSomeThing(param: number|string):string {
return param+''
}
getSomeThing(123)
getSomeThing("字符串")
type MyType = 1 | 2 | 3 | 4 | 5;
let a: MyType = 2;
let b: MyType = 4;
// 数组中,既可以有字符串,也可以有数字
let arr:(number|string)[]
arr = [123, '哈哈']a
rr = [66, 88, 99]
typeof 操作符
用来获取一个变量或对象的类型
interface Person {
name: string;
age: number;
}
const test: Person = { name: "Tom", age: 20 };
type Sys = typeof test; // type Sys = Person
在上面代码中,我们通过 typeof 操作符获取 test 变量的类型并赋值给 Sys 类型变量,之后我们就可以使用 Sem 类型:
const Vivo: Sys = { name: "Lili", age: 3 }
也可以对嵌套对象执行相同的操作:
const kakaxi = {
name: "kakaxi",
age: 27,
address: {
province: '湖南',
city: '长沙'
}
}
type Kakaxi = typeof kakaxi;
/* 以下即为Kakaxi类型
type Kakaxi = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
此外, typeof 操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型,比如
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
类型断言
断言语法
同java中的类型转换
- 关于断言是用来跳过编译检查的语法
主要用于当 TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型。
只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
有两种语法,关键字
as
和标签<>
两种,
- <类型>值
- 值 as 类型
由于
<>
会与JSX
语法冲突,建议统一使用as
来进行类型断言。
as
语法(推荐):
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
<>
语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
断言扩展
非空断言 !
!
表示此处一定有值, 跳过类型检查
如果编译器不能够去除 null 或 undefined,可以使用非空断言 !
手动去除。
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // name 被断言为非空
}
name = name || "Bob"
return postfix("great")
}
代码解释:
第 2 行,postfix()
是一个嵌套函数,因为编译器无法去除嵌套函数的 null (除非是立即调用的函数表达式),所以 TypeScript 推断第 3 行的 name
可能为空。
第 5 行,而 name = name || "Bob"
这行代码已经明确了 name
不为空,所以可以直接给 name 断言为非空(第 3 行)。
双重断言
双重断言极少有应用场景,只需要知道有这种操作即可:
interface User {
nickname: string,
admin: boolean,
group: number[]
}
const user = 'Evan' as any as User
代码解释: 最后一行,使用 as 关键字进行了两次断言,最终变量 user 被强制转化为 User 类型。
扩展 - 类型的其他用法
类型别名
- 为任意类型取别名, 语法:
type 别名 = 类型
- 使用场景: 当同一类型(复杂)被多次使用时, 可以通过类型别名, 简化对该类型的编码
- 需要使用关键字
type
, 别名命名, 建议首字母大写
type CustomArray = (number|string)[]
let arr1:CustomArray = [1, '啊', 6]
let arr2:CustomArray = ['x', 'y', 8]
一个变量指定多个类型
// 表示a可以是string或number类型
let a: string | number;
扩展&使用
let j:{name: string} & {age: number}
j = {
name: '卡卡西',
age: 25
}
j = {name: '鸣人'} // 报错,删了一个age属性
继承、实现概述
extends
- 调用父类使用super
- 子类继承父类的属性和方法
- 子类可以改写父类的属性和方法
类的继承
只能单继承, 但是可以多级继承
class A {
name:string;
constructor(name:string){
this.name = name
}
}
class B extends A {
age:number;
constructor(age:number, name:string){
// 必须先用super调用父类构造器
super(name)
this.age = age
}
}
class C extends B {
sex:string;
constructor(sex:string, age:number, name:string){
super(age,name)
this.sex = sex
}
}
const instance = new C('张三', 18, '男')
console.log(instance)
// C { name: '男', age: 18, sex: '张三' }
接口的继承
接口可以多继承
interface A {
id: number
}
interface B {
code: string
}
interface C extends A,B {
isFlag: boolean
}
const test:C = {
id:200,
code:'凌凌漆',
isFlag:false
}
console.log(test)
// { id: 200, code: '凌凌漆', isFlag: false }
implements
- 实现是对接口、抽象类中规定的方法, 进行具体的编码
- 实现可以多实现
类、接口、抽象类
在TS中, 这三者的概念, 和Java中一样, 这里只用最直白简洁的语言进行阐述.
类
- 类最单纯的理解可以看做是代码的封装, 好比数组是储存数据, 而类可以存储的更多, 还可以存储代码
接口
- 简单理解成一种约束、规则.
- 主要通过定义抽象方法, 让继承的子类去实现
- 试着理解如果引入一个插件, 需要创建一个类, 写固定的方法进行配置, 在插件里就可以通过定义接口, 让用户实现该接口, 进而约束用户进行固定配置的编码
##抽象类
-
开发不常用
-
里面既可以有具体方法, 也可以有抽象方法
-
既是对子类, 公共代码的封装; 也是对子类, 需要具体编码的方法的约束
-
可以结合上述两者的功能, 对其理解
接口及接口中属性的扩展
接口
- 类比java,可看做一种强制性的规范,一种约束
interface IPerson {
firstName:string
lastName:string
}
function showFullName (person:IPerson) {
return `${person.firstName}_${person.lastName}`
}
let kakaxi:IPerson {
firstName:'旗木'
lastName:'卡卡西'
}
console.log(showFullName(kakaxi))
// 旗木_卡卡西
接口继承
-
和类的继承是一个概念
-
TS支持继承多个接口
interface point2D { x: number y: Number } interface point3D { z: number } interface point4D extends point2D, point3D { w: number } const pointTest: point4D = { x: 100, y: 200, z: 300, w: 400 } console.log(pointTest)
扩展 - 只读和可省略
- readonly 只读
- ? 可省略
interface IAnimals {
// id是只读的number类型
readonly id:number
name:string
age:number
// sex可省略
sex?:string
}
const dog:IAnimals = {
id: 1,
name: '来福',
age: 2,
sex: '男'
}
类、继承、super
类的概念
- 类比Java
- 类的类型,通过接口定义
- 一个类可以同时实现多个接口
- 类里面定义构造函数contructor,是为了实例化的时候对属性值进行初始化
- 接口和接口之间:继承
- 类和接口之间:实现
interface IFly {
fly:()
}
// 定义一个类,这个类的类型就是上面定义的接口
class Person implements IFly {
fly() {
console.log("飞上天,和太阳肩并肩")
}
}
const p1 = new Person()
p1.fly()
interface IFly {
fly:()
}
interface ISwim {
swim:()
}
class Person implements IFly,ISwim {
fly() {
console.log("会飞天")
},
swim() {
console.log("会游泳")
}
}
const p2 = new Person()
p2.fly()
p2.swim()
interface TMyFlyAndSwim extends IFly,ISwim {}
class Person implements TMyFlyAndSwim {
fly() {
console.log("会飞天")
},
swim() {
console.log("会游泳")
}
}
const p3 = new Person()
p2.fly()
p2.swim()
继承
-
同比Java
-
super关键字可以调用父类的构造器,也可以调用父类中的实例函数
class Person { name:string age:number gender:string sayHi(food:string){ console.log(`我是${this.name},喜欢${food}`) } constructor(name:string,age:number,gender:string) { this.name = name this.age = age this.gender = gender } } class Student extends Person { constructor( name:string, age:number, gender:string) { // 使用super,调用父类的构造器实例化 super(name,age,gender) { } // 调用父类中的方法 sayHi(food:string) { super.sayHi(food) } } const p1 = new Person("张三",25,"男") p1.sayHi('西瓜') // 我是张三,喜欢西瓜 const s1 = new Student('小明',18,'男) s1.sayHi('草莓') // 我是小明,喜欢草莓
多态
- 父类引用,指向子类对象
- 不同类型的对象,针对相同的方法,产生了不同的行为
class Animal {
name:string
constructor(name:string){
this.name = name
}
run(distance:number) {
console.log(`${this,name}跑了${distance}米远的距离`)
}
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
// 重写run函数
run(distance:number) {
console.log(`${this,name}跑了${distance}米远的距离-----dog`)
}
}
class Pig extends Animal {
constructor(name:string) {
super(name)
}
// 重写run函数
run(distance:number) {
console.log(`${this,name}跑了${distance}米远的距离======pig`)
}
}
const ani:Animal = new Animal('动物')
ani.run()
const dog:Animal = new Dog('旺财')
dog.run()
const pig:Animal = new Pig('佩奇')
pig.run()
// 使用多态
const dog1:Animal = new Dog('小狗')
dog1.run()
const pig1:Animal = new Dog('小猪')
pig1.run()
修饰符
用于描述类中的成员(属性、构造器、函数)的可访问性
- public: 默认;表示公共的,被修饰的成员,任何位置都可访问;
- private:表示私有的,被修饰的成员,仅类中可访问;子类的类中也不能访问(this.xxx);
- protected:表示受保护的,被修饰的成员,子类的类中可访问(this.xxx);外部不可访问;
泛型
基本概念
泛型就是解决 类、接口、方法的复用性,以及对不特定数据类型的支持
- 函数名称后加
<>
, 一般使用符号T
占位, T代表具体的类型 - 入参、出参的类型一般具有关联性
function identity<K>(...arg: T[]): T[] {
return [...arg]
}
console.log(identity(1,2,3))
keyof关键字
keyof 用于获取某种类型的所有键,其返回类型是联合类型
代码释义:
表示只能是传入OOO类型, OOO类型中的属性之一(“id” | “code” | “age”)
let obj = {
id:"100",
code:996,
age:18
}
type OOO = typeof objfunction main<T, P1 extends keyof T>(obj:T, prop1: P1) {
console.log(obj[prop1]);
}
main(obj, 'id'); // '100'
main(obj, 'code'); // 996
main(obj, 'age'); // 18
泛型约束
泛型可以表示任意类型, 但是实际编码中, 有时候可以知道类型大致的范围,
为了避免使用出错, 给泛型限定类型的使用范围, 这就是泛型约束(缩小类型的取值范围)
- 一般通过两种方式实现
- 指定更加具体的类型
- 添加约束
eg1: 限制出入参为数组, 数组元素类型不固定
function func<T>(param: T[]): T[] {
return param
}
func([1,2,3,4])
eg2: 传入的类型, 必须含有 length 属性
interface ILength {
length: number
}
function getId<T extends ILength>(param: T): T {
console.log(param.length)
return param
}
// 虽然字符串和数组并没有继承 ILength, 但自身拥有length属性
console.log(getId('农夫山泉'));
console.log(getId([11,33,44]));
eg2: 传入的类型是一个对象, 必须拥有name, age属性
interface IUser {
name: string
age: number
}
function func<T extends IUser>(param: T): T {
console.log(param.name)
console.log(param.age)
return param
}
泛型在接口中的使用
interface IUser<T> {
getId: (param: T) => T
}
泛型工具类
- 泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
- 说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
Partial<Type>
Readonly<Type>
Pick<Type, Keys>
Partial
- Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props = {
id: string
children: number[]
}
type PartialProps = Partial<Props>
- 解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。
Readonly
- Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props = {
id: string
children: number[]
}
type ReadonlyProps = Readonly<Props>
- 解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
let props: ReadonlyProps = {
id: '1',
children: []
}
// 错误演示
props.id = '2'
- 当我们想重新给 id 属性赋值时,就会报错:无法分配到 “id” ,因为它是只读属性。
Pick
- Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型。
interface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
- 解释:
- Pick 工具类型有两个类型变量:1 表示选择谁的属性 2 表示选择哪几个属性。 2. 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
- 第二个类型变量传入的属性只能是第一个类型变量中存在的属性。
- 构造出来的新类型 PickProps,只有 id 和 title 两个属性类型。