【Vue3】生命周期 hook函数 toRef
目录
一、vue2生命周期钩子
二、Vue3组合式API
三、自定义hook函数
生命周期顺序:
小demo
四、toRef
总结不易~本章节对我有很大的收获,希望对你也是!!!
本章节素材已上传至Gitee:yihaohhh/我爱Vue - Gitee.comhttps://gitee.com/liu-yihao-hhh/i-love---vue/tree/master/11_src3_%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F
一、vue2生命周期钩子
我们先来回顾一下各个生命周期钩子:
App组件:
<template><button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button><Demo v-if="isShowDemo" />
</template><script>
import Demo from './components/Demo'
import {ref} from 'vue'
export default {name: 'App',components: {Demo},setup() {let isShowDemo = ref(true)return {isShowDemo}}
}
</script>
Demo组件:通过配置的形式 来使用生命周期钩子
<template><h2>当前求和为:{{ sum }}</h2><button @click="sum++">点我 + 1</button>
</template><script>
import {ref} from 'vue'
export default {name: 'DemoBox',setup() {let sum = ref(0)console.log('---setup---')return {sum,}},// 通过配置的形式 使用生命周期钩子// 创建前钩子 实例刚被创建,data 和 methods 都还未初始化beforeCreate() {console.log('---beforeCreate---')},// 创建后钩子 实例创建完成,data、methods 已经可以使用created() {console.log('---created---')},// 挂载前钩子 模板编译完成,尚未挂载到 DOMbeforeMount() {console.log('---beforeMount---')},// 挂载后钩子 DOM 挂载完成,页面已渲染mounted() {console.log('---mounted---')},// 更新前钩子 数据更新时调用,DOM 还未重新渲染beforeUpdate() {console.log('---beforeUpdate---')},// 更新后钩子 数据更改并 DOM 更新完成后调用updated() {console.log('---updated---')},// 卸载前钩子 组件即将被卸载前调用(Vue 3 中新增)beforeUnmount() {console.log('---beforeUnmount---')},// 卸载后钩子 组件卸载完成后调用(Vue 3 中新增)unmounted() {console.log('---unmounted---')},
}
</script>
可以 看到setup是在所有生命周期钩子中最先执行的,也就是在程序启动的时候,setup跟beforeCreate生命周期钩子是平级的,并且比beforeCreate生命周期钩子先执行
因为Demo组件是靠v-if来显示就是进行组件的删除和创建,可以看到组件的删除是考beforeUnmount 和 unmounted来进行操控
Vue3从Vue2演变过来,生命周期也得到了改变, Vue3为我们提供了组合式API的形式,可以写到setup里面,但是这里要注意,beforeCreate 和 created并没有为我们提供组合式API的形式
也就是说setup就相当于beforeCreate 和 created两个生命周期钩子了
本章节素材已上传至Gitee:yihaohhh/我爱Vue - Gitee.comhttps://gitee.com/liu-yihao-hhh/i-love---vue/tree/master/11_src3_%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F
二、Vue3组合式API
就是把每一个生命周期钩子都加上一个on来进行vue函数的引用,他们都是函数数据类型,他们都可以传递一个回调函数,这个回调函数都是在钩子挂载之前执行,就是相当于你在外面写生命周期一样
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
export default {name: 'DemoBox',setup() {let sum = ref(0)console.log('---setup---')// 通过组合式API形式去使用生命周期钩子onBeforeMount(() => {console.log('---onBeforeMount---')})onMounted(() => {console.log('---onMounted---')})onBeforeUpdate(() => {console.log('---onBeforeUpdate---')})onUpdated(() => {console.log('---onUpdated---')})onBeforeUnmount(() => {console.log('---onBeforeUnmount---')})onUnmounted(() => {console.log('---onUnmounted---')})return {sum,}},
如果你同时写了setup外面 和 里面两种生命周期钩子,那么on-生命周期钩子的优先级会更高一点
本章节素材已上传至Gitee:yihaohhh/我爱Vue - Gitee.comhttps://gitee.com/liu-yihao-hhh/i-love---vue/tree/master/12_src3_%E8%87%AA%E5%AE%9A%E4%B9%89hook
三、自定义hook函数
<template><h2>当前求和为:{{ sum }}</h2><button @click="sum++">点我 + 1</button><hr><h2>当前点击时鼠标的坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template><script>
import {ref, reactive, onMounted} from 'vue'
export default {name: 'DemoBox',setup() {let sum = ref(0)let point = reactive({x:0,y:0})onMounted(() => {window.addEventListener('click', (e) => {point.x = e.pageXpoint.y = e.pageYconsole.log(e.pageX, e.pageY)})}) return {sum,point}},
}
</script>
生命周期顺序:
beforeCreate → created → beforeMount → mounted
mounted钩子,页面渲染完成了,Vue 组件的 DOM 元素也出现在浏览器上了,此时你可以安全地操作它们。
由于当前组件在被销毁后,但是该组件内部的挂载的各种事件却没有被销毁,还是能够发生,所以我们就要在组件被销毁前进行事件的取消效果!
beforeUnmount组件被销毁前:
-
mounted
:演员 上台 了,可以开始表演; -
beforeUnmount
:演员 快下台 了,要把台词稿、话筒、灯光等东西收拾好。
由于这是两个新创建的函数,所以不可取分别在创建和销毁上,所以要单独分离出来同一个函数来进行创建和销毁该事件!
function savaPoint(e) {point.x = e.pageXpoint.y = e.pageYconsole.log(e.pageX, e.pageY)}// 当组件挂载(即页面加载)完毕后,给 window 绑定了一个 点击事件监听器onMounted(() => {window.addEventListener('click', savaPoint)}) // 在组件即将销毁之前,移除事件监听器;onBeforeUnmount(() => {window.removeEventListener('click', savaPoint)})
但是有没有想过,我们是否能把这个强大的功能给分离出来呢!既然我们能够用,就业想让别人也能够复用这个功能,那么就要引入hook函数了!本质是一个函数,把setup函数中使用的组合式API进行了封装。
我们在src下创建一个hooks文件夹!然后创建该功能的名字一般叫use功能名!
usePoint.js:将demo组件的功能抽离出来!做到,并且进行暴露!但是注意一定要求给返回值!
import { reactive, onMounted, onBeforeUnmount } from 'vue'
export default function () {// 实现鼠标“打点”相关的数据let point = reactive({x: 0,y: 0})// 实现鼠标“打点”相关的方法function savaPoint(e) {point.x = e.pageXpoint.y = e.pageYconsole.log(e.pageX, e.pageY)}// 当组件挂载(即页面加载)完毕后,给 window 绑定了一个 点击事件监听器onMounted(() => {window.addEventListener('click', savaPoint)})// 在组件即将销毁之前,移除事件监听器;onBeforeUnmount(() => {window.removeEventListener('click', savaPoint)})return point
}
Demo组件:来进行函数引入和接受就行!
<template><h2>当前求和为:{{ sum }}</h2><button @click="sum++">点我 + 1</button><hr><h2>当前点击时鼠标的坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template><script>
import {ref,} from 'vue'
import usePoint from '../hooks/usePoint'
export default {name: 'DemoBox',setup() {let sum = ref(0)let point = usePoint()return {sum,point}},
}
</script>
小demo
- 进行函数方法暴露的时候,如果暴露的是已经取好名字的,就需要用别人取好名字的方法{ref,reactive}
- 但是直接暴露我们自定义的hook函数,就是没有取名字的,我们就可以当场取名字,不需要加{}
-
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
本章节素材已上传至Gitee:yihaohhh/我爱Vue - Gitee.comhttps://gitee.com/liu-yihao-hhh/i-love---vue/tree/master/13_src3_toRef%E4%B8%8EtoRefs
四、toRef
toRef(想获取的对象, ‘对象中的某个属性值’)
toRef就是定义一个新的变量,来充当对象里面某个属性的本体,就是c++的&引用,这样就不会是属于新建的一个形参,不会对对象本体产生影响!
let person = reactive({name: '张三',age: 18,job: {j1: {salary: 20}}})// 这个name1 他是字符型 一个新建的常量型const name1 = person.nameconsole.log(name1)// 第一个参数:想取哪个对象// 第二个参数:对象里面的哪个属性名// 此时的name2 就是& 引用类型!const name2 = toRef(person, 'name')console.log('###', name2)
这样就可以返回我们只需要用到的对象,来在模板进行简化写法
<template><h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><h2>薪资:{{ salary }}k</h2><button @click="name+='~'">修改姓名</button><button @click="age++">增长年龄</button><button @click="salary++">长薪</button>
</template><script>
import {reactive, toRef} from 'vue'
export default {name: 'DemoBox',setup() {let person = reactive({name: '张三',age: 18,job: {j1: {salary: 20}}})// 这个name1 他是字符型 一个新建的常量型const name1 = person.nameconsole.log(name1)// 第一个参数:想取哪个对象// 第二个参数:对象里面的哪个属性名// 此时的name2 就是& 引用类型!const name2 = toRef(person, 'name')console.log('###', name2)return {name:toRef(person, 'name'),age: toRef(person, 'age'),salary:toRef(person.job.j1, 'salary')}}
}
</script>
那么问题就来了,既然toRef目的就是要做到响应式,那么为什么不直接返回ref()数据呢?
return {person,name:ref(person.name),age: ref(person.age),salary:ref(person.job.j1.salary)}
可以看到,虽然我们同样能够做到响应式的数据变化但是,原本对象里面的值却没有被改变!说明这只是创建了一个新的对象来进行返回进行响应式的!所以这里要对原本的对象进行改变还是需要用到toRef()
总结:toRef()就是引用对象,ref()就是复制对象
那么问题又来了,通过toRef()我们只是一个对象属性的引用来进行,如果有一百个,我们不可能单独写一百个toRef来进行引用对象属性吧!所以这里就引入toRefs()
const x = toRefs(person)console.log(x)
可以看到x就是通过Proxy进行操控的数据,也就是person对象本身,x就也是一个对象!
那么我们在进行返回的时候,对象里面是不能再次嵌套一个对象的!也就是通过es6语法...toRefs(person)来讲对象里面的内容进行展开,来平铺到该return语句的对象中!
但是toRefs却只能取到对象里面的每个第一层,所以访问后面的深层次对象还是需要手动进行遍历,但是访问第一层就已经很方便了!
return {...toRefs(person)}<h2>姓名:{{ name }}</h2><h2>年龄:{{ age }}</h2><h2>薪资:{{ job.j1.salary }}k</h2><button @click="name+='~'">修改姓名</button><button @click="age++">增长年龄</button><button @click="job.j1.salary++">长薪</button>
小结:
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)