文章目录
二、常用 Composition API
问题:啥叫“组合式API”?
答案:请看官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
1.拉开序幕的setup
问题:setup函数返回值中若返回一个渲染函数,如何理解?
答案:举例说明,比如App.vue中定义h1标签,通过渲染函数就能直接把<h1>标签体的值修改为渲染函数设置的值。其中h函数就是渲染函数,这个在vue2中创建vue实例也有用到h渲染函数。
<h1>一个人的信息</h1>
setup(){
//返回一个函数(渲染函数)
return ()=> h('h1','尚硅谷')
}
注意点2:
光写setup是无法实现数据响应式更新的,需要和ref函数一起使用才生效,后面会讲解到。
2.ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数。
注意点1:
问题:输出ref函数长啥样?
答案:如图
注意点2:
以后管vue3中ref叫“引用对象”
注意点3:
响应式依然是靠“Object.defineProperty()”的get与set完成的,且ref中吧get和set放在了响应式原型属性中(而vue2中是直接放在实例对象上的),这样看起来更清爽,更干净,如图
注意点4:
总结:ref处理基础数据类型使用get 和set,ref处理引用类型使用ES6的Proxy代理对象进行获取,整体思路图如图1
举例比如:使用ref设置与修改“基础数据类型数据”,使用name.value设置新值,打印name输出结果如图2
setup(){
//数据
let name = ref('张三')
//方法
function changeInfo(){
name.value = '李四'
}
使用ref设置与修改“引用数据类型数据”,使用job.value.type而不是job.value.type.value设置新值,打印job.value输出结果如图3
setup(){
let job = ref({
type:'前端工程师',
salary:'30K'
})
//方法
function changeInfo(){
job.value.type = 'UI设计师'
job.value.salary = '60K'
}
如图1
如图2
如图3
3.reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
注意点1:
问题:reactive函数使用基本类型数据会报错
注意点2:总结:基础类型数据推荐适用ref函数,引用类型数据推荐适用reactive函数
和ref知识点的注意点4有区别,如果想使用reactive函数实现引用数据响应式,使用job
setup(){
let job = reactive({
type:'前端工程师',
salary:'30K'
})
//方法
function changeInfo(){
job.type = 'UI设计师'
job.salary = '60K'
}
如果想使用ref函数实现引用数据响应式,使用job.value
setup(){
let job = ref({
type:'前端工程师',
salary:'30K'
})
//方法
function changeInfo(){
job.value.type = 'UI设计师'
job.value.salary = '60K'
}
注意点3:
当然也可以把基础类型数据和引用类型数据封装成一个代理对象,通过reactive函数关联,使用起来也很方便,缺点是还是写了很多person.xxx重复字符串
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h3>工作种类:{{person.job.type}}</h3>
<h3>工作薪水:{{person.job.salary}}</h3>
<h3>爱好:{{person.hobby}}</h3>
<h3>测试的数据c:{{person.job.a.b.c}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
type:'前端工程师',
salary:'30K',
a:{
b:{
c:666
}
}
},
hobby:['抽烟','喝酒','烫头']
})
//方法
function changeInfo(){
person.name = '李四'
person.age = 48
person.job.type = 'UI设计师'
person.job.salary = '60K'
person.job.a.b.c = 999
person.hobby[0] = '学习'
}
//返回一个对象(常用)
return {
person,
changeInfo
}
}
}
</script>
4.Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。
- MDN文档中描述的Proxy与Reflect:
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
详情请访问这个地址:
mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
注意点1:
问题:Vue3中使用Proxy()比vue2中使用Object.defineProperty()要好,好在哪?
答案:
1)vue2中使用Object.defineProperty()针对每一个属性都要写一套方法,太重复,而Vue3中使用Proxy()写的可以针对所有属性实现正删改操作。
2)vue2中使用Object.defineProperty()只能对原有的属性做到响应式,如果是“对象.xx 或者 delete 对象.xx”新增/删除的就无法做到响应式,比如:person对象有name和age属性,我想新增age属性,person.age = 10,这样值能设置进去但无法做到响应式,除非使用this.$set或者Vue.set才能实现响应式。
而Vue3中使用Proxy()就可以做到,哪怕是用“对象.xx 或者 delete 对象.xx”方式,代理对象都会实现属性的响应式。
注意点2:
Reflect是window上的一个内置对象,通过它即可实现数据的增删改
其中://target:目标对象,prop:属性,value:修改的值
读取属性值:Reflect.get(target, prop)
设置属性值或添加新属性:Reflect.set(target, prop, value)
删除属性:Reflect.deleteProperty(target, prop)
注意点3:
ECMA这种语法想把Object上的所有东西移植到Reflect中,比如Object有defineProperty()方法,实际上Reflect中也有defineProperty()方法。
注意点4:
Object.defineProperty()不能针对同一个属性再次定义方法,代码如图1,会报错如图2,也就是Object.defineProperty()针对的属性重名了,整段代码挂掉了,报错了。如果使用Reflect代码如图3,那么就不会报错,即重复定义多次不会报错,但是只在第一次定义生效,及c值最后为3,而不是为4。
如图1
如图2
如图3
注意点5:
问题:有人可能会问Reflect定义重复属性不生效,那定义它干啥?
答案:Reflect有返回值,值为boolean类型,返回true代表设置生效,返回false代表设置不生效
注意点6:
问题:有人还谁说还是感觉使用Object.defineProperty()简单明了,重名就给我报错,代码就不往下走了多少,简单明了;而Reflect不报错我还得根据返回值reue/false去判断,多费事?
答案:Object.defineProperty()针对属性来说使用确实方便,但是针对“封装框架”来说就很恐怖,不好排查错误,只能使用try catch进行输出报错打印,而且你会发现代码中每使用一次Object.defineProperty(),就得设置try catch一次,最后你会发现你有一堆的try catch,显得代码太乱了。
总结:对于封装框架来说,使用Reflect算是相对有好一点的。
5.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
6.setup的两个注意点
-
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
注意点1:
Vue2中使用自定义事件直接用就行,而在vue3中使用自定义事件,必须写emits配置项用于声明,否则会报错如图1,吐槽一下修改方式是添加emits配置项用于声明,当然不写也不会报错,估计后续vue3版本迭代会移除吧。
如图1
注意点2:
推荐使用插槽的时候最好使用关键字<template v-slot:qwe>,而不是使用原先的<template slot=’qwe’>
注意点3:
setup中this是underfine,所以使用普通函数和箭头函数都可以,因为不会用到this关键字,vue2中才会一直用到this关键字。
7.计算属性与监视属性
1.computed函数
注意点1:
Vue3中写vue2的计算属性也是可以的,但是不建议混用。
注意点2:
Vue2中computed计算属性如何写
Vue3中computed计算属性如何写
案例:拼姓名字符串,同vue2计算属性案例类似
完整代码
项目目录
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
App.vue
<template>
<Demo/>
</template>
<script>
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
}
</script>
Demo.vue
<template>
<h1>一个人的信息</h1>
姓:<input type="text" v-model="person.firstName">
<br>
名:<input type="text" v-model="person.lastName">
<br>
<span>全名:{{person.fullName}}</span>
<br>
全名:<input type="text" v-model="person.fullName">
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
firstName:'张',
lastName:'三'
})
//计算属性——简写(没有考虑计算属性被修改的情况)
/* person.fullName = computed(()=>{
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
}
}
}
</script>
结果展示:
2.watch函数
Vue2中watch监视属性如何写
Vue3中watch监视属性如何写
注意点2:
Vue2中watch作为配置项执行定义一次,不能写多个watch,而vue3中就可以写多个watch配置项,比如监视多个属性变化
注意点3:
如果想实现监视多个属性可以配置数组,不用写多个watch,比如如图1,但是既然配置数组了,那我监视的多个属性如何用newValue,oldValue进行接收呢?答案看如图2,它会把监视所有属性的newValue放到一个数组中,同理可得oldValue对应的数组。
如图1
如图2
注意点4:
问题:如果我想实现深度监视或者配置immediate:true放哪里?
答案:watch是可以配置第三个参数的,如图
注意点5:
透露个小秘密,这个deep:true在vue3中有点小问题,后续会介绍并补充上。
注意点6:一个坑
如果你用reactive定义的响应式数据交给watch去监视,那么就会发现此处无法正确获取oldValue,但是你用ref定义的就不存在这个问题。举例,用reactive定义输出结果如图,会发现oldValue值不对。
let person = reactive({
name:'张三',
age:18
})
/*
情况三:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
注意点7:针对注意点6中如果你改为ref定义,会发现控制台打印监视没结果,为啥?因为ref定义的响应式对象数据,最后还是会内部调用reactive,同时ref监视的是person.value才能输出打印,如果像如下代码是不会有任何打印的,除非改成监视person.value,改之后控制台能打印,但是oldValue还是失效的。
let sum = ref(0)
let msg = ref('你好啊')
let person = ref({
name:'张三',
age:18
})
//监视错误person
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
---------------------------------------------------------------
//监视正确 person.value
watch(person.value,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
总结:
1)如果监听的是ref定义的基础属性(字符串或者数值),那么watch监听的第一个参数不要加.value,因为sum.value代表实际监听的是数值0这个参数,而0你咋监听,你监听的只能是RefImpl对象(或者说监听的是保存数据的一个结构)。
正确写法
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true})
--------------------------------------------------------------------------------------------
错误写法
watch(sum.value,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true})
2)监听整个person对象
如果使用ref定义的对象类型数据,那么watch监听的必须是person.value才正确,因为person.value实际是Proxy,也就是ref内部调用reactive封装的Proxy代理对象;而如果使用reactive定义的对象类型数据,那么watch监听的必须是person整个对象才是正确的;
思考问题:为啥监听person.value就不用开启深度监视?
答案:监听person.value 对应的是Proxy代理对象,属于内部隐式调用reactive,默认自动开启深度监视,而监听person对应的是RefImpl对象,必须手动开启深度监视
如果使用ref定义的对象类型数据
let person = ref({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//正确写法方式1:第一种监听person.value
watch(person.value,(newValue,oldValue)=>{
:console.log('person变化了',newValue,oldValue)
})
------------------------------------------------------------------------------------------
//正确写法方式2:第二种监听person,但是开启深度监视deep:true
watch(person,(newValue,oldValue)=>{
:console.log('person变化了',newValue,oldValue)
},{deep:true})
如果使用reactive定义的对象类型数据直接监视person对象就行,默认开启深度监视,所以不用设置deep:true
let person = ref({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
watch(person,(newValue,oldValue)=>{
:console.log('person变化了',newValue,oldValue)
})
3)监听person对象的某个属性
推荐用reactive封装对象类型数据,不推荐ref封装对象类型数据。
所以这里咱们考虑使用reactive定义的对象类型数据,如果监听的是person对象中的基础属性,比如监听name属性(字符串或者数值),那么监听第一个参数请写成函数
//情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的name或age变化了',newValue,oldValue)
})
如果监听的是person对象中job对象中的某个属性,那么必须设置{deep:true},否则深度监听无效
这是正确的
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
--------------------------------------------------------------------------------------------
这是错误的
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
})
注意点8:
用reactive定义的对象有多层嵌套的话,那么vue3默认开启深度监视,而vue2必须开启deep:true后才能识别深度监视。
注意点9:(和注意点12进行对比记忆)
用reactive定义的响应式对象数据,强制开启深度监视,无法关闭deep配置(前提是watch监视的是reactive定义的整个对象,如果监视的是person对象中job属性中的salary那就不好使了)
注意点10:
监视reactive定义的一个响应式数据对象中的某个属性,第一个参数必须写成函数且返回值(否则写别的无效),例如()=>person.name代表只监视name属性变化,这样写这个oldValue就是生效的。
注意点11:
问题:针对注意点10,如果我想监视多个属性呢?
注意点12:(和注意点9进行对比记忆)
如下代码如果想实现监听person.job,但是点击涨薪按钮后修改的是job里的salary发现没监听到?错误展示效果看如图:watch监视的特殊情况不生效案例.gif
watch监视的特殊情况不生效案例.gif
错误原因在于没开启deep:true深度监视
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//特殊情况
//错误写法,未配置deep:true
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
}})
------------------------------------------------------------------
//正确写法,配置deep:true
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
}},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
注意点13:
项目中使用情况三方式多些,此处oldValue是无效的,但是如果你非想使用oldValue,那么可以把person中需要监听oldValue的属性单独用ref去包裹设置,因为ref方式中oldValue是生效的。
注意点14:
总结:坑其实有3个
第1个坑:监视reactive所定义的一个响应式数据对象person,无法正确的获取oldValue,且强制开启了深度监视(deep配置无效)
第2个坑:监视person对象中的name属性,比如情况四,那么oldValue就是好使的
第3个坑:针对特殊情况,如果监视person.job,而你修改的是person.job里的嵌套属性salary,那么必须开启深度监视deep:true后才能监听到。
案例:计算求和
完整代码
项目目录
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
App.vue
<template>
<Demo/>
</template>
<script>
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
}
</script>
Demo.vue
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前的信息为:{{msg}}</h2>
<button @click="msg+='!'">修改信息</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪资:{{person.job.j1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>
<script>
import {ref,reactive,watch} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//情况一:监视ref所定义的一个响应式数据
/* watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true}) */
//情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
},{immediate:true})
/*
情况三:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)
*/
// watch(person,(newValue,oldValue)=>{
// console.log('person变化了',newValue,oldValue)
// },{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)
}) */
//特殊情况
/* watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效 */
//返回一个对象(常用)
return {
sum,
msg,
person
}
}
}
</script>
结果展示:
3.watchEffect函数
问题:watchEffect和watch区别?
答案:
1)watchEffect不告诉你它监视谁,回调中用到哪个属性我就自动监视哪个属性,跟watch函数区别是没有第一个参数
2)watchEffect默认开启立即执行属性immediate:true和深度监视deep:true
注意点2:
问题:watchEffect和computed区别?
答案:computed注重return的返回值,而watchEffect更注重过程
8.生命周期
vue2.x的生命周期
vue3.0的生命周期
注意点1:
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名,显得更加智能完全对应起来了。Vue2中感觉没对应上,而vue3中直接对应上了。
vue2中叫mounted已挂载 -> beforeDestroy销毁前 -> destroyed销毁完成 -> 已销毁
Vue3中叫mounted已挂载 -> beforeUnmount -> unmounted -> 已卸载
其中:
beforeDestroy =》 改名为 beforeUnmount
destroyed =》 改名为 unmounted
注意点2:
问题:为啥生命周期中vue3比vue2更好?
答案:vue2.x的生命周期中,创建vue实例但是没设置el挂载,那么也会向下执行2个钩子(也就是不管el挂载没挂载我都会向下执行几步),明显不太友好,如图
而vue3.0的生命周期中,先创建实例app,然后执行el挂载,都准备完毕后才会向下执行,如果el没挂载完毕,就不会向下执行了,如图
问题:Vue2生命周期中有个图绿色框,而在vue3中没有体现,vue3难道没有?
注意点4:通过组合式API的形式去使用生命周期钩子(了解即可)
Vue2中原来的写法使用基础的配置项也能实现生命周期钩子,而vue3中可以把生命周期钩子通过组合式API形式再改个名塞进setup配置项中。
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
注意点5:
问题:如图,setup配置项中绿色框中的生命周期钩子的回调函数何时执行?
答案:“再挂载之前或者挂载的前一刻”去执行,相当于在setup外面定义好函数,setup里面直接用一样。
注意点6:
结论:组合式API的生命周期钩子执行时机 要比 配置项写法快一点。当然正常只写一个就行,要么组合式API要么vue2配置项写法。
案例:假设就有人想把“组合式API”和“vue2配置项写法”写一起,那么执行顺序啥样?
项目代码
项目目录
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
App.vue
<template>
<button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
<Demo v-if="isShowDemo"/>
</template>
<script>
import {ref} from 'vue'
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup() {
let isShowDemo = ref(true)
return {isShowDemo}
}
}
</script>
Demo.vue
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'Demo',
setup(){
console.log('---setup---')
//数据
let sum = ref(0)
//通过组合式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}
},
//通过配置项的形式使用生命周期钩子
//#region
beforeCreate() {
console.log('---beforeCreate---')
},
created() {
console.log('---created---')
},
beforeMount() {
console.log('---beforeMount---')
},
mounted() {
console.log('---mounted---')
},
beforeUpdate(){
console.log('---beforeUpdate---')
},
updated() {
console.log('---updated---')
},
beforeUnmount() {
console.log('---beforeUnmount---')
},
unmounted() {
console.log('---unmounted---')
},
//#endregion
}
</script>
9.自定义hook函数
注意点1:
创建hooks文件夹,把每个模块需要封装的代码封装到一个js文件中,这样的好处是组件中看着很清爽,只管引入和调用即可,具体hook如何实现那是别人负责的,当前组件只管引入使用即可。
使用步骤:
1)封装每个模块的hook函数的js文件
2)组件中import引入hook函数
3)return中进行返回设置
4)页面模板中直接引用即可
案例:页面打印鼠标滑动坐标
项目代码
项目目录
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
App.vue
<template>
<button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
<Demo v-if="isShowDemo"/>
</template>
<script>
import {ref} from 'vue'
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup() {
let isShowDemo = ref(true)
return {isShowDemo}
}
}
</script>
usePoint.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',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
Demo.vue
<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: 'Demo',
setup(){
//数据
let sum = ref(0)
let point = usePoint()
//返回一个对象(常用)
return {sum,point}
}
}
</script>
结果展示
10.toRef函数和toRefs函数
注意点1:
toRef(person, ‘name’)最后获取的值也是等同于上一行的person.name的值,但是它做了中间环节,先转换为RefImpl引用对象,再去获取值,这样就是响应式的,而直接调用person.name获取的就是值,不是响应式。
注意点2:
问题:案例代码中有两种写法都可以实现功能,也不报错,但是写法2有个致命的问题在哪?
答案:写法2的致命问题在,初始化的时候确实读取person里的name属性值张三,但是页面用户点击按钮修改name属性值时,修改的不是person的值,而是修改ref(person.name)这个新弄出来对象的name属性值。详情错误结果展示请看图
总结:
ref(person.name)只是读取name属性值,打包成一个新的ref,页面点击按钮后修改的是如图1中绿色框ref(person.name)返回的对象的name属性值在变,而原person里的name压根没变。
而toRef(person.job.j1, ‘salary’)返回的RefImpl对象有value属性值,它偷偷通过get方法指向原person的name属性值,如图2
如图1
如图2
注意点3:
toRef只能处理对象中一个属性,而toRefs是把这个对象的所有属性创建成多个ref对象。
但是,它只作用第一层参数,打印toRefs对象如图1,使用toRefs的时候页面salary那里会报错无效果,原代码如图4,因为toRefs只把pserson对象属性的第一层进行创建多个ref对象填充到return代码块中,而salary属于person里的job里的嵌套属性,所以代码模板里得进行适当修改才能生效,如图5。这样使用toRefs的好处是,大多数属性直接模板调用即可,比如{{name}},而个别属性比如salary再写成{{job.j1.salary}}。
另外:return代码块中想使用toRefs不能写成如图2那样,要写成如图3那样;
如图1
如图2
如图3
如图4
如图5
案例:区分toRef与toRefs
项目代码
项目目录
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
App.vue
<template>
<button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
<Demo v-if="isShowDemo"/>
</template>
<script>
import {ref} from 'vue'
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup() {
let isShowDemo = ref(true)
return {isShowDemo}
}
}
</script>
Demo.vue
<template>
<h4>{{person}}</h4>
<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>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
// const name1 = person.name
// console.log('%%%',name1)
// const name2 = toRef(person,'name')
// console.log('####',name2)
const x = toRefs(person)
console.log('******',x)
//返回一个对象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
结果展示
本人其他相关文章链接
1.《vue3第二章》常用组合式 Composition API,包括setup、ref函数、reactive函数、vue3.0中的响应式原理、计算属性与监听属性
2.vue3知识点:setup
3.vue3知识点:ref函数
4.vue3知识点:reactive函数
5.vue3知识点:Vue3.0中的响应式原理和 vue2.x的响应式
6.vue3知识点:reactive对比ref
7.vue3知识点:计算属性与监视属性
8.vue3知识点:生命周期
9.vue3知识点:自定义hook函数
10.vue3知识点:toRef函数和toRefs函数
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/106133.html