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>
流程
- myRef('hello')进入 customRef
- get()得到数据(hello),页面又读取到get()的数据----上面代码例子读取两次数据
- 修改数据进入set(),然后把新值赋给value(第一次是hello)
- 修改数据后告诉vue去重新渲染解析模板,此时数据已经改到但页面不会有显示(控制台可看)
- 数据更新后跟踪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

浙公网安备 33010602011771号