Vue3学习

Vue3新增

(1)vue3支持TypeScript

(2)CompostionAPI

  • setup配置
  • ref与reactive
  • watch与watchEffect
  • provide与inject

(3)内置API

  • Fragment
  • Teleport
  • Suspence

(4)Vue3组件中template可以没有div嵌套

vue3项目搭建

1.使用vue-cli搭建
    安装--npm i -g @vue/cli
    创建--vue create vue_project
    启动--npm run serve
2.使用vite创建
    创建-- npm init vite-app project_name
    安装依赖-- npm i
    启动--npm run dev

常用CompostionAPI

setup语法糖

1.setup是vue3的配置项,值为一个函数
2.组件中所有用到的数据和方法都要配置在setup中
3.setup函数有两种返回值:
    若返回为一个对象,则对象的属性和方法在模板中均可使用 `重点`
    若返回一个渲染函数,则自定义渲染内容
`注意`:
	(1)Vue3尽量不要与vue2配置混用
    	    -->vue2中配置(data,methods,computed...)中可以访问到setup的属性方法
	    -->setup不能访问到vue2的配置(data.methods.computed)
	    -->如果数据或方法重名,vue3的setup优先
	(2)setup不能是一个async函数,因为函数不再是return的对象,而是promise包裹的对象

`参数`
    第一个参数:props--- proxy代理的接受数据
    第二个参数:context上下文---对象
    context对象的属性	attrs: 相当于Vue2中的$attrs
			emit: 触发自定义事件,相当于$emit
			expose:
			slots: 插槽 
eg:
App.vue中
<template>
	<test msg='哈哈哈'></test>
</template>

test.vue中
import {reactive} from 'vue'
export default{
    props:['msg'],
    setup(props){
       console.log(props) //打印 Proxy({msg:'哈哈哈'})
    }
}

ref函数--将数据变成响应式

ref处理基本数据类型用的RefImpl的value,而ref处理复杂数据类型转换成代理proxy(利用了vue3的一个新函数--reactive函数)
eg:
<template>
    姓名:{{name}}
    年龄:{{age}}
  <btn @click='changeInfo'>改变信息</btn>
</template>    
setup(){
    let name=ref("LHT"),
    let age=ref(18),
    let obj=ref({
        type:'前端工程师',
        monery:'20k'
    }) 
    function changeInfo(){
        name.value="lht"
        age.value=24,
        obj.value.type='全栈工程师',
        obj.value
    }    
    return{
        name,
        age,
        changeInfo
    }
}

reactive函数---定义一个对象类型的响应式数据

	只能定义对象类型的响应式数据,基本类型不要使用reactive,reactive无法处理,ref可以接受基本类型跟对象类型
	语法:reactive(对象)或数据,返回一个代理对象proxy
eg:
setup(){
    let name=ref("LHT"),
    let age=ref(18),
    let obj=reactvie({
        type:'前端工程师',
        monery:'20k'
    })
    function changInfo(){
        obj.type='后端工程师',
        obj.monery='25k'    
    }
    return{
        name,age,obj,
        changeInfo
    }
    
}

Vue3的响应式原理

vue2原理实现:
    对象类型:通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
    数组类型:通过重写更新数组的一些列方法来实现拦截(vue2对数组的方法进行修改)
    	Object.defineProperty(data,'count',{
            get(){}
            set(){}
        })
`存在问题`
    1.新增属性,删除属性,界面不会更新
    	eg:this.obj.name='lll' //界面不会显示修改
	detele this.obj.name //界面不会显示修改
    	---通过this.$set(this.obj,'name','lll')解决
	---通过this.$delete(this.obj,'name')解决
    2.直接通过下标修改数据,界面不会自动更新
 
Vue3实现原理
    通过proxy(代理):拦截对象中任意属性的变化,包括属性值的读写,添加,删除等
    通过Reflect(反射):对被代理的对象的属性进行操作
    
     

vue2响应式原理示例

//模拟Vue2中实现响应式
let person={
	name:'张三',
	age:18
}
let p={}
Object.defineProperty(p,'name',{
    get(){//有人读取name时调用
		return person.name	
	}
    set(value){//有人修改name时调用
    	person.name=value
	}	
})
//age同上

Vue3响应式原理示例

