一、TypeScript快速上手

初识TypeScript

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6+ 的支持,它由 Microsoft 开发,代码开源于 GitHub

安装TypeScript

命令行运行如下命令,全局安装TypeScript:

npm install -g typescript

安装完成后,在控制台运行如下命令,检查安装是否成功:

tsc -V 

第一个TypeScript程序

  • 编写TS程序
//helloworld.ts
function greeter(person){
  return 'hello,'+person
}
let user='tao'
console.log(greeter(user))
  • 手动编译代码
    我们使用了.ts扩展名,但是这段代码仅仅是JS而已
    在命令行上,运行 TypeScript 编译器:
tsc helloworld.ts

输出结果为一个helloworld.js文件,它包含了和输入文件中相同的JS代码
在命令行上,通过Node.js运行这段代码:

node helloworld.js

控制台输出:

hello,tao
  • vscode自动编译
1)生成配置文件tsconfig.json
tsc --init

2)修改tsconfig.json配置
"outDir":"./js",
"strict":false,

3)启动监视任务
终端->运行任务->监视tsconfig.json
  • 类型注解
    给函数参数添加:string类型的注解,如下:
function greeter(person:string){
   return 'hello,'+person
}
let user='tao'
console.log(greeter(user))

TypeScript 里的类型注解是一种轻量级的为函数或变量添加约束的方式。在这个例子里,我们希望函数接受一个字符串参数。然后尝试传入数组:

function greeter(person:string){
  return 'hello,'+person
}
let user=[0,1,2]
console.log(greeter(user))

重新编译,产生了一个错误:

error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
  • 接口
    使用接口来描述一个拥有firstNamelastName字段的对象。在TypeScript里,只在两个类型内部的结构,那么这两个类型就是兼容的。这就允许我们在实现接口的时候只要保证包含了接口要求的结构就可以,而不必明确地使用implement语句
interface Person{
  firstName:string,
  lastName:string
}
function greeter(person:Person){
  return 'hello,'+person.firstName+person.lastName
}
let user={
 firstName:'tao',
 lastName:'xie'
}
console.log(greeter(user))

  • 创建一个User类,它带有一个构造函数和一些公共字段。因为类的字段包含了接口所需要的字段,所以他们能很好的兼容
class User {
 fulName:string,
 firstName:string,
 lastName:string
 
 constructor(firstName:string,lastName:string){
    this.firstName = firstName
    this.lastName = lastName
    this.fullName = firstName + ' ' + lastName
  }
}

interface Person {
  firstName: string
  lastName: string
}

function greeter (person: Person) {
  return 'Hello, ' + person.firstName + ' ' + person.lastName
}

let user = new User('tao', 'xie')

console.log(greeter(user))

TypeScript常用语法

基础类型
  • 布尔值
    最基本的数据类型就是简单的true/false,在JS和TS里叫做boolean
let isDone:boolean=false
isDone=true
//isDone=2 //error
  • 数字
    和JS一样,TS里的所有数字都是浮点数。这些浮点数的类型都是number。除了支持十进制和十六进制字面量,TS还支持ECMAScript2015中引入的二进制和八进制字面量
let a1:number=10 //十进制
let a2:number=0b1010 //二进制
let a3:number=0o12 //八进制
let a4:number=0xa //十六进制
  • 字符串
    使用string表示文本数据类型。和JS一样,可以使用双引号"或者单引号'表示字符串
let name:string='tom'
name='jack'
//name=12 //error
let age:number=12
const info=`My name is ${name},I am ${age} years old`
  • undefined和null
    默认情况下,nullundefined是所有类型的子类型。
let u:undefined=undefined
let u:null=null
  • 数组
//第一种定义数组:直接在元素类型后面加[]
let list1:number[]=[1,2,3]

//第二种定义数组
let list2:Array<number>=[1,2,3]
  • 元组Tuple
    元组类型允许表示一个已知元素数量和类型的数组,各数组元素不必相同
let t1:[string,number]
t1=['hello',1] //ok
t1=[1,'hello'] //error

当访问一个已知索引的元素,会得到正确的类型:

console.log(t1[0].substring(1)) //ok 
console.log(t1[1].substring(1)) //error number不存在substring方法
  • 枚举
    enum使用枚举类型可以为一组数组赋予友好的名字
enum Color {
  Red,
  Green,
  Blue
}
//枚举数值默认从0开始依次递增
//根据特定的名称得到对应的枚举类型
let myColor:Color = Color.Green //0
console.log(myColor,Color.Red,Color.Blue)

默认情况下,从0开始为元素编号,也可以指定成员的数值:

enum Color {
  Red=1,
  Green,
  Blue
}
let c:Color=Color.Green

或者全部采用手动赋值:

enum Color {
  Red=1,
  Green=2,
  Blue=4
}
let c:Color=Color.Green

枚举类型提供一个便利是可以由枚举的值得到它的名字:

