1.Vue快速上手
vue3地址
8.2 Vue3优势
(1)性能的提升。
(2)源码升级。
(3)支持TypeScript。
(4)新特性。
8.1创建Vue3工程
官方文档:快速上手 | Vue.jsVue.js - 渐进式的 JavaScript 框架https://cn.vuejs.org/guide/quick-start.html
8.1.1 vue-cli创建
##1.查看vue-cli版本: vue --version vue -V ##2.如果版本在4.5.0以下升级或安装执行: npm install -g @vue/cli ##3.创建: vue create vue3_test ##4.启动: cd vue_test npm run serve
8.1.2 vite 创建
vite 官网:
Vite | 下一代的前端工具链下一代前端工具链
https://cn.vitejs.dev/weback打包模式:
vite打包模式:
##1.创建工程: npm init vite-app <project-name> ##2.进入工程目录: cd <project-name> ##3.安装依赖: npm install ##4.启动: npm run serve
8.2常用composition API
组合式API
8.2.1 setup函数
(1)概念:Vue3中的一个新的配置项,值为一个函数。
(2)setup是所有Compositon API的“表演的舞台”。
(3)组件中所用到的:数据,方法等等,均要在setup中配置。
(4)setup函数中的两种返回值:
(4.1)若返回一个对象,则对象中的属性,方法,在模板中均可以直接使用(常用)。
(4.2)若返回一个渲染函数,则可以自定义渲染内容(了解)。
(5)注意:
(5.1)尽量不要与Vue2.x混用:
(5.1.1)Vue2.x中的(data,methods,computed等等)可以访问到setup的方
法,属性。
(5.1.2)setup中不能访问到Vue2.x中的(data,methods,computed等等)。
(5.1.3)如果重名,setup优先级高。
(5.2)setup不能是一个async函数,因为返回值不再是一个return对象,而是
promise,模板看不到return中的属性
总结:setup用于替代 Vue2 中的 beforeCreate 和 created 钩子函数。setup 选项是一个函数,它在组件实例被创建之前执行,并返回一个包含状态和方法等配置信息的对象。
import { h } from "vue"; export default { name: "App", setup() { let person = { name: "张三", age: 18, }; function sayHello() { alert(`${person.name}:hello word,年龄:${person.age}`); } //返回一个对象 return { person, sayHello, }; //返回一个渲染对象 /* return ()=>{ return h('h1',person.name) } */ }, };
8.2.2 ref函数
(1)作用:用于定义一个响应式的数据。
(2)语法:const xxx=ref(initValue)。
(2.1)创建一个包含响应式数据的引用对象(reference对象)。
(2.2)js中操作数据用xxx.value操作。
(2.3)模板中读取:直接{{xxx}}。
(3)备注:
(3.1)接收基本类型:响应式还是依靠Object.defineProperty()的get与set实现。
(3.2)接收对象类型:内部使用了Vue3中的一个新函数reactive函数,底层是用Proxy
实现的。
import { ref } from "vue"; export default { name: "App", setup() { /* let person = { name: ref("张三"), age: ref(18), }; */ let person =ref({ name: "张三", age: 18 }); function changeInfo(){ console.log(person) person.value.age=25 } return { person, changeInfo }; }, };
8.2.3 reactive函数
(1)作用:定义一个响应式数据(基本数据不用他,用ref函数)。
(2)语法:const 代理对象=reactive(源对象) 接受一个对象或数组,返回一个代理对象(proxy对象)。
(3)reactive定义的响应式数据是深层次的。
(4)内部基于es的proxy实现,通过代理对象操作源对象内部的数据。
let person = reactive({ name: "张三", age: 18, hobby: [1, 2, 3, 4, 5], a: { b: { c: { d: 666, }, }, }, }); function changeInfo() { person.hobby[0]=111; } return { person, changeInfo, };
8.2.4 响应式原理
8.2.4.1 vue2中的响应式
对象类型:通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)。
数组类型:通过重写数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
问题:
(1)新增属性,删除属性,界面不会更新。(监测不到数据修改)
(2)不通过制定方法修改数组,界面不会自动更新。(监测不到数据修改)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <script> //源数据 let person = { name: "张三", age: 18, }; //模拟vue2中的响应式实现原理 let p = {}; Object.defineProperty(p, "name", { get() { console.log("有人读取了p.name属性"); return person.name; }, set(value) { console.log("有人修改了p.name属性为:", value); person.name = value; }, }); Object.defineProperty(p, "age", { get() { console.log("有人读取了p.age属性"); return person.age; }, set(value) { console.log("有人修改了p.age属性为:", value); person.age = value; }, }); </script> <body> </body> </html>
当我们创建一个响应式对象,修改其中的属性会触发相应的方法,而我们添加的属性,并不是响应式的,并非通过get和set方法操作属性,而vue2的更新dom操作就是在set方法中实现的,所以监控不到属性的新增和删除。
具体参考:1.Vue核心-CSDN博客
Vue核心的1.5小结数据代理以及1.10.5 Vue无法检测到的数据改变和解决方案。
8.2.4.2 vue3中的响应式
(1)了解Reflect(反射):
let person = { name: "张三", age: 18, }; let p={}
当出现如下代码时,会出现语法错误导致页面无法正常渲染。
Object.defineProperty(p, "name", { get() { console.log("有人读取了p.name属性"); return person.name; } }); Object.defineProperty(p, "name", { get() { console.log("有人读取了p.name属性"); return person.name; } });
当我们使用Reflect操作时,失败并不会出现错误,而是返回一个false。
const a= Reflect.defineProperty(p, "name", { get() { console.log("有人读取了p.name属性"); return person.name; } }); const b= Reflect.defineProperty(p, "name", { get() { console.log("有人读取了p.name属性"); return person.name; } }); console.log('a',a,'b',b)
a true b false
所以,当我们使用第一种方法,去操作一个属性有可能会出现异常,导致页面出现问题,而是用第二种方式去操作,会返回一个操作结果是否成功。站在代码兼容性考虑,是有必要使用Reflect来操作属性的。
(2)了解Proxy:
p=new Proxy(person,{ //读取属性调用 get(target,propName){ console.log('有人读取了p的',propName,"属性,值",person.propName) return target[propName] }, //修改,新增属性调用 set(target,propName,value){ console.log('有人修改了p的',propName,"属性,原值",person.propName,'改为:',value) target[propName]=value }, //删除属性调用 defineProperty(target,propName){ console.log('有人删除了了p的',propName,"属性") return delete target[propName] }, })
创建一个Proxy对象当我们对该对象进行属性操作时:
当我们对对象的属性进行增删改查操作时,都会被相应的监控的,并且执行的操作会在person上执行。
(3)总结:当结合了Proxy和Reflect后就形成了vue3的响应式:
Proxy(代理):拦截对象中任意属性的变化(增删改查)。
Reflect(反射):对被代理对象的属性执行操作(不会出现语法错误)。
p=new Proxy(person,{ //读取属性调用 get(target,propName){ console.log('有人读取了p的',propName,"属性,值",person.propName) return Reflect.get(target,propName) }, //修改,新增属性调用 set(target,propName,value){ console.log('有人修改了p的',propName,"属性,原值",person.propName,'改为:',value) Reflect.set(target,propName,value) }, //删除属性调用 defineProperty(target,propName){ console.log('有人删除了了p的',propName,"属性") return Reflect.defineProperty(target,propName) }, })
Proxy和Reflect的浅层理解:Proxy和Reflect-CSDN博客
8.2.5 reactive和ref
从定义角度相比:
ref:用来定义基本数据类型。
reactive:用来定义对象或数组类型数据。
从原理角度相比:
ref:通过Object.defineProperty()的get和set来实现响应式(数据劫持)。
reactive:通过Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内数据。
从使用角度相比:
ref:操作数据需要用xxx.value,读取时不需要xxx.value。
reactive:操作和读取数据都不用xxx.value
8.2.6 setup的两个注意点
(1)setup执行时机在beforeCreate之前,且只执行一次,this是undefined。
(2)setup的参数:
(2.1)props:值对象,包含:组件外传递过来的,且组件内部生命接受了的属性。
(2.2)context:上下文对象:
(2.2.1)attrs:值对象,外部传递进来,但props未接收的属性(this.$attrs)。
(2.2.2)slots:收到的插槽内容,相当于this.$slots。(最好用v-slot命名)。
(2.2.3)emit:分发自定义事件的函数(this.$emit)。
8.2.7 计算与监视属性
8.2.7.1 计算属性 computed
(1)与vue2.x的computed配置功能一致。
(2)写法:
import { computed } from "vue"; setup(props, context) { const data = reactive({ person: { name: { firstName: "张", lastName: "三", }, age: 18, }, }); //计算属性--简写--只考虑回写没考虑修改 /* data.person.fullName=computed(()=>{ return data.person.name.firstName+"--"+ data.person.name.lastName }) */ //计算属性--完整写法 data.person.fullName = computed({ get() { return data.person.name.firstName + "-" + data.person.name.lastName; }, set(value) { const nameArr = value.split("-"); data.person.name.firstName = nameArr[0]; data.person.name.lastName = nameArr[1]; }, }); return { data, }; },
8.2.7.2 监视属性 watch
与vue2中的watch功能一致。
问题:
(1)监视reactive定义的响应式数据时,oldValue无法正确获取(由于旧值和新值实际
上是同一个引用,因此旧值和新值看起来总是相同的。),且默认开启了深度监
视,无法关闭(deep配置失效)。
(2)监视reactive定义的响应式数据中的某个对象属性时,deep配置又生效了。
//情况1:监视ref定义的一个响应式数据 watch(sum,(newValue, oldValue)=>{ console.log("sum被修改了", newValue, oldValue); }) //情况2:监视ref定义的多个响应式数据 watch([sum,msg],(newValue, oldValue)=>{ console.log("sum或msg被修改了", newValue, oldValue); }) //传入第三个参数配置对象 watch(sum,(newValue, oldValue)=>{ console.log("sum被修改了", newValue, oldValue); },{immediate:true}) /* 情况3:监视reactive定义的对象 全部属性 1.reactive定义的数据无法正确获取oldValue 2.强制开启了深度监视且无法关闭(deep配置失效了) */ watch(data,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 情况4:监视reactive定义的对象 某个属性 */ watch(()=>data.age,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 情况5:监视reactive定义的对象 某些属性 */ watch([()=>data.age,()=>data.name],(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 特殊情况:监视reactive定义的对象 中的对象属性 */ watch(()=>data.job,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{deep:true})
8.2.7.2 补充
(1)监视ref定义的基本数据类型的值时,不可以用xxx.value否则无法监视到。
(2)监视ref定义的对象属性时,需要用xxx.value否则由于内存中地址未改变,无法监视到ref请求reactive生成的Proxy的代理类,也可以开深度监视来实现,监视对象属性中的内容。
8.2.7.3 wactcEffect函数
watcg:既要指明监视的属性,也要指明监视的回调。
watchEffect:不指明监视的属性,在回调中用到了哪个属性就监视哪个属性。
watchEffect优点类似于computed:
computed注重的是结果(回调函数的返回值),所以必须return。
watchEffect注重的是过程(回调的函数体),所以不用写返回值。
watch(data, (newValue, oldValue) => { console.log("person变化了", newValue, oldValue); },{immediate:true}); watchEffect(()=>{ console.log('watchEffect的回调') const x=data.age })
8.2.8 Vue3中的生命周期
Vue3.0中可以继续使用Vue2.x的生命周期钩子,但是有两个被更名:
beforeDestroy===》beforeUnmount destroyed ===》unmounted
Vue3.0也提供了Composition API形式的生命周期钩子,与Vue2.x对应关系如下:
beforeCreate ===> setup() created ===> setup() beforemount ===> onBeforeMount mounted ===> onMounted befoerUpdate ===> onBeforeUpdate updated ===> onUpdated beforeUnmount ===> onBeforeUnmount unmounted ===> onUnmounted
App:
<template> <Demo > </Demo> </template> <script> import Demo from "./componets/Demo.vue"; export default { name: "App", components: { Demo, }, setup() { }, }; </script>
Demo:
<template> <div class="container"> <!-- <h1>当前求和值为:{{ sum }}</h1> <button @click="add">点我++</button> <h1>当前求和值为:{{ msg }}</h1> <button @click="editMsg">点我修改信息</button> --> <h1>姓名:{{ data.name }}</h1> <h1>年龄:{{ data.age }}</h1> <button @click="data.name+='@'">修改姓名</button> <button @click="data.age++">点我修改年龄</button> </div> </template> <script> import { reactive, toRefs, onBeforeMount, onMounted,watch,ref } from "vue"; export default { name: "App", watch: { //简写无法深度监视 /* data(newValue,oldValue){ console.log('sum被修改了',newValue,oldValue) } */ //完整写法 /* data: { immediate: true, deep:true, handler(newValue, oldValue) { console.log("sum被修改了", newValue, oldValue); }, }, */ }, setup() { let sum=ref(0) let msg=ref('信息') const data = reactive({ name: '张三', age:18, job:{ j1:1000 } }); //情况1:监视ref定义的一个响应式数据 watch(sum,(newValue, oldValue)=>{ console.log("sum被修改了", newValue, oldValue); }) //情况2:监视ref定义的多个响应式数据 watch([sum,msg],(newValue, oldValue)=>{ console.log("sum或msg被修改了", newValue, oldValue); }) //传入第三个参数配置对象 watch(sum,(newValue, oldValue)=>{ console.log("sum被修改了", newValue, oldValue); },{immediate:true}) /* 情况3:监视reactive定义的对象 全部属性 1.reactive定义的数据无法正确获取oldValue 2.强制开启了深度监视且无法关闭(deep配置失效了) */ watch(data,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 情况4:监视reactive定义的对象 某个属性 */ watch(()=>data.age,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 情况5:监视reactive定义的对象 某些属性 */ watch([()=>data.age,()=>data.name],(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) }) /* 特殊情况:监视reactive定义的对象 中的对象属性 */ watch(()=>data.job,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{deep:true}) return { data, // add,sum,msg,editMsg }; }, }; </script> <style lang="scss" scoped> </style>
8.2.9 自定义hook函数
定义:本质是一个函数,把steup函数中使用的Composition API进行的封装。
类似于Vue2.x中的mixin。
自定义hook的优势:服用代码,让setup中的逻辑更加清楚。
新建hooks文件夹,并创建功能对应的js文件(以点击获取鼠标当前xy轴位置为例):
userPoint.js
import {
reactive,
onMounted,
onBeforeUnmount
} from "vue";
export default function () {
let point = reactive({
x: 0,
y: 0
})
function savePiont(event) {
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX)
console.log(event.pageY)
}
onMounted(() => {
window.addEventListener('click', savePiont)
}),
onBeforeUnmount(() => {
window.removeEventListener('click', savePiont)
})
return point
}
Demo.vue
<template>
<div class="container">
<h1>当前鼠标X:{{point.x}},Y:{{point.y}}</h1>
</div>
</template>
<script>
import {
reactive
} from "vue";
import userPoint from '../hook/userPoint'
export default {
name: "Demo",
watch: {},
setup() {
let point= userPoint()
//使用组合式API
return {
point
};
}
};
</script>
<style lang="scss" scoped>
</style>
8.2.10 toRef
作用:创建一个ref对象,其value指向另一个对象中的某个属性。
语法:const name=toRef(person,'name')。
应用:将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs与toRef功能一致,但是可以创建多个ref对象,已发toRefs(person)。
<template> <h1>Vue3</h1> <h2> 姓名: {{ name }}<br> 年龄:{{ person.age }}<br> 性别:{{ person.sex }}<br> 爱好:{{ person.hobby }}<br> 爱好:{{ person.job.j1.pay }}<br> <button @click="changeName">修改名字信息</button> <button @click="deleteAge">删除属性年龄</button> <button @click="addSex">添加一个属性sex</button> <button @click="changeArr">通过下标修改数组</button> <button @click="person.job.j1.pay+=100">涨薪</button> </h2> </template> <script> import { reactive,toRef,toRefs } from "vue"; export default { name: "App", setup() { let person = reactive({ name: "张三", age: 18, hobby: [1, 2, 3, 4, 5], job:{ j1:{ pay:1000 } } }); function changeName() { person.name="李四" } function addSex() { person.sex="男" } function changeArr() { person.hobby[2]=999 } function deleteAge() { delete person.age } return { person, /* name:toRef(person,'name'), pay:toRef(person.job.j1,'pay'), */ ...toRefs(person), addSex, changeArr, changeName, deleteAge, }; }, }; </script>
8.3 其他Composition API
8.3.1 shallowReactive和shallowRef
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式,不进行对象响应式的处理。
使用场景:
如果有一个对象,结构比较深,但是变化时值是外层属性变化===》shallowReactive。
如果有一个对象,后续不会修改对象属性,而是产生新的对象替换===》shallowRef。
8.3.2 readonly和shallowReadonly
readonly:让一个响应式数据变为只读的(深只读)。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
使用场景:不希望数据被修改时。
8.3.3 toRaw和markRaw
toRaw:
作用:讲一个有reactive生成的响应式对象转为普通对象。(ref的不行)
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起
页面的更新。
markRaw:
作用:标记一个对象,使其永远不会在成为响应式对象。
使用场景:
(1)如果我们声明了一个reactive的对象,后续对这个对象进行属性添加的操作,
添加上的属性默认就是响应式的,而有些值不应该设置为响应式的,例如:
异常复杂的第三方类库等等。
(2)当显然具有不可变数据源的大列表时,条过响应式转换可以提高性能。
8.3.4 customRef
作用:创建一个自定义的ref,并对其依赖项跟踪和更新出发进行显示控制(可以实现自定义的控制页面更新时机,track告诉get方法这个参数需要跟踪,triger通知vue更新dom)。
实现防抖:
<template> <h1>Vue3</h1> <h2> <input type="text" v-model="keyWords" /> {{ keyWords }} </h2> </template> <script> import { reactive, ref, customRef } from "vue"; export default { name: "App", setup() { //自定义ref function myRef(value,delay) { let timer; return customRef((track,triger) => { return { get() { console.log("---myRef里的get---",value); track()//通知vue监视这个值的改变 return value; }, set(newValue) { console.log("---myRef里的set---",newValue); //通知vue重新解析模板 clearTimeout(timer) timer= setTimeout(() => { value=newValue triger() }, delay); }, }; }); } // let keyWords=ref('hello')//使用Vue提供的ref let keyWords = myRef("hello",500); //使用自己的ref return { keyWords, }; }, }; </script>
8.3.5 provide与inject
作用:实现祖与后代组件之间的通信。
父组件中有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据。
具体写法:
祖组件:
setup() { let car=reactive({ name:'奔驰', price:400000 }) //给自己的后代组件传递数据 provide('car',car)
后代组件:
let car=inject('car') console.log("二级子组件",car) },
8.3.6 响应式数据的判断
isRef:检查一个值是否为ref对象。
isReactive:检查一个值是否是由reactive创建的响应式代理。
isReadonly:检查一个对象是否是由readonlu创建的只读代理。
isProxy:检查一个对象是否由reactive或者readonly方法创建。
8.4 Composition API的优势
8.4.1 Options API
传统的OptionsAPI(配置API)中,新增或修改一个需求,要分别在data,methods,computed里修改
8.4.2 Composition API
让相同功能的变量,函数等等更加有序的组织在一起(借助hook函数)。
8.5 新的组件
8.5.1 Fragment
在Vue2.x中,组件必须有一个跟标签。
在Vue3中,组件可以没有跟标签,内部会将多个标签包含在一个Fragment的虚拟内存中。
优势:减少标签层级,减小内存占用。
8.5.2 Teleprot
概念:Teleprot是一种能够将我们组件中的html结构移动到指定位置的技术。
<template> <div> Dialog <button @click="isShow = true">点我弹框</button> <teleport to="body"> <div class="dialog" v-show="isShow"> 弹框 内容 <button @click="isShow = false">关闭</button> </div> </teleport> </div> </template> <script> import { ref } from "vue"; export default { name: "Dialog", setup() { let isShow = ref(false); return { isShow, }; }, }; </script> <style scoped> .dialog { background-color: green; width: 300px; height: 300px; } </style>
将代码直接传送到html的body内。
8.5.3 Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验。
使用步骤:
异步引入组件:
import { defineAsyncComponent } from "vue"; const Demo = defineAsyncComponent(() => import("./componets/Demo1.vue"));
使用Supense包裹组件,并配置好default与fallback:
<Suspense> <template v-slot:default> <Demo></Demo> </template> <template v-slot:fallback> <div> <h2>加载中....</h2> </div> </template> </Suspense>
8.6 其他
8.6.1 全局API转移
Vue2.x中有序地全局API和配置。
例如:注册全局组件,注册全局指令等等
//全局组件 Vue.component('MyButton',{ data:()=>({ cont:0 }), template:'<button @click='cont++'>{{cont}}++</button>' }) //全局指令 Vue.directive('focus',{ inserted:el=>el.focus() })
Vue3对这些API做出了调整:
将全局API(Vue.xxx)调整到应用实例app上
8.6.2 其他改变
8.6.2.1 data选项
data选项始终都要被声明为一个函数。
8.6.2.2 过渡类名的更改
Vue2.x写法:
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{ opacity:1 }
8.6.2.3 移除keyCode
移除keyCode作为v-on修饰符,同时也不再支持config.keyCodes
8.6.2.4 移除native
移除native作为v-on修饰符:
父组件绑定事件:
<my-component v-on:close='dialogClose' v-on:click='dialogClick' />
子组件js声明自定义事件(不声明则被认为是js原生事件):
export default { emits:['close'] }
8.6.2.5 移除过滤器
过滤器虽然看起来很方便,但是他需要一个自定义语法,打破大括号内表达式只是JavaScript的假设,不仅有学习成本还有实现成本,建议用方法或计算属性替换过滤器。