//模拟Vue3中实现响应式
let person={
	name:'张三',
	age:18
}
const p=new Proxy(person,{
    get(target,prop){//target为读取的对象,prop为读取对象的某个属性
        //读取p的属性
        console.log(target,prop)
       // return target[prop] //写法1
        return Reflect.get(target,prop)//写法2
    },
    set(target,prop,value){
        //增加/修改p的属性的值
        //target[prop]=value//写法1
        Reflect.set(target,prop,value)//写法2
    },
    deleteProperty(target,prop){
        //删除某个属性
       return delete target[prop]//写法1
       return Reflect.deleteProperty(target,prop)//写法2 
    }
})
p.name='李四'

computed计算属性

写法:

import {reactive,computed} from 'vue'
export default{
    setup(){
       let person=reactive({
           firstName:'张',
           lastName:'三'
       }) 
       //计算属性
       //计算属性--简写 (这样修改的数据可能不是响应式的) 
       person.fullName=computed(()=>{//这里往person添加属性fullName
           return person.firstName+'-'person.lastName
       }) 
       //计算属性--完整写法(考虑读写)
       person.fullName=computed({
           get(){
               return person.firstName+'-'+person.lastName
           },
           set(value){
              const nameArr=value.split('-');
               person.firstName=nameArr[0];
               person.lastName=nameArr[1]
           }
       })
       return{
           person
       }
  }
}

watch函数

注意

  • 监视reactive定义的响应式数据时,oldVal无法被正确读取,强制开始了深度监视(deep配置失效)
  • 监视reactive定义的响应式数据中某个属性时,deep配置有效
import {ref,reactive,watch} from 'vue'
//情况1:监听一个数据---ref
setup(){
    let sum=ref(0);
    let person=reactive({
        name:'lht',
        age:18
    })
    watch(sum,(newVal,oldVal)=>{
        consolg.log('sum变化',newVal,oldVal) //newVal和oldVal为基本数据类型
    })
    return{
        ...
    }
}
//情况2:监听多个数据---ref
setup(){
    let sum=ref(0)
    let sum2=ref('hhh')
    watcj([sum,sum2],(newVal,oldVal)=>{
        console.log('sum或sum2变化',newVal,oldVal) //newVal和oldVal为数组类型
    })
    return{
        ...
    }
}
//情况3:监听reactive所定义的一个响应式数据 
setup(){
    let person=reactive({
        name:'lht',
        age:18,
        job:{
           monery{
            	much:'20k'
        	}
        }
    })
    watch(person,(newVal,oldVal)=>{
       console.log('person变化',newVal,oldVal)
    },{deep:false})//deep失效,强制深度监听
    return{
        ...
    }
}
    
//情况4:监听reactive所定义的一个响应式数据的某个属性
 ...
 	watch(()=>person.name,(newVal,oldVal)=>{
     	console.log('person的name变化',newVal,oldVal)
 	})
 ...
 
 //情况5:监听reactive所定义的一个响应式数据的某些属性
 ...
 	watch([()=>person.name,()=>person.age](newVal,oldVal)=>{
     	console.log('person的name或age变化',newVal,oldVal)
 	})
 ...
 
 //特殊情况
 ...
 	watch(person.job,(newVal,oldVal)=>{},{deep:true})
    //这里监听的是reactive所定义的对象中某个属性,所以设置deep有效
 ...

参数:(data,fn,options)

  • data为监视的数据可以是复杂类型
  • fn (newVal,oldVal) {} 监听新旧值变化的函数
  • options watch配置项

watchEffect函数

说明:

  • 既要指明监视的属性,也要指明监视的回调

  • 类似于computed ,但computed注重计算的值(回调函数的返回值),所以必须要写返回值;而watchEffect注重过程(回调函数的函数体),可以不用写返回值

用法:
let sum=ref(0);
let person=reactive({
    name:'lht',
    age:18
})
watchEffect(()=>{//watchEffect所指定的回调中用到的数据只要发生变化,就直接重新执行回调
    const x1=sum.value
    const x2=person.age
    console.log('watchEffect的回调发生变化')
})

生命周期

`1.配置项的形式的生命周期钩子`
app=Vue.createApp(App).mount('#app')
---> 初始化 事件&生命周期
   beforeCreate  
---> 初始化 注入&响应式
   created
---> 是否有template
    beforeMount
---> 创建app.$el并添加到el
    Mounted
---> 当数据发生变化前
    beforeUpdate
---> 当数据发生变化后 虚拟DOM重新渲染更新
    Updated