enum Color {
  Red=1,
  Green,
  Blue
}
let colorName:string=Color[2]
console.log(colorName) //Green
  • any
let notSure:any=4
notSure='maybe a string'
notSure=false

在对现有代码进行改写的时候,any类型是十分有用的,它允许在编译时可选择地包含或移除检查。并且当只知道一部分数据的类型时,any类型也是有用的:

let list:any[]=[1,true,'free']
list[1]=100
  • void
    某种程度上,void类型像是与any类型相反,它表示没有任何类型。当一个函数没有返回值时,通常会见到其返回值类型的是void:
/*表示没有任何类型,一般用来说明函数的返回值不能是undefined和null之外的值*/
function fn():void{
 console.log('fn()')
 //return undefined
 //return null
 //return 1  //error
}

声明一个void类型的变量没有什么用,因为你只能为它赋予undefinednull:

let unusable:void=undefined
  • object
    object表示非原始类型,也就是除number,string,boolean之外的类型
    使用object类型,就可以更好的表示像Object.create这样的API
function fn2(obj:object){
  console.log('fn2()',obj)
  return {}
  // return undefined
  // return null
}
console.log(fn2(new String('abc')))
//console.log(fn2('abc')) //error
console.log(fn2(String))
  • 联合类型
    联合类型表示取值可以为多种类型中的一种
    需求1:定义一个一个函数得到一个数字或字符串值的字符串形式值
function toString2(x:number|string):string{
  return x.toString()
}

需求2:定义一个一个函数得到一个数字或字符串值的长度

function getLength(x:number|string){
  //return x.length //error
  if(x.length){ //error
    return x.length
  }else{
    return x.toString().length
  }
}
  • 类型断言
    通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语法里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
    类型断言有两种形式。其一是 '尖括号’语法,另一个为as语法
/*
 类型断言:可以用来手动指定一个值的类型
 语法:
     方式一:<类型>值
     方式二:值 as 类型  tsx中只能用这种方式
*/

/*需求:定义一个函数得到一个字符串或者数值数据的长度*/
function getLength(x:number|string){
    if((<string>x).length){
      return (x as string).length
    }else{
      return x.toString().length
    }
}
console.log(getLength('abcd'),getLength(1234))
  • 类型推断
    类型推断:TS在没有明确的指定类型的时候推测出一个类型
    有下面2种情况:1.定义变量时赋值,推断为对应的类型 2.定义变量时没有赋值,推断为any类型
/*定义变量时赋值,推断为对应的类型*/
let b9=123. //number
//b9='abc' //error

/*定义变量时没有赋值,推断为any类型*/
let b10 //any
b10=123
b10='abc'
接口

接口是对象的状态(属性)和行为(方法)的抽象(描述)

  • 接口初探
    需求:创建人的对象,需要对人的属性进行一定的约束
id是number类型,必须有,只读
name是string类型,必须有
age是number类型,必须有
sex是string类型,可以没有

下面通过一个简单示例来观察接口是如何工作的:

/*
 在ts中,我们使用接口(interface)来定义对象的类型
 接口:是对象的状态(属性)和行为(方法)的抽象(描述)
 接口类型的对象
   多了或者少了属性是不允许的
   可选属性:?
   只读属性:readonly
*/

/*
需求:创建人的对象,需要对人的属性进行一定的约束
id是number类型,必须有,只读
name是string类型,必须有
age是number类型,必须有
sex是string类型,可以没有
*/

//定义人的接口
interface IPerson{
 id:number,
 name:string,
 age:number,
 sex:string
}
const person1:Person={
  id:1,
  name:'tom',
  age:20,
  sex:'男'
}

类型检查器会查看对象内部的属性是否与Iperson接口描述一致,如果不一致就会提示类型错误

  • 可选属性
    接口里的属性不全是必需的。有些是只在某些条件下存在,或者不存在
interface IPerson{
 id:number,
 name:string,
 age:number,
 sex?:string
}

带有可选属性的接口与普通接口定义差不多,只是在可选属性名字定义的后面加一个?
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误

const person2:IPerson={
  id:1,
  name:'tom',
  age:20,
  //sex:'男' //可以没有
}
  • 只读属性
    一些对象属性只能在对象刚刚创建的时候修改其值。可以在属性名前用readonly来指定只读属性:
interface IPerson{
 readonly id:number,
 name:string,
 age:number,
 sex?:string
}

一旦赋值后再也不能被改变

const person2:IPerson={
 id:2,
 name:'tom',
 age:20,
 //sex:'男' //可以没有
 //xxx:12. //error 没有在接口中定义,不能有
}
person2.id=2 //error

readonly vs const
最简单判断该用readonly还是const的方法是看要把它作为变量使用还是作为一个属性。作为变量使用的话用const,作为属性使用的话用readonly

  • 函数类型
    接口能够描述JS中对象拥有的各种各样的外形。除了描述带有属性的普通对象外,接口也可以描述函数类型。
    为了使用接口表示函数类型,需要给接口定义一个调用签名。就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
