TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6+ 的支持,它由 Microsoft 开发,代码开源于 GitHub上
命令行运行如下命令,全局安装TypeScript:
npm install -g typescript
安装完成后,在控制台运行如下命令,检查安装是否成功:
tsc -V
//helloworld.ts
function greeter(person){
return 'hello,'+person
}
let user='tao'
console.log(greeter(user))
.ts
扩展名,但是这段代码仅仅是JS而已tsc helloworld.ts
输出结果为一个helloworld.js
文件,它包含了和输入文件中相同的JS代码
在命令行上,通过Node.js运行这段代码:
node helloworld.js
控制台输出:
hello,tao
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'.
firstName
和lastName
字段的对象。在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))
boolean
let isDone:boolean=false
isDone=true
//isDone=2 //error
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`
null
和undefined
是所有类型的子类型。let u:undefined=undefined
let u:null=null
//第一种定义数组:直接在元素类型后面加[]
let list1:number[]=[1,2,3]
//第二种定义数组
let list2:Array<number>=[1,2,3]
各数组元素不必相同
。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
let notSure:any=4
notSure='maybe a string'
notSure=false
在对现有代码进行改写的时候,any
类型是十分有用的,它允许在编译时可选择地包含或移除检查。并且当只知道一部分数据的类型时,any
类型也是有用的:
let list:any[]=[1,true,'free']
list[1]=100
void
类型像是与any
类型相反,它表示没有任何类型
。当一个函数没有返回值时,通常会见到其返回值类型的是void
:/*表示没有任何类型,一般用来说明函数的返回值不能是undefined和null之外的值*/
function fn():void{
console.log('fn()')
//return undefined
//return null
//return 1 //error
}
声明一个void
类型的变量没有什么用,因为你只能为它赋予undefined
和null
:
let unusable:void=undefined
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))
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))
/*定义变量时赋值,推断为对应的类型*/
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
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/
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的两个子类:Horse
和Snake
。派生类包含一个构造函数,它必须调用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
关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。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
限定一个参数属性会声明并初始化一个私有成员;对于public
和protected
来说也是一样。
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()
//命名函数
function add(x,y){
return x+y
}
//匿名函数
let myAdd=function(x,y){
return x+y
}
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 }
null
和undefined
作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。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())
arguments
来访问所有传入的参数。...
)后面给定的名字,可以在函数体内使用这个数组。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
,创建一个包含count
个value
的数组不用泛型的话,这个函数可能是下面这样: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中通过标签引入
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()
1.了解相关信息
2.性能提升
3.新增特性
Composition(组合)API
setup
ref和reactive
computed和watch
新的生命周期函数
provide与inject
…
新组件
Fragment-文档碎片
Teleport-瞬移组件的位置
Suspense-异步加载组件的loading界面
其他API更新
全局API的修改
将原来的全局API转移到应用对象
模版语法变化
## 安装或者升级
npm install -g @vue/cli
## 保证vue cli版本在4.5.0以上
vue --version
## 创建项目
vue create my-project
npm init vite-app <project-name>
cd <project-name>
npm install
npm run sev
<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>
<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的响应式
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 与 Reflecttitle>
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>
this.$attrs
this.$slots
this.$emit
<template>
<h2>Apph2>
<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>
<template>
<h2>Apph2>
<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>Apph2>
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>Apph2>
<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>
<template>
<h2>Apph2>
<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>