---> 组件解绑前
    beforeUnmout
---> 组件解绑后
    unmounted
    
`2.组合式API形式的生命周期钩子`
  app=Vue.createApp(App).mount('#app')
---> 初始化 事件&生命周期
   beforeCreate  <----------------------------->setup()
---> 初始化 注入&响应式
   created		 <----------------------------->setup()
---> 是否有template
    beforeMount  <----------------------------->onBeforeMount
---> 创建app.$el并添加到el
    Mounted		 <----------------------------->onMounted
---> 当数据发生变化前
    beforeUpdate <----------------------------->onBeforeUpdate
---> 当数据发生变化后 虚拟DOM重新渲染更新
    Updated      <----------------------------->onUpdated
---> 组件解绑前
    beforeUnmout <----------------------------->onBeforeUnmount
---> 组件解绑后
    unmounted    <----------------------------->onUnmounted

eg:
import {ref,onBeforeMount,onMounted....} form 'vue'
setup(){
   console.log("----setup--")
   let sum=ref(0)
   onBeforeMount(()=>{
      console.log("----onBeforeMount--")
   })
   onMounted(()=>{
      console.log("----onMounted--")
   })
   ...
   return{
       sum
   }
}

自定义hook函数

定义

  • 本质是一个函数,把setup函数中使用的CompostionAPI进行了封装
  • 类似Vue2的mixin
  • 优势:复用代码,逻辑清晰
1.创建hook文件夹
	-->userPoint.js `注意:一般创建hook的js文件命名用use开头,以利于辨别使用hook`)
userPoint.js
	import {reactive,onMounted,onBeforeUnmount} from 'vue'
	export default function(){
        //实现点击页面的坐标点数据
        let point=reactive({
        	x:0,
        	y:0
    	})
        //实现点击页面的坐标点方法
        function savePoint(event){
            point.x=event.pageX
            point.y=event.pageY
            console.log(event.pageX,event.pageY)
        }
	//实现点击页面的坐标点相关的生命周期钩子
	onMounted(()=>{
            window.addEventListener('click',savaPoint)
        })
	onbeforeUnmount(()=>{
            window.removeEventListener('click',savaPoint)
        })

	return point 
    }

2. 组件.vue中导入hook
	import userPoint from '@/hooks/userPoint'
	export default {
        setup(){
           let point=userPoint()
           return {point}
        }
    }

toRef函数与toRefs函数

定义

  • 创建一个ref对象,其value值指向另外一个对象中的某个属性
  • 语法:const name = toRef(person,'name')
  • 用处:将响应式对象中的某个属性单独提供给外部使用
  • toRefs与toRef,toRefs可以批量创建多个ref对象,将一个对象中多个属性转换成代理响应式对象proxyImpl ...toRefs(person)
<template>
  <div>
    <h2>{{person}}</h2>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h2>姓名:{{salary}}K</h2>
    <button @click="name+='~'">修改姓名</button>
    <button @click="age++">修改年龄</button>
    <button @click="salary++">涨薪</button>
  </div>
</template>

import {reactive,toRef} from 'vue'
export default {
  name:"toRefs",
  setup () {
    let person=reactive({
      name:'张三',
      age:18,
      job:{
        j1:{
          salary:20
        }
      }
    })
    // let name1=person.name // name1为字符串
    // let name2=toRef(person,'name') // name2为RefImpl refImpl有value属性
    return {
      person,
       // name:ref(person.name), //这里只是将person的name数据变成proxy响应式代理对象RefImpl,跟person没有关联到  
      name:toRef(person,'name'),
      age:toRef(person,'age'),
      salary:toRef(person.job.j1,'salary')
      ...toRefs(person) //将person的多个属性转换成refImpl 
      /**
      这里使用...toRefs(person)后 只是将person的一层对象转换了,所以@click="salary++"里面要改写成 @click="job.j1.salary++"
      **/
    }
  }
}

示例图片

toRef

错误示例


其他compostionAPI

shallowReactive与shallowRef

  • shallowReacive:只处理对象最外层属性的响应式
  • shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理

readonly与shallowReadonly

  • readonly:让一个响应式数据变成只读
  • shallowReadonly:让一个响应式数据变成只读(只读表面一层)
  • 应用场景:使用别人的响应式数据但不操作改变