/*
 接口可以描述函数类型(参数的类型与返回的类型)
*/
interface SearchFunc{
 (source:string,subString:string):boolean
}

这样定义后,可以像使用其他接口一样使用这个函数类型的接口。下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量:

const mySearch:SearchFunc=function(source:string,sub:string):boolean{
   return source.search(sub)>-1
}
console.log(mySearch('abcd','bc'))
  • 类类型
    类实现接口
/*
 类类型:实现接口
 1.一个类可以实现多个接口
 2.一个接口可以继承多个接口
*/
interface Alam{
  alert():any
}
interface Light{
  lightOn():void
  lightOff():void
}
class Car implements Alam {
 alert(){
  console.log('Car alert')
 }
}
  • 一个类可以实现多个接口
class Car2 implements Alam,Light{
 alert(){
   console.log('Car alert')
 },
 lightOn(){
   console.log('Car light on')
 }lightOff(){
   console.log('Car light off')
 }
}
  • 接口继承接口
    和类一样,接口也可以相互继承。能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重写的模块里
interface LightableAlarm extends Alam,Light{

}
  • 基本示例:
/*
 类的基本定义与使用
*/
class Greeter{
 //声明属性
 message:string
 //构造方法
 constructor(message:string){
  this.message=message
 }
 //一般方法
 greet():string{
   return 'hello'+this.message
 }
}

//创建类的实例
const greeter=new Greeter('world')
//调用实例的方法
console.log(greeter.greete())

在引用任何一个类成员的时候都用了this。它表示访问的是类的成员。

后面一行,使用new构造了Greeter类的实例。它会调用之前定义的构造函数,创建一个Greeter类型的新对象,并执行构造函数初始化它。

最后一行通过greeter对象调用其greet方法

  • 继承
    基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
/*
 类的继承
*/
class Animal{
  run(distance:number){
    console.log(`Animal run ${distance}m`)
  }
}
class Dog extends Animal{
  cry()
   console.log('wang! wang!')
  }
}
const dog=new Dog()
dog.cry()
dog.run(100) //可以调用从父中继承得到的方法

这个例子展示了最基本的继承:类从基类中继承了属性和方法。这里,Dog是一个派生类,它派生自Animal基类,通过extends关键字。派生类通常被称作子类,基类通常被称作超类。
因为Dog继承了Animal的功能,因此我们可以创建一个Dog的实例,它能够cry()run()

class Animal {
  name:string
  constructor (name;string){
    this.name=name
  }
  run(distance:number=0){
    console.log(`${this.name} run ${distance} m`)
  }
}

class Snake extends Animal {
  constructor(name:string){
    //调用父类型构造方法
    super(name)
  }
  //重写父类型的方法
  run(distance:number=5){
    console.log('sliding...')
    super.run(distance)
  }
}

class Horse extends Animal {
  constructor (name:string){
    //调用父类型构造方法
    super(name)
  }
  //重写父类型的方法
  run(distance:number=50){
    console.log('dashing...')
    //调用父类型的一般方法
    super.run(distance)
  }
  xxx(){
    console.log('xxx()')
  }
}

const snake=new Snake('sn')
snake.run()

const horse=new Horse('ho')
horse.run()

//父类型引用指向子类型的实例=》多态
const tom:Animal=new Horse('ho22')
tom.run()

/*如果子类型没有扩展的方法,可以让子类型引用指向父类型的类型*/
const tom3:Snake=new Animal('tom3')
tom3.run()
/*如果子类型有扩展的方法,不能让子类型引用指向父类型的实例*/
//const tom2:Horse=new Animal('tom2')
//tom3.run()

使用extends关键字创建了Animal的两个子类:HorseSnake。派生类包含一个构造函数,它必须调用super(),它会执行基类的构造函数。而且,在构造函数里访问this的属性之前,一定要调用super()。这是TS强制执行的一条重要规则。

这个例子演示了如何在子类里可以重写父类的方法。Snake类和Horse类都创建了run方法,它们重写了从Animal继承来的run方法,使得run方法根据不同的类而具有不同的功能。注意,即使tom被声明为Animal类型,但因为它的值是Horse,调用tom.run(34)时,它会调用Horse里重写的方法。

  • 公共,私有与受保护的修饰符
    1.默认为public
    可以自由的访问程序里定义的成员。在TS里,成员都默认为public。也可以明确的将一个成员标记为public

    2.理解private
    当成员被标记为private时,它就不能在声明它的类的外部访问。

    3.理解protected
    protected修饰符与private修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以访问。

