《vue3第二章》常用组合式 Composition API,包括setup、ref函数、reactive函数、vue3.0中的响应式原理、计算属性与监听属性

在这里插入图片描述

二、常用 Composition API

问题:啥叫“组合式API”?

答案:请看官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

1.拉开序幕的setup

在这里插入图片描述
注意点1:

问题: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()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了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的响应式

实现原理:

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()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

6.setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

注意点1:
Vue2中使用自定义事件直接用就行,而在vue3中使用自定义事件,必须写emits配置项用于声明,否则会报错如图1,吐槽一下修改方式是添加emits配置项用于声明,当然不写也不会报错,估计后续vue3版本迭代会移除吧。
在这里插入图片描述
如图1

在这里插入图片描述
如图2

注意点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函数

在这里插入图片描述
注意点1:

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函数

在这里插入图片描述
注意点1:

问题: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没挂载完毕,就不会向下执行了,如图

在这里插入图片描述
注意点3:

问题:Vue2生命周期中有个图绿色框,而在vue3中没有体现,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有个致命的问题在哪?

写法1,使用toRef
在这里插入图片描述
写法2,使用ref
在这里插入图片描述

答案:写法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函数

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue 3的组合式API是一种新的编程模式,它使得在Vue组件可以更灵活地组织和复用逻辑。下面是对Vue 3组合式API的介绍: 1. Composition API组合式API):Vue 3引入了Composition API,它允许我们将逻辑按照功能进行组合,而不是按照生命周期钩子进行划分。这样可以更好地组织和复用代码。 2. setup函数:在Vue 3,我们需要在组件使用setup函数来定义组合式APIsetup函数在组件创建之前执行,并且接收两个参数:props和context。我们可以在setup函数定义响应式数据、计算属性、方法等。 3. reactive函数reactive函数Vue 3用来创建响应式数据的函数。我们可以使用reactive函数将普通对象转换为响应式对象,从而实现数据的双向绑定。 4. ref函数ref函数Vue 3用来创建单个响应式数据的函数。与reactive函数不同,ref函数返回一个包装过的对象,我们需要通过.value属性来访问和修改数据。 5. computed函数:computed函数用来创建计算属性。与Vue 2计算属性类似,我们可以使用computed函数来定义一个依赖其他响应式数据的属性。 6. watch函数:watch函数用来监听响应式数据的变化。我们可以使用watch函数来执行一些副作用操作,比如发送网络请求或者更新DOM。 7. 生命周期钩子:在Vue 3,生命周期钩子函数被废弃了,取而代之的是使用setup函数来处理组件的生命周期逻辑。我们可以在setup函数使用onMounted、onUpdated等函数来模拟Vue 2的生命周期钩子。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘大猫.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值