<div>{{sum}}</div>
<div>{{job.j1.salary}}K</div>
<btn @click='sum++'>点击+1</btn>
<btn @click="job.j1.salary">涨薪</btn>
import {readonly,ref,reactive} from 'vue'
setup(){
    let sum=ref(0);
    let person=reactive({
        name:'lht',
        job:{
            j1:{
               salary:19
            }
        }
    })
    sum=readonly(sum);//将sum的数据设置为只读,@click='sum++''失效,sum一直为零
    person=shallowReadonly(person);//将person表层数据只读,但salary有效
    return{
        sum,person
    }
}

toRaw与markRaw

  • toRaw: 只能处理reactive生成响应式的数据

    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面刷新
  • markRaw

    • 作用:标记一个对象,使其永远不会再成为响应式对象

    • 应用:有些值不应被设置为响应式,例如第三方类库

      ​ 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

customRef--自定义ref

  • 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
  • 应用:防抖输入数据
  • 参数((track,trigger)=>{})
    • track 跟踪数据变化
    • trigger 通知vue去重新解析模板
<template>
  <div>
    <input type="text" v-model="keyword">
    <div>{{keyword}}</div>
    
  </div>
</template>

<script>
import {ref,customRef} from 'vue'
export default {
  setup () {
   
    //自定义ref
    function myRef(value){
      return customRef((track,trigger)=>{
        return {
          get(){ //读取数据, 读取了两次 v-modal一次  div{{keyword}}一次
            track() //追踪value变化 跟get打招呼,让他把newValue给到keyword
            return value
          },
          set(newValue){ //修改数据
            value = newValue 
            trigger() //通知vue去重新解析模板
          },
        }
      })
    }
    let keyword=myRef('hello') //自定义ref
    return {
      keyword
    }
  }
}
</script>

流程

  1. myRef('hello')进入 customRef
  2. get()得到数据(hello),页面又读取到get()的数据----上面代码例子读取两次数据
  3. 修改数据进入set(),然后把新值赋给value(第一次是hello)
  4. 修改数据后告诉vue去重新渲染解析模板,此时数据已经改到但页面不会有显示(控制台可看)
  5. 数据更新后跟踪value的变化然后告诉get(),get()再把更新的值赋到页面的值上

privde与inject

祖组件
setup(){
	...
	let car =reactive({name:'LHT'})
	provide('car',car)
}

孙组件
setup(){
	...
	const car=inject('car')
	return{
	  car
	}
	...
}

响应式数据的判断

  • isRef:判断一个值是否为一个ref对象
  • isReactive:判断一个对象是否为一个reactive对象
  • isReadonly:判断一个对象是否有readonly创建的只读代理
  • isProxy:判断一个对象是否有reactive或readonly创建的代理

CompostionAPI的优势

vue2的缺点

vue2的option中新增或修改一个需求,需要在methods,data,computed中修改,不方便

vue3的优势

集中处理相关数据方法,让相关数据整合在一起,简洁明了

Vue3新组件

1.Fragment---vue3开发者工具查看

  • 在vue2中,组件必须有个根标签
  • 在vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用

2.Teleport---将组件的html结构移动到指定位置

<teleport to="body">  //将html结构放到body中
    <h1>11111</h1>
</teleport>    

3.Suspense组件---异步引入组件

//import Test form './componets/Test'---//静态引入
import {defineAsyncComponent} form 'vue'--- //动态引入
const test=defineAsyncComponet(()=>import("./components/Test"))---//异步引入

<template> 
    <Suspense>
    	<template v-slot:default> //default
	  <h3>加载内容</h3>
	</template>
	<template v-slot:fallback> //fallback 当default加载很慢时,先加载这个fallback插槽
          <h3>加载中......</h3>
	</template>
   </Suspense> 
</template>    

Vue3中API操作变化

全局API变化

vue2全局ApI vue3全局Api
Vue.config.xxx app.config.xxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

过渡类名更改

vue2写法
.v-enter,v-leave-to{
	opacity:0;
}
.v-leave,v-enter-to{
    opacity:1;
}

Vue3写法
.v-enter-from,v-leave-to{
    opacity:0;
}
.v-leave-from,v-enter-to{
    opactiy:1
}

移除属性

  • 移除keyCode作为v-on的修饰符,不支持config.keyCodes
  • 移除v-on.native修饰符
  • 移除过滤器filter
posted @ 2022-06-14 22:13  残星落影  阅读(280)  评论(0)    收藏  举报