/*
 访问修饰符:用来描述类内部的属性/方法的可访问性
 public:默认值,公开的外部可以访问
 private:只有类内部可以访问
 protected:类内部和子类可以访问
*/
class Animal {
  public name:string
  public constructor(name:string){
    this.name=name
  }
  public run(distance:name=0){
    console.log(`${this.name} run ${distance}m`)
  }
}
class Person extends Animal{
  private age:number=18
  protected sex:string='男'
  run (distance:number){
    console.log('Perosn jumping...')
    super.run(distance)
  }
}
class Student extends Person{
  run(distance:number=6){
    console.log('Studnet jumping...')
    
    console.log(this.sex) //子类能看到父类中受保护的成员
    //console.log(this.age) //子类看不到父类中私有的成员

    super.run(distance)
  }
}

console.log(new Person('abc').name)  //公开的可见
//console.log(new Person('abc').sex)  //受保护的不可见
//console.log(new Person('abc').age). //私有的不可见
  • readonly修饰符
    可以使用readonly关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。
class Person{
  readonly name:string='abc'
  constructor(name:string){
   this.name=name
  }
}
let john=new Person('John')
//john.name='peter'  //error

参数属性
在上面的例子中,必须在Person类里定义一个只读成员name和一个参数为name的构造函数,并且立刻将name的值赋给this.name,这种情况经常会遇到。参数属性可以方便地让在一个地方定义并初始化一个成员。

class Person2{
  constructor (readonly name:string){
    
  }
}
const p=new Person2('jack')
console.log(p.name)

注意看是如何舍弃参数name,仅在构造函数里使用readonly name:string参数来创建和初始化name成员。把声明和赋值合并至一处。
参数属性通过给构造函数参数面前添加一个访问限定符来声明。使用private限定一个参数属性会声明并初始化一个私有成员;对于publicprotected来说也是一样。

  • 存取器
    Typescript支持通过getters/setters来截取对对象成员的访问。它能帮助你有效的控制对对象成员的访问。
class Person {
  firstName:string='A'
  lastName:string='B'
  get fullName(value){
     return this.firstName+'-'+this.lastName
  }
  set fullName(value){
     const names=value.split('-')
     this.firstName=name[0]
     this.lastName=name[1]
  }
}

const p=new Person()
console.log(p.fullName)

p.firstName='C'
p.lastName='D'
console.log(p.fullName)

p.fullName='E-F'
console.log(p.firstName,p.lastName)
  • 静态属性
    可以创建类的静态成员,这些属性存在于类本身而不是类的实例上。
/*
静态属性:是类对象的属性
非静态属性:是类的实例对象的属性
*/
class Person{
  name1:string='A'
  static name2:string='B'
}
console.log(Person.name2)
console.log(new Person().name1)
  • 抽象类
    抽象类作为其他派生类的基类使用。它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象的方法。
/*
抽象类
  不能创建实例对象,只有实现类才能创建实例
  可以包含未实现的抽象方法
*/
abstract class Animal{
  abstract cry()
  run(){
    console.log('run()')
  }
}
class Dog extends Animal{
  cry(){
    console.log('Dog cry()')
  }
}
const dog=new Dog()
dog.cry()
dog.run()
函数
  • 基本实例
    和JS一样,TS函数可以创建有名字的函数和匿名函数。你可以随意选择合适应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。
    下面的例子可以迅速回想起JS的两种函数:
//命名函数
function add(x,y){
  return x+y
}
//匿名函数
let myAdd=function(x,y){
  return x+y
}
  • 函数类型
    1.为函数定义类型
    为上面函数添加类型
function add(x:number,y:number):number{
  return x+y
}

let myAdd=function(x:number,y:number):number{
  return x+y
}

可以给每个参数添加类型之后再为函数本身添加返回值类型。TS能够根据返回语句自动推断出返回值类型。

  • 书写完整函数类型
let myAdd2:(x:number,y:number)=>number=function(x:number,y:number):number{ return x+ y }
  • 可选参数和默认参数
    TS里的每个函数参数都是必须的。这不是指不能传递nullundefined作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
    JS里,每个参数都是可选的,可传可不传。没有传参的时候,它的值就是undefined。在TS里可以在参数名旁使用?实现可选参数的功能。
function bulidName(firstName:string='A',lastName?:string):string {
  if(lastName){
    return firstName+'-'+lastName
  } else {
    return firstName
  }
}

console.log(bulidName('C','D'))
console.log(bulidName('C'))
console.log(bulidName())
  • 剩余参数
    必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。有时,想同时操作多个参数,或者并不知道会有多少参数传递进来。在JS里,可以使用arguments来访问所有传入的参数。
    在TS里,可以把所有参数收集到一个变量里:剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。编译器创建参数数组,名字是你在省略号(...)后面给定的名字,可以在函数体内使用这个数组。
function info(x:string,...args:string[]){
  console.log(x,args)
}
info('abc','c','b','a')
  • 函数重载
    函数重载:函数名相同,而形参不同的多个函数
/*
函数重载:函数名相同,而形参不同的多个函数
需求:我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行想加
*/
//重载函数声明
function add(x:string,y:string):string
function add(x:number,y:number):number

//定义函数实现
function add(x:string | number,y:string | number):string | number {
  //在实现上要注意严格判断两个参数的类型是否相等,而不是简单的写一个x+y
  if(typeof x==='string'&&typeof y==='string'){
    return x+y
  }else if(typeof x==='number'&&typeof y==='number'){
    return x+y
  }
}

console.log(add(1,2))
console.log(add('a'+'b'))
//console.log(1,'a') //error
泛型

指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。

  • 引入
    下面创建一个函数,实现功能:根据指定的数量count和数据value,创建一个包含countvalue的数组不用泛型的话,这个函数可能是下面这样:
function createArray(value:any,count:number):any[]{
  const arr:any[]=[]
  for(let index=0;index<count;index++){
    arr.push(value)
  }
  return arr
}

const arr1=createArray(11,3)
const arr2=createArray('aa',3)
console.log(arr1[0].toFixed(),arr2[0].split(''))
  • 使用函数泛型
function createArray2<T>(value:T,count:number){
  const arr:Array<T>=[]
  for(let index=0;index<count;index++){
    arr.push(value)
  }
  return arr
}
const arr3=createArray2<number>(11,3)
console.log(arr3[0].toFixed())
// console.log(arr3[0].split('')) //error
const arr4=createArray2<string>('aa',3)
console.log(arr4[0].split(''))
//console.log(arr[4].toFixed()) //error
  • 多个泛型参数的函数
//一个函数可以定义多个泛型参数
function swap<K,V>(a:K,b:V):[K,V]{
  return [a,b]
}
const result=swap<string,number>('abc',123)
console.log(result[0].length,result[1].toFixed())
  • 泛型接口
    在定义接口时,为接口中的属性或方法定义泛型类型
    在使用接口时,再指定具体的泛型类型
interface ibaseCRUD<T>{
  data:T[]
  add:(t:T)=>void
  getById:(id:number)=>T
}

class User{
  id?:number;//id逐渐自增
  name:string;//姓名
  age:number;//年龄

  constructor(name,age){
    this.name=name
    this.age=age
  }
}

class UserCRUD implements IbaseCRUD <User>{
  data:User[]=[]
  add(user:User):void{
    user={...user,id:Date.now()}
    this.data.push(user)
    console.log('保存user',user.id)
  }
  getById(id:number):User{
    return this.data.find(item=>item.id===id)
  }
}

const userCRUD=new UserCRUD()
UserCRUD.add(new User('tom',12))
UserCRUD.add(new User('tom2',13))
console.log(userCRUD.data)
  • 泛型类
    在定义类时,为类中的属性或方法定义泛型类型 在创建类的实例时,再指定特定的泛型类型
class GenericNubmer<T>{
  zeroValue:T
  add:(x:T,y:T)=>T
}
let myGenericNumber=new GenericNubmer<number>()
myGenericNumber.zeroValue=0
myGenericNumber.add=function(x,y){
  return x+y
}

