Vue 3 学习笔记
一、初识vue3
1.vue3简介
-
2020年9月18日,vue3发布3.0版本,代号大海贼时代来临,One Piece
-
特点:
-
无需构建步骤,渐进式增强静态的 HTML
-
在任何页面中作为 Web Components 嵌入
-
单页应用 (SPA)
-
全栈 / 服务端渲染 (SSR)
-
Jamstack / 静态站点生成 (SSG)
-
开发桌面端、移动端、WebGL,甚至是命令行终端中的界面
-
2.Vue3带来了什么
-
打包大小减少40%
-
初次渲染快55%,更新渲染快133%
-
内存减少54%
3.分析目录结构
- main.js中的引入
- 在模板中vue3中是可以没有根标签了,这也是比较重要的改变
- 应用实例并不只限于一个。createApp API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。
//main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import {createApp} from 'vue
import App from './App.vue
//创建应用实例对象-app(类似于之前vue2中的vm实例,但是app比vm更轻)
createApp(APP).mount('#app')
//卸载就是unmount,卸载就没了
//createApp(APP).unmount('#app')
//之前我们是这么写的,在vue3里面这一块就不支持了,会报错的,引入不到 import vue from 'vue';
new Vue({
render:(h) => h(App)
}).$mount('#app')
//多个应用实例
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
二、 常用Composition API(组合式API)
1. setup函数
-
1.理解:Vue3.0中一个新的额配置项,值为一个函数
-
2.setup是所有Composition API(组合api) “表演的舞台”
-
3.组件中所用到的:数据、方法等等,均要配置在setup中
-
4.setup函数的两种返回值:
-
若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点关注)
-
若返回一个渲染函数:则可以自定义渲染内容。(不常用)
-
-
5.注意点:
-
尽量不要与Vue2.x配置混用
- Vue2.x配置(data ,methos, computed…)中访问到setup中的属性,方法
- 但在setup中不能访问到Vue2.x配置(data.methos,compued…)
- 如果有重名,setup优先
-
setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
但如果是异步引入的方式,是可以返回一个promise的,采用
defineAsyncComponent
引入时
-
<template>
<div>
<h1>SetupMain</h1>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>地址:{{ address() }}</h2>
</div>
</template>
<script>
export default {
name: "SetupMain",
setup() {
function address() {
return "无锡。。。。"
}
return {
name: "wsy",
age: 24,
address
}
}
}
</script>
2. ref函数
- 作用:定义一个响应式的数据
- 语法
const xxx = ref(value)
- 创建见一个包含响应式数据的引用对象(reference对象)
- JS中操作数据:
xxx.value
- 模板中读取数据:不需要 .vuale,可以直接
<div>{{xxx}}</div>
使用
- 备注:
- 接收的数据可以是:基本类型,也可以是对象类型
- 基本类型的数据:响应式依旧是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型的数据:内部使用了Vue3.0中的一个新函数——
reactive
函数
3. reactive函数
- 作用:定义一个对象类型的响应式数据(基本类型不使用这个函数,用
ref
函数) - 语法:
const 代理对象 = reactive(被代理对象)
接收一个对象(或数组),返回一个代理器对象(proxy对象) - reactive定义的响应式数据是深层次的(多层数据改变都可以监测)
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
4. Vue3.0中的响应式原理
Vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性进行读取,修改进行拦截(数据劫持) -
数组类型:通过重写更新数组的一系列方法来实现拦截,(对数组的变更方法进行了包裹)
Object.defineProperty(data,'count'{ get(){}, set(){} })
-
-
存在问题:
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
-
vue2 解决方法
也可以通过调用强制更新钩子
this.$forceUpdate()
<template> <div> <h1>Test</h1> <h2>姓名:{{ person.name }}</h2> <h2 v-show="person.age">年龄:{{ person.age }}</h2> <h2 v-show="person.sex">性别:{{ person.sex }}</h2> <h2>数组:{{ list }}</h2> <button @click="fun1">删除年龄</button> <br> <button @click="fun2">新增性别</button> <br> <button @click="fun3">修改数组</button> <br> </div> </template> <script> import Vue from "vue" export default { name: "Test", data() { return { person: { name: "xiaohua", age: 18, }, list: [1, 2, 3] } }, methods: { fun1() { console.log("删除年龄成功") //这种方式是不会产生页面修改的 delete this.person.age; //可以通过这种方式强制更新 // this.$forceUpdate() // this.$delete(this.person,"age") // Vue.delete(this.person,"age") }, fun2() { console.log("新增性别成功") //这种方式是不会产生页面修改的 // this.person.sex = "女" // this.$set(this.person,"sex","女") Vue.set(this.person, "sex", "女") }, fun3() { console.log("修改数组成功") //这种方式是不会产生页面修改的 // this.list[0]=2 this.list.splice(0, 1, 2) }, } } </script>
Vue3.x的响应式
-
实现原理:
- 通过 Proxy(代理):拦截对象中任意属性的变化,包括属性值的读写,属性的添加,属性的删除等
- 通过 Reflect(反射):对源对象的属性进行操作。
-
大致实现代码
var person = {name:"xiaoli",age:12} //#region //利用Proxy var p = new Proxy(person,{ /** * @param target 源对象 (被代理的对象) * @param property 具体的某个字段 例如‘name’ * @param receiver 就是当前这个proxy实例 * */ //拦截读取属性值 get(target, property, receiver) { console.log(`有人读取了p身上的${property}属性`) /*console.log(target, property, receiver)*/ //在底层用的不是这种写法 ,而是借助于Reflect // return target[property] return Reflect.get(target,property) }, //拦截设置属性值或添加新属性 set(target, property, newValue, receiver) { console.log(`有人修改了p身上的${property}属性,新值为${newValue}`) // console.log(target, property, receiver) //return target[property] = newValue return Reflect.set(target,property,newValue) }, //拦截删除属性 deleteProperty(target, property, receiver) { console.log(`有人删除了p身上的${property}属性`) // console.log(target, property, receiver) // return delete target[property] return Reflect.deleteProperty(target,property) } }); //#endregion
效果:
5. reactivce对比ref
- 从定义数据角度对比:
- ref用来定义:基本数据类型的数据
- reactive用来定义:对象(或数组)类型数据
- 备注:ref也可以用来定义对象(或数组)类型数据,但是它内部会自动通过
reactive
转为 代理对象
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持) - reactive通过使用Proxy来实现响应式(数据劫持),并通过 Reflect操作源对象内部的数据
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
- reactive定义的数据:操作数据与读取数据:均不需要
.value
- ref定义的数据:操作数据需要
6. setup的两个注意点
- setup执行的时机
- 在beforCreate之前执行一次,this是undefined
- setup的参数
- props:值为对象,包含组件外部传递过来的,且组件内部声明接受了的属性
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但是没有在props配置中声明的属性,相当于
this.$attrs
- slots:收到的插槽内容,相当于
this.$slots
- emit:分发的自定义事件的函数,相当于
this.$emit
- attrs:值为对象,包含:组件外部传递过来,但是没有在props配置中声明的属性,相当于
7. 计算属性与监视属性
1.computed函数
-
与Vue2.x中computed配置功能一致
-
写法
import {computed, reactive, ref} from "vue"; export default { name: "ComputedComp", setup(){ let person = reactive({ firstName :"", lastName:"" }) //计算属性 简写 只读取的情况 // let fullName = computed(()=>{ // return person.firstName+"_"+person.lastName // }) //计算属性完整写法 let fullName = computed({ get(){ return person.firstName+"_"+person.lastName }, set(value){ let nameArr = value.split("_"); person.firstName = nameArr[0]; person.lastName = nameArr[1] } }) return{ fullName, person } }
2.watch函数
-
与Vue2.x中watch配置功能一直
-
两个问题
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
- 监视reactive定义的响应式数据中的某个属性(多层级)时:deep配置有效
setup() { let sum = ref(0); let msg = ref("你好") let person = reactive({name: "小张", age: 12, job: {a: {b: 1}}}) //情况一,监视ref定义的响应式数据 watch(sum,(newValue , oldValue)=>{ console.log("sum变化了",newValue,oldValue) }) //情况二,监视多个ref定义的响应式数据 watch([sum, msg], (newValue, oldValue) => { console.log("sum或msg变化了", newValue, oldValue) }) //情况三,监视reactive定义的响应式数据 /** * !!!若watch监视的是reactive定影一的响应式数据,则无法正确获得oldValue * 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue , oldValue)=>{ console.log("person内的属性发生变化了",newValue,oldValue) },{immediate:true,deep:false})//此处的deep配置不在生效 //情况四:监视reactive所定义的一个响应式数据中的某个属性 watch(()=>person.name,(newValue , oldValue)=>{ console.log("person内的name属性发生变化了",newValue,oldValue) }) //情况五:监视reactive所定义的一个响应式数据中的某些数据 watch([()=>person.name,()=>person.age],(newValue , oldValue)=>{ console.log("person内的name或age属性发生变化了",newValue,oldValue) }) //特殊情况:监视reactive所定义的一个响应式数据中的多层级数据,可以利用deep watch(() => person.job, (newValue, oldValue) => { console.log("person内的job属性发生变化了", newValue, oldValue) }, {deep: true}) return { sum, msg, person }
-
watch中的.value问题
-
监视ref定义的基本类型数据,不可以加value
-
监视ref定义的对象类型数据,需要加value
let personRef = ref({name: "小张", age: 12, job: {a: {b: 1}}}) // 监视ref定义的对象类型数据,需要加value watch(personRef.value,(newValue , oldValue)=>{ console.log("personRef变化了",newValue,oldValue) })
-
3.watchEffect函数
-
watch的语法是:既要指明监视的属性,也要指明监视的回调
-
watchEffect的语法是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就用哪个属性
-
watchEffect有点像computed
- 但computed注重重新计算出来的值(回调函数的返回值),所以必须要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调 watchEffect(()=>{ const value = sum.value; const job = person.job.a.b; console.log("watchEffect被调用了") })
8. 生命周期
1.vue 2.0 与 vue 3.0的生命周期对比
2.生命周期对应
vue 2.0 | vue 3.0 | 对应setup中组合api | 备注 |
---|---|---|---|
无 | setup | -无 | setup是3.0提出的 |
beforeCreate | beforeCreate | 被setup取代 | 创建前 |
created | created | 被setup取代 | 创建 |
beforeMount | beforeMount | onBeforeMount() | 挂载前 |
mounted | mounted | onMounted() | 挂载 |
beforeUpdate | beforeUpdate | onBeforeUpdate() | 更新前 |
updated | updated | onUpdated() | 更新 |
beforeDestroy | beforeUnmount(改名) | onBeforeUnmoun() | 卸载前(2.0的钩子被改名) |
destroy | unmounted(改名) | onUnmounted() | 卸载 |
代码实例
setup(){
let sum = ref(1)
console.log("===setup===")
onBeforeMount(()=>{
console.log("===setup 中 onBeforeMount===")
})
return{
sum
}
},
beforeCreate() {console.log("===beforeCreate===")},
created() {console.log("===created===")},
beforeMount() {console.log("===beforeMount===")},
mounted() {console.log("===mounted===")},
beforeUpdate() {console.log("===beforeUpdate===")},
updated() {console.log("===updated===")},
//销毁钩子已经被弃用,写了也没有用
beforeDestroy() {console.log("===beforeDestroy===")},
//销毁钩子已经被弃用,写了也没有用
destroyed() {console.log("===destroyed===")},
beforeUnmount() {console.log("===beforeUnmount===")},
unmounted() {console.log("===unmounted===");}
注意: 当配置项中的钩子函数与setup中的钩子函数同时存在,两个都会被触发,但是setup中的函数先被触发
9. 自定义hook函数
-
hook函数——本质是一个函数,把setup函数中使用的Composition API进行了封装
-
类似于vue2.x中的mixin
-
优点:复用代码,让setup中的逻辑更清楚易懂
-
使用
1.将需要使用的函数代码封装到一个js文件中
import {onMounted, onUnmounted, reactive} from "vue"; export default function (){ let point = reactive({x:0,y:0}) function clickPoint(event){ console.log(event.pageX,event.pageY) point.x = event.pageX; point.y = event.pageY; } onMounted(()=>{ //绑定一个打点事件 window.addEventListener("click",clickPoint) }) onUnmounted(()=>{ //移除一个打点事件 window.removeEventListener("click",clickPoint) }) return point }
2.在需要使用的vue文件中引入
<template> <h1>Hook函数</h1> <h2>point: x:{{ point.x }} y:{{ point.y }}</h2> </template> <script> import clickPoint from "@/hook/usePoint" export default { name: "HookMain", setup() { let point = clickPoint() return { point } } } </script>
10. toRef
-
作用:创建一个ref对象,其value指向另一个对象中的某个属性
-
语法:
const name = toRef(person,'name')
,const x = toRefs(person)
-
应用:要将响应式对象中的某个属性单独提供给外部使用时
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个ref对象 -
使用:
import {reactive, toRef, toRefs} from "vue"; export default { name: "RefComp", setup() { let person = reactive({name: "小张", age: 12, job: {a: {b: 1}}}) let nameRef = toRef(person, "name"); let personRef = toRefs(person); return { ...personRef,//后续使用就可以直接使用name等属性,不用写personRef person, nameRef } } }
三、 其他Composition API
1. shallowReactive 与 shallowRef函数
-
shallowReactive:值处理对象最外层属性的响应式(浅响应式)
-
shallowRef:只处理基本数据类型的响应式,不进行对象的响应处理
-
使用
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化,使用shallowReactive
- 如果有一个对象属性,后续功能不会修改对象中的属性,而是生成新的对象进行替换,使用shallowRef
-
setup() { //只会处理对象对外层的属性响应(浅响应式) // let person = shallowReactive( //值处理基本类型的响应式 不进行对象类型的响应式处理 let person = shallowRef( // let person = reactive( {name: "小张", age: 12, job: {a: {b: 1}}}) let sum = shallowRef(0) return { sum, person } }
2.readonly 与 shallowReadonly
-
readonly:让一响应式数据变为只读的(深只读)
-
shallowReadonly:让一个响应式数据变为只读的(浅只读)
-
应用场景:不希望数据被修改时
let person = reactive( {name: "小张", age: 12, job: {a: {b: 1}}}) let sum = ref(0) // person = readonly(person) person = shallowReadonly(person) sum = readonly(sum)
3.toRaw 与 markRaw
-
toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
- 作用:将一个由
-
markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:
- 有些值不应该被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
-
function fun() { //可以将一个reactive生成的对象转为普通对象 console.log(toRaw(person)) } function addCar() { const car = {name: '奔驰s450', price: 100}; //可以标记一个对象,使其永远不会再成为响应式对象 markRaw(car); person.car = car; }
4.customRef
-
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
-
案例,实现防抖效果
<template> <h1>CustomRefComp</h1> <input v-model="keyword"/> <h3>{{keyword}}</h3> </template> <script> import {customRef} from "vue"; export default { name: "CustomRefComp", setup(){ // let keyword = ref("hello") //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉vue这个value是需要被追踪的 console.log("get被触发") return value; }, set(newValue){ clearTimeout(timer) console.log("set被触发",newValue) timer = setTimeout(()=>{ value = newValue trigger() //告诉vue去更新界面 },delay) } } }) } let keyword = myRef("hello",500) return{ keyword } } }
5.provide 与 inject
-
作用:实现祖孙组件之间的通信
-
使用:父组件利用
provide
选项来提供数据,子组件利用inject
选项来使用这些数据 -
具体写法
-
祖组件中
setup(){ ...... let car = reactive({name:"奔驰s450",price:"100w"}) //为后代组件提供数据 provide("car",car); ...... }
-
后代组件中
setup(){ ...... //接收数据 const car = inject("car") return{ car } ...... }
-
-
注意: 传递的数据是一个响应式数据,所以在后代组件中修改数据,是会影响到所有使用这个数据的地方的。
6.响应式数据的判断
- isRef: 检查一个值是否为一个
ref
对象 - isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以利用hook函数更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.Teleport
-
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术 -
优点:
-
不影响其他组件html的结构
举例说明:在多层嵌套的组件结构中,我的孙组件有个弹窗组件,需要显示或隐藏,如果不使用Teleport,那么每次展示弹窗时候会影响别的组件html结构,而使用Teleport就不会影响
-
方便定位,直接把html结构直接传送走,比如案例的传送至body处或者其他处。
-
-
使用例子
<teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h1>弹窗</h1> <h2>内容:</h2> <h2>内容:</h2> <h2>内容:</h2> <button @click="isShow=false">关闭弹窗</button> </div> </div> </teleport> <style scoped> .app { background-color: #aa2222; } .mask{ width: 100%; height: 100%; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,.5); } .dialog{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 500px; background-color: #FF8A3B; padding: 10px; text-align: center; } </style>
这种方式可以将标签内写的元素移动到
to
后面的元素后面,这样方便我们去做定位,例如这个例子相对于body进行垂直水平居中
3.Suspense(后续可能更改)
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用
-
异步引入组件
import {defineAsyncComponent} from "vue"; const SusComp = defineAsyncComponent(()=>import("./components/03.新的标签/02.Suspense/SusComp.vue"))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<Suspense> 异步组件返回的时候展示自己的标签 <template v-slot:default> <SusComp></SusComp> </template> 异步组件没返回的时候展示fallback <template v-slot:fallback> <h1>请稍等</h1> </template> </Suspense>
-
-
注意:当使用异步引入组件的时候,被引入组件的setup可以返回一个promise
setup(){ let name = ref("ffff"); return new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve({name}) },1000) }) }
六、其他
1.全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上
-
2.其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
将
v-enter
与v-leave
修改为v-enter-from
和v-leave-from
-
-
移除 keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes
-
移除 v-on.native修饰符
-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { //如果这里加上clikc,则click也会被当成自定义事件 emits: ['close'] } </script>
==注意:==使用emits声明的就是自定义事件,未声明的就当做原生事件
-
-
移除 过滤器(filter)文章来源:https://www.toymoban.com/news/detail-438678.html
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。文章来源地址https://www.toymoban.com/news/detail-438678.html
到了这里,关于Vue 3.0 学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!