let myGenericString=new GenericNubmer<string>()
myGenericString.zeroValue='abc'
myGenericString.add=function(x,y){
  return x+y
}
console.log(myGenericString.add(myGenericNumber.zeroValue,'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue,12))
  • 泛型约束
    直接对一个泛型参数取length属性会报错,因为这个泛型根本不知道它是否有这个属性
//没有泛型约束
function fn<T>(x:T):void{
  //console.log(x.length) //error
}

可以使用泛型约束来实现

interface Lengthwise{
  length:number
}
//指定泛型约束
function fn2<T extends Lengthwise>(x:T):void{
  console.log(x.length)
}

需要传入符合约束类型的值,必须包含length属性:

fn2('abc')
//fn2(123) //error number没有length属性
  • 其他

声明文件


当使用第三方库时,需要引用它的生命文件,才能获取对应的代码补全、接口提示等功能

什么是生命语句

假如想使用第三方库jQuery,一种常见的方式是在html中通过<script>标签引入jQuery,然后就可以使用全局变量$jQuery

但是在ts中,编译器并不知道$或jQuery是什么东西

/*
 当使用第三方库时,需要引用它的生命文件,才能获得对应的代码补全、接口提示等功能。
 声明语句:如果需要ts对新的语法进行检查。需要加载了对应的类型说明代码
 declare var jQuery:(selector:string)=>any
 声明文件:把声明语句放到一个单独的文件(jQuery.d.ts)中,ts会自动解析到项目中所有声明文件
 下载声明文件:npm install @type/jquery --save-dev
*/
jQuery('#foo')
//ERROR:Cannot find name 'jQuery'

这时,需要使用declare var 来定义它的类型

declare var jQuery:(selector:string)=>any
jQuery('#foo')

一般声明文件都会单独写成一个xxx.d.ts文件

创建01_jQuery.d.ts,将声明语句定义其中,TS编译器会扫描并加载项目中所有的TS声明文件

declare var jQuery:(selector:string)=>any

很多的第三方库都定义了对应的声明文件库,库文件名一般为@type /xxx,可以在https://www.npmjs.com/package/package进行搜索

有的第三方库在下载时就会自动下载对应的声明文件库(比如:webpack),有的可能需要单独下载(比如jQuery/react)

内置对象


JS中有很多内置对象,它们可以直接在TS中当做定义好了的类型

内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指ECMAScript和其他环境(比如DOM)的标准

1.ECMAScript的内置对象

/*1.ECMAScript的内置对象*/
let b:Boolean=new Boolean(true)
let n:Number=new Number(1)
let s:String=new String('abc')
let d:Date=new Date()
let r:RegExp=/^1/
let e:Error=new Error('error message')
b=true
// let bb:boolean=new Boolean(2) //error

2.BOM和DOM的内置对象

const div:HTMLELement=document.getElementById('test')
const divs:NodeList=document.querySelectorAll('div')
document.addEventListener('click',(event:MouseEvent)=>{
   console.log(event.target)
})
const fragment:DocumentFragment=document.createDocumentFragment()

二、认识vue3

认识vue3

1.了解相关信息

  • Vue3支持vue2的大多数属性
  • 更好的支持Typescript

2.性能提升

  • 使用Proxy代替defineProperty实现数据响应式
  • 重写虚拟DOM的实现和Tree-Shaking

3.新增特性

  • Composition(组合)API

  • setup
    ref和reactive
    computed和watch
    新的生命周期函数
    provide与inject

  • 新组件
    Fragment-文档碎片
    Teleport-瞬移组件的位置
    Suspense-异步加载组件的loading界面

  • 其他API更新
    全局API的修改
    将原来的全局API转移到应用对象
    模版语法变化

创建vue3项目

  • 使用vue-cli创建
## 安装或者升级
npm install -g @vue/cli

## 保证vue cli版本在4.5.0以上
vue --version
## 创建项目
vue create my-project
  • 使用vite创建
npm init vite-app <project-name>
cd <project-name>
npm install
npm run sev

Composition API(常用部分)

  • setup
    新的option,所有的组合API函数都在此使用,只在初始化时执行一次
    函数如果返回对象,对象中的属性或方法,模板中可以使用
  • ref
    1.作用:定义一个数据的响应式
    2.语法:const xxx=ref(initValue)
    创建一个包含响应式数据的引用(reference)对象
    js中操作数据:xxx.value
    模板中操作数据:不需要.value
    3.一般用来定义一个基本类型的响应式数据
<template>
  <h2>{{count}}</h2>
  <hr>
  <button @click="update">更新</button>
</template>
<script>
import {ref} from 'vue'
export default{
  /*在vue3中依然可以使用data和methods配置,但建议使用其新语法实现*/
  //data(){
  // return{
  //     count:0
  //   }
  //},
  //methods:{
  //  update(){
  //   this.count++
  //   }
  //}
  /*使用vue3的composition API*/
  setup(){
    //定义响应式数据 ref对象
    const count=ref(1)
    console.log(count)

    //更新响应式数据的函数
    function update(){
      count.value=count.value+1
    }
  }
}
</script>
  • reactive
    1.作用:定义多个数据的响应式
    2.const proxy=reactive(obj):接受一个普通对象然后返回该普通对象的响应式代理器对象
    3.响应式转换是“深层的”:会影响对象内部所有嵌套的属性
    4.内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
<template>
  <h2>name:{{state.name}}</h2>
  <h2>age:{{state.age}}</h2>
  <h2>wife:{{state.wife}}</h2>
  <hr>
  <button @click="update">更新</button>
</template>
<script>
import {reactive} from 'vue'
export default{
 setup(){
  /*定义响应式对象*/
  const state=reactive({
     name:'tom',
     age:25,
     wife:{
      name:"marry",
      age:22
     }
  })
  console.log(state,state.wife)
 
  const update=()=>{
    state.name+='--'
    state.age+=1
    state.wife.name+='++'
    state.wife.age+=2
  }

  return {
    state,
    update
  }
 }
}
</script>
  • 比较vue2与vue3的响应式(重要)

vue2的响应式

1.核心:
对象:通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
数组:通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持

Object.defineProperty(data,'count',{
   get(){},
   set(){}
})

2.问题
对象直接新添加的属性或删除已有属性,界面不会自动更新
直接通过下标替换元素或更新length,界面不会自动更新arr[1]={}

vue3的响应式
1.核心:
通过Proxy(代理):拦截对data任意属性的任意(13种)操作,包括属性值的读写,属性的添加,属性的删除等…
通过Reflect(反映):动态对被代理对象的相应属性进行特定的操作

new Proxy(data,{
  //拦截读取属性值
  get(target,prop){
    return Reflect.get(target,prop)
  }
  //拦截设置属性值或添加新属性
  set(target,prop,value){
    return Reflect.set(target,prop,value)
  }
  //拦截删除属性
  deleteProperty(target,prop){
    return Reflect.deleteProperty(target,prop)
  }
})
proxy.name="tom"
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Proxy 与 Reflect</title>
</head>
<body>
  <script>
    
    const user = {
      name: "John",
      age: 12
    };

    /* 
    proxyUser是代理对象, user是被代理对象
    后面所有的操作都是通过代理对象来操作被代理对象内部属性
    */
    const proxyUser = new Proxy(user, {

      get(target, prop) {
        console.log('劫持get()', prop)
        return Reflect.get(target, prop)
      },

      set(target, prop, val) {
        console.log('劫持set()', prop, val)
        return Reflect.set(target, prop, val); // (2)
      },

      deleteProperty (target, prop) {
        console.log('劫持delete属性', prop)
        return Reflect.deleteProperty(target, prop)
      }
    });
    // 读取属性值
    console.log(proxyUser===user)
    console.log(proxyUser.name, proxyUser.age)
    // 设置属性值
    proxyUser.name = 'bob'
    proxyUser.age = 13
    console.log(user)
    // 添加属性
    proxyUser.sex = '男'
    console.log(user)
    // 删除属性
    delete proxyUser.sex
    console.log(user)
  </script>
</body>
</html>
  • setup
    1.setup执行的时机
    1)在beforeCreate之前执行(一次),此时组件对象还没创建;
    2)this是undefined,不能通过this来访问data/computed/methods/props;
    3)其实所有的composition API相关回调函数中也都不可以;
    2.setup的返回值
    1)一般都返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性/方法
    2)返回对象中的属性会与data函数返回对象合并成为组件对象的属性
    3)返回对象中的方法会与methods中的方法合并成功组件对象的方法
    4)如果有重名,setup优先
    5)注意:一般不要混合使用:methods中可以访问setup提供的属性和方法,但在setup方法中不能访问data和methods;setup不能是async函数:因为返回值不再是return的对象,而不是promise,模板看不到return对象中的属性数据
    3.setup参数
    1)setup(props,context)/setup(props,{attrs,slots,emit})
    2)props:包含props配置声明且传入了所有属性的对象
    3)attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs
    4)slots:包含所有传入的插槽内容的对象,相当于this.$slots
    5)emit:用来分发自定义事件的函数,相当于this.$emit
<template>
  <h2>App</h2>
  <p>msg: {{msg}}</p>
  <button @click="fn('--')">更新</button>

  <child :msg="msg" msg2="cba" @fn="fn"/>
</template>
<script lang="ts">
import {
  reactive,
  ref,
} from 'vue'
import child from './child.vue'

export default {

  components: {
    child
  },

  setup () {
    const msg = ref('abc')

    function fn (content: string) {
      msg.value += content
    }
    return {
      msg,
      fn
    }
  }
}
</script>
<template>
  <div>
    <h3>{{n}}</h3>
    <h3>{{m}}</h3>

    <h3>msg: {{msg}}</h3>
    <h3>msg2: {{$attrs.msg2}}</h3>

    <slot name="xxx"></slot>

    <button @click="update">更新</button>
  </div>
</template>
<script lang="ts">

import {
  ref,
  defineComponent
} from 'vue'

export default defineComponent({
  name: 'child',

  props: ['msg'],

  emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验

  data () {
    console.log('data', this)
    return {
      // n: 1
    }
  },

  beforeCreate () {
    console.log('beforeCreate', this)
  },

  methods: {
    // update () {
    //   this.n++
    //   this.m++
    // }
  },

  // setup (props, context) {
  setup (props, {attrs, emit, slots}) {

    console.log('setup', this)
    console.log(props.msg, attrs.msg2, slots, emit)

    const m = ref(2)
    const n = ref(3)

    function update () {
      // console.log('--', this)
      // this.n += 2 
      // this.m += 2

      m.value += 2
      n.value += 2

      // 分发自定义事件
      emit('fn', '++')
    }

    return {
      m,
      n,
      update,
    }
  },
})
</script>
  • reactive与ref细节
    1)是vue3的composition API中最重要的响应式API
    2)ref用来处理基本类型的数据,reactive用来处理对象(递归深度响应式)
    3)如果用ref对象/数组,内部会自动将对象/数组转换为reactive的代理对象
    4)ref内部:通过给value属性添加getter/setter来实现对数据的劫持
    5)reactive内部:通过使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
    6)ref的数据操作:在js中要.value,在模板中不需要(内部解析模板时会自动添加.value)
<template>
  <h2>App</h2>
  <p>m1: {{m1}}</p>
  <p>m2: {{m2}}</p>
  <p>m3: {{m3}}</p>
  <button @click="update">更新</button>
</template>
<script lang="ts">
import {
  reactive,
  ref
} from 'vue'

export default {

  setup () {
    const m1 = ref('abc')
    const m2 = reactive({x: 1, y: {z: 'abc'}})

    // 使用ref处理对象  ==> 对象会被自动reactive为proxy对象
    const m3 = ref({a1: 2, a2: {a3: 'abc'}})
    console.log(m1, m2, m3)
    console.log(m3.value.a2) // 也是一个proxy对象

    function update() {
      m1.value += '--'
      m2.x += 1
      m2.y.z += '++'

      m3.value = {a1: 3, a2: {a3: 'abc---'}}
      m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
      console.log(m3.value.a2)
    }

    return {
      m1,
      m2,
      m3,
      update
    }
  }
}
</script>
  • 计算属性与监视
<template>
  <h2>App</h2>
  fistName: <input v-model="user.firstName"/><br>
  lastName: <input v-model="user.lastName"/><br>
  fullName1: <input v-model="fullName1"/><br>
  fullName2: <input v-model="fullName2"><br>
  fullName3: <input v-model="fullName3"><br>

</template>
<script lang="ts">
/*
计算属性与监视
1. computed函数: 
  与computed配置功能一致
  只有getter
  有getter和setter
2. watch函数
  与watch配置功能一致
  监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
  通过配置deep为true, 来指定深度监视
3. watchEffect函数
  不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
  默认初始时就会执行第一次, 从而可以收集需要监视的数据
  监视数据发生变化时回调
*/

import {
  reactive,
  ref,
  computed,
  watch,
  watchEffect
} from 'vue'

export default {

  setup () {
    const user = reactive({
      firstName: 'A',
      lastName: 'B'
    })

    // 只有getter的计算属性
    const fullName1 = computed(() => {
      console.log('fullName1')
      return user.firstName + '-' + user.lastName
    })

    // 有getter与setter的计算属性
    const fullName2 = computed({
      get () {
        console.log('fullName2 get')
        return user.firstName + '-' + user.lastName
      },

      set (value: string) {
        console.log('fullName2 set')
        const names = value.split('-')
        user.firstName = names[0]
        user.lastName = names[1]
      }
    })

    const fullName3 = ref('')

    /* 
    watchEffect: 监视所有回调中使用的数据
    */
    /* 
    watchEffect(() => {
      console.log('watchEffect')
      fullName3.value = user.firstName + '-' + user.lastName
    }) 
    */

    /* 
    使用watch的2个特性:
      深度监视
      初始化立即执行
    */
    watch(user, () => {
      fullName3.value = user.firstName + '-' + user.lastName
    }, {
      immediate: true,  // 是否初始化立即执行一次, 默认是false
      deep: true, // 是否是深度监视, 默认是false
    })

    /* 
    watch一个数据
      默认在数据发生改变时执行回调
    */
    watch(fullName3, (value) => {
      console.log('watch')
      const names = value.split('-')
      user.firstName = names[0]
      user.lastName = names[1]
    })

    /* 
    watch多个数据: 
      使用数组来指定
      如果是ref对象, 直接指定
      如果是reactive对象中的属性,  必须通过函数来指定
    */
    watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
      console.log('监视多个数据', values)
    })

    return {
      user,
      fullName1,
      fullName2,
      fullName3
    }
  }
}
</script>
  • 生命周期
    与2.x版本生命周期相对应的组合API
    beforeCreate=>使用setup()
    create=>使用setup()
    beforeMount=>onBeforeMount
    mounted=>onMounted
    beforeUpdate=>onBeforeUpdate
    updated=>onUpdated
    beforeDestroy=>onBeforeUnmount
    destroyed=>onUnmounted
    errorCaptured=>onErrorCaptured

  • toRefs
    把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref
    应用:当从合成函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
    问题:reactive对象取出的所有属性值都是非响应式的
    解决:利用toRefs可以将一个响应式reactive对象的所有原始属性转换为响应式的ref属性

<template>
  <h2>App</h2>
  <h3>foo: {{foo}}</h3>
  <h3>bar: {{bar}}</h3>
  <h3>foo2: {{foo2}}</h3>
  <h3>bar2: {{bar2}}</h3>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
  将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
  应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
        这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {

  setup () {

    const state = reactive({
      foo: 'a',
      bar: 'b',
    })

    const stateAsRefs = toRefs(state)

    setTimeout(() => {
      state.foo += '++'
      state.bar += '++'
    }, 2000);

    const {foo2, bar2} = useReatureX()

    return {
      // ...state,
      ...stateAsRefs,
      foo2, 
      bar2
    }
  },
}

function useReatureX() {
  const state = reactive({
    foo2: 'a',
    bar2: 'b',
  })

  setTimeout(() => {
    state.foo2 += '++'
    state.bar2 += '++'
  }, 2000);

  return toRefs(state)
}

</script>
  • ref获取元素
    利用ref函数获取组件中的标签元素
    功能需求:让输入框自动获取焦点
<template>
  <h2>App</h2>
  <input type="text">---
  <input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/* 
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
  setup() {
    const inputRef = ref<HTMLElement|null>(null)

    onMounted(() => {
      inputRef.value && inputRef.value.focus()
    })

    return {
      inputRef
    }
  },
}
</script>
Logo

前往低代码交流专区

更多推荐