setup函数
setup 函数是一个新的组件选项, 作为组件中 compositionAPI 的起点
从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
setup 中不能使用 this, this 指向 undefined
在模版中需要使用的数据和函数,需要在 setup 返回。
reactive函数
1.setup需要有返回值,只有返回的值才能在模板中使用
2.默认普通的数据,不是响应式的
作用:
通常是用来自定义响应式对象数据
ref函数
reactive处理的数据,必须是复杂类型,如果是简单类型无法处理成响应式,所以有ref函数
作用:
对传入的数据(一般简单数据类型),包裹一层对象,转换成响应式
-
ref函数接收一个的值,返回一个ref响应式对象,有唯一的属性value,指向该初始值
-
在setup函数中,通过ref对象的value属性,可以访问到值
-
在模块中,ref属性会自动解套,不需要额外的value
-
ref函数也支持传入复杂类型,传入复杂类型,也会做响应式处理
ref和reactive的最佳属于方式
明确的对象,明确的属性,用reactive,其他用ref
ref函数的作用:处理响应式数据 使用ref处理的数据在setup中需要value
reactive的作用:处理复杂数据类型的响应式
reactive函数
返回普通对象的响应式副本 参数为需要定义为响应式数据的数据对象,可以理解为相当于vue2中的data
如果希望直接使用data中的属性,一般我们会用es6的解构赋值,但直接将data解构,那么data中的数据就不再具有响应式的特性
setup() { const data = reactive({ name: 'zhangsan', age: 0 }) return { data // 组件中使用data中的属性时需要使用data[key]的方式使用,例如组件中使用name应该写成data.name } }
unref函数
参数是ref,则返回参数的value,否则直接返回参数本身
const count = ref(2) unref(count) // 2 const str = '111' unref(str) // '111'
toRefs函数
讲响应式对象转换为普通对象,其中结果对象的每个property都是指向原始对象相应property的ref,那么再将结果对象结构赋值的时候,属性的响应式特性会保留
setup() { const data = reactive({ name: 'zhangsan', age: 0 }) return { ...toRefs(data) // 组件中可以直接使用data中的属性,且为响应式 } }
toRef函数
可以用来为源响应式对象的某个property新创建一个ref。ref可以被传递,它会保持对其源property的响应式连接
setup() { const data = reactive({ num: 1 }) const count = toRef(data, 'num') const changeCount = () => { count.value += 1 } console.log(data.num) // 2 return { count // 组件中可以直接使用count,且为响应式 } }
toRaw和markRaw方法
/* ---toRaw-- toRaw() 作用是将一个由 reactive 或 readonly 生成的响应式对象转为普通对象,作用类似于返回最原始的对象 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。 person是一个响应式对象,通过toRaw(person)得到的user,是一个普通对象,不再是一个响应式对象。 更改user.age的值时,页面不会更新。 只有更改person.age的值时,页面才会更新。 */ var person = reactive({ name: "long", age: 23, }); var user = toRaw(person); var change = () => { user.age++; console.log(user) }; return { user, person, change, }; /* ---markRaw--- markRaw 作用是标记一个对象,使其永远不会再成为响应式对象 person是一个响应式对象, 通过person.likes=likes,往person中添加的属性,那么这个新添加的属性likes也会是响应式的。 如果通过person.likes= markRaw(likes),往person中添加的属性,那么likes被标记为不是响应式的,那么这个新添加的属性就不是响应式的,更改它的值不会更新页面。 */ setup() { var person = reactive<UserInfo>({ name: "long", age: 23, }); var likes = ['吃饭','睡觉']; person.likes = markRaw(likes); var change = () => { person.likes[0] += '=='; console.log(person.likes); }; return { person, change, }; },
script setup语法(*)
script setup是在单文件组件(SFC)中使用组合式API的编译时语法糖 相比于普通的script语法更加简洁 要使用这个语法,需要将 setup attribute 添加到 <script> 代码块上: <script setup> console.log('hello script setup') </script> 顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用 <template> <div> <h3>根组件</h3> <div>点击次数:{{ count }}</div> <button @click="add">点击修改</button> </div> </template> <script setup> import { ref } from 'vue' const count = ref(0) const add = () => { count.value++ } </script>
计算属性computed函数
computed函数调用时,要接收一个处理函数,处理函数中,需要返回计算属性的值
<template> <div>我今年的年纪 <input type="text" v-model="age" /></div> <div>我明年的年龄 {{ nextAge }}</div> <div>我后年的年龄 <input type="text" v-model="nextAge2" /></div> </template> <script setup> import { computed, ref } from 'vue' const age = ref(10) // 不带set的计算属性 const nextAge = computed(() => { return +age.value + 1 }) // 带set的计算属性 const nextAge2 = computed({ get() { return +age.value + 2 }, set(value) { age.value = value - 2 }, }) </script>
watch函数与wacthEffect函数
wacth函数与wacthEffect函数都是监听器,在写法和用法上有一定的区别,是同一种功能的两种不同形态,底层都一样
watch和watchEffect的对比
watch
-
watch显式指定依赖数据,依赖数据更新时执行回调函数
-
具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行(设置immediate: true时可以变为非惰性,页面首次加载就会执行)
-
监视ref定义的响应式数据时可以获取到原值
-
既要指明监视的属性,也要指明监视的回调
watchEffect
-
watchEffect自动收集依赖数据,依赖数据更新时重新执行自身
-
立即执行,没有惰性,页面的首次加载就会执行
-
无法获取到原值,只能得到变化后的值
-
不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性
watch函数
-
参数1:监视的数据源
-
参数2:回调函数
-
参数3:额外的配置
// 监听单个ref const money = ref(100) watch(money, (value, oldValue) => { console.log(value) }) // 监听多个ref const money = ref(100) const count = ref(0) watch([money, count], (value) => { console.log(value) }) // 监听ref复杂数据 const user = ref({ name: 'zs', age: 18, }) watch( user, (value) => { console.log('user变化了', value) }, { // 深度监听,,,当ref的值是一个复杂数据类型,需要深度监听 deep: true, immediate: true } ) // 监听对象的某个属性的变化 const user = ref({ name: 'zs', age: 18, }) watch( () => user.value.name, (value) => { console.log(value) } )
深度解析watch函数
watch函数有两个小坑:
-
监视reactive定义的响应式数据(该数据为一个对象,因为reactive只能定义数组或对象类型的响应式)时:oldValue无法正确获取,会强制开启深度监视,deep配置不生效。
-
监视reactive定义的响应式数据中的某个属性时,且该属性是一个对象,那么此时deep配置生效
具体的watch函数的用法在下面代码中都有所体现,注释详细
<template> <div> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点我+1</button> <hr> <h2>当前的信息为:{{msg}} </h2> <!-- 点击button拼接! --> <button @click="msg+='!'">修改数据</button> <hr> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}</h2> <button @click="person.name+='~'"> 修改姓名</button> <button @click="person.age++"> 增长年龄</button> <button @click="person.job.j1.salary++"> 增长薪资</button> </div> </template> <script> import {ref,reactive,watch,watchEffect} from 'vue' export default { name:'demo', setup(){ //数据 let sum = ref(0) let msg = ref('hello') let person = reactive({ name:'zhangsan', age:'18', job:{ j1:{ salary:20 } } }) //监视(三个参数,第一个是监视的对象,第二个是监视的回调函数,第三个是监视的配置) //情况一:监视ref所定义的一个响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum的值变化了',newValue,oldValue) },{immediate:true,deep:true}) //immediate的值为true时表示非惰性的立即执行的(默认情况下是false) //deep深层次触发(此处设置deep无意义) //情况二:监视ref所定义的多个响应式数据,写成数组的形式 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或者msg变了',newValue,oldValue) }) //情况三:监视reactive所定义的响应式数据 //若监视的是reactive定义的响应式数据,则无法正确获得oldValue //若监视的是reactive定义的响应式数据,则watch会强制开启深度监视 //我们发现改变person的任意一个属性都会被监视到 watch(person,(newValue,oldValue)=>{ console.log('person改变了',newValue,oldValue) }) //我们尝试设置deep:false,关闭深度监听(目的:改变job的值不会被watch监听到) //但是我们发现deep:false并没有生效,原因是此时watch监视的是reactive定义的响应式对象,默认强制开启了深度监听 watch(person,(newValue,oldValue)=>{ console.log('person改变了',newValue,oldValue) },{deep:false}) //情况四:监视reactive所定义的响应式数据中的某个属性 watch(()=>person.name,(newValue,oldValue)=>{ console.log('person的job改变了',newValue,oldValue) }) watch(()=>person.age,(newValue,oldValue)=>{ console.log('person的job改变了',newValue,oldValue) }) watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job改变了',newValue,oldValue) }) //从上边我们发现改变name,age都会触发监听,但是改变job不会 //这是因为name和age属性的值只是一个简单的基本类型数据, //而job属性的值是一个对象,比较深,想要监视到,就要开启深度监视,程序如下: watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job改变了',newValue,oldValue) },{deep:true})//此时job改变,会被监视到,此处的deep配置生效 //需要和情况三进行区分,此处watch监视的是reactive所定义的对象中的某个属性,而情况三watch监视的是reactive所定义的对象 //情况五:监视reactive所定义的响应式数据中的某些属性,写成数组的形式 watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{ console.log('person的name或age改变了',newValue,oldValue) }) //返回一个对象(常用) return{ sum, msg, person } } } </script>
watch取消监听
const stop1 = watch( [() => nameObj.name, () => nameObj.name], ([curName, curEng], [prevName, curEng]) => { console.log(curName, curEng, "----", prevName, curEng); setTimeout(() => { stop(); }, 5000); } );
深度解析watchEffect函数
函数用法如下代码所示,注释详细:
<template> <div> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点我+1</button> <hr> <h2>当前的信息为:{{msg}} </h2> <!-- 点击button拼接! --> <button @click="msg+='!'">修改数据</button> <hr> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}</h2> <button @click="person.name+='~'"> 修改姓名</button> <button @click="person.age++"> 增长年龄</button> <button @click="person.job.j1.salary++"> 增长薪资</button> </div> </template> <script> import {ref,reactive,watch,watchEffect} from 'vue' export default { name:'demo', setup(){ //数据 let sum = ref(0) let msg = ref('hello') let person = reactive({ name:'zhangsan', age:'18', job:{ j1:{ salary:20 } } }) //watchEffect函数内部所指定的回调中用到的数据只要发生变化,就会重新执行回调 //只有一个参数,就是回调 watchEffect(()=>{ const x1 = sum.value//因为sum是ref定义的响应式数据,需要使用.value调用 const x2 = person.age console.log('watchEffect配置的回调执行了') }) return{ sum, msg, person } } } </script>
watchEffect取消监听
const stop = watchEffect(() => { console.log(nameObj.name); setTimeout(() => { stop(); }, 5000); });
watchEffect与computed
watchEffect与computed有点像:
-
但是computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
-
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
-
computed若是值没有被使用时不会调用,但是watchEffect始终会调用一次
举例:
<template> <div> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点我+1</button> <hr> <h2>当前的信息为:{{msg}} </h2> <!-- 点击button拼接! --> <button @click="msg+='!'">修改数据</button> <hr> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}</h2> <button @click="person.name+='~'"> 修改姓名</button> <button @click="person.age++"> 增长年龄</button> <button @click="person.job.j1.salary++"> 增长薪资</button> </div> </template> <script> import {ref,reactive,watch,watchEffect, computed} from 'vue' export default { name:'demo', setup(){ //数据 let sum = ref(0) let msg = ref('hello') let person = reactive({ name:'zhangsan', age:'18', job:{ j1:{ salary:20 } } }) let person1 = reactive({ firstName:'张', lastName:'三' }) //computed //计算属性——简写(没有考虑计算属性被修改的情况) person1.fullName = computed(()=>{ //必须含有返回值 return person1.firstName+'-'+person1.lastName }) //计算属性——完整写法(考虑读和写) person1.fullName = computed({ //必须含有返回值 get(){ return person1.firstName+'-'+person1.lastName }, set(value){ const nameArr = value.split('-') person1.firstName = nameArr[0] person1.lastName = nameArr[1] } }) //watchEffect //可以不写给返回值 watchEffect(()=>{ const x1 = sum.value//因为sum是ref定义的响应式数据,需要使用.value调用 const x2 = person.age console.log('watchEffect配置的回调执行了') }) return{ sum, msg, person, person1 } } } </script>
钩子函数的作用
Vue3的生命周期
1、setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
2、onBeforeMount() : 组件挂载到节点上之前执行的函数;
3、onMounted() : 组件挂载完成后执行的函数;
4、onBeforeUpdate(): 组件更新之前执行的函数;
5、onUpdated(): 组件更新完成之后执行的函数;
6、onBeforeUnmount(): 组件卸载之前执行的函数;
7、onUnmounted(): 组件卸载完成后执行的函数;
8、onActivated(): 被包含在 <keep-alive> 中的组件,会多出两个生命周期钩子函数,被激活时执行;
9、onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
10、onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
Vue2和Vue3对比
Vue2和Vue3钩子变化不大,beforeCreate 、created 两个钩子被setup()钩子来替代
vue2 -------> vue3 beforeCreate --------> setup(()=>{}) created --------> setup(()=>{}) beforeMount --------> onBeforeMount(()=>{}) mounted --------> onMounted(()=>{}) beforeUpdate --------> onBeforeUpdate(()=>{}) updated --------> onUpdated(()=>{}) beforeDestroy --------> onBeforeUnmount(()=>{}) destroyed --------> onUnmounted(()=>{}) activated --------> onActivated(()=>{}) deactivated --------> onDeactivated(()=>{}) errorCaptured --------> onErrorCaptured(()=>{})
组件通讯
props 是实现父组件向子组件传递信息,props的数据是只读的
父组件 <template> <div class="box"> <h1>我是父组件</h1> <hr /> <Child info="我是父组件参数" :money="money"></Child> </div> </template> 子组件:需要使用到defineProps方法去接受父组件传递过来的数据 <script setup lang="ts"> //defineProps是Vue3提供方法,不需要引入直接使用 let props = defineProps(['info','money']); //数组|对象写法都可以 </script
全局总线事件:全局事件总线$bus可以实现兄弟之间的通讯
子组件1:接受子组件2传递的参数 <script setup lang="ts"> import $bus from "../../bus"; import { onMounted } from "vue"; //组件挂载完毕的时候绑定一个事件,受将接来兄弟组件传递的数据 onMounted(() => { $bus.on("money", (money) => { console.log(car); }); }); </script> 子组件2:给子组件1传递参数 <script setup lang="ts"> import $bus from '../../bus'; //点击按钮回调 const handler = ()=>{ $bus.emit('money',{money:"1个w"}); } </script>
v-model进行通信:相当于给子组件绑定props,同时也相当于绑定了自定义事件
父组件:(可以在传递多个参数) <template> <div> <h1>v-model:{{pageNo}}{{pageSize}}</h1> <hr /> <Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1> </div> </template> 子组件 <template> <div class="child2"> <button @click="handler">pageNo{{ pageNo }}</button> <button @click="$emit('update:pageSize', pageSize + 4)"> pageSize{{ pageSize }} </button> </div> </template> <script setup lang="ts"> let props = defineProps(["pageNo", "pageSize"]); let $emit = defineEmits(["update:pageNo", "update:pageSize"]); //第一个按钮的事件回调 const handler = () => { $emit("update:pageNo", props.pageNo + 3); }; </script>
useAttrs: 如果和props同时使用的话props接受了的属性,useAttrs接受不到了就。
父组件 <template> <div> <h1>useAttrs</h1> <ButtonTitle type="primary" size="small" :icon="Edit" title="编辑按钮" ></ButtonTitle > </div> </template> 子组件 <template> <div :title="title"> <el-button :="$attrs"></el-button> </div> </template> <script setup lang="ts"> import {useAttrs} from 'vue'; let $attrs = useAttrs(); let props =defineProps(['title']); //但是props接受了useAttrs方法就获取不到了 console.log($attrs); </script>
provide与inject 实现隔代之间的数据传递
//爷爷组件 <template> <div class="box"> <h1>爷爷组件{{money}}</h1> <hr /> <Child></Child> </div> </template> <script setup lang="ts"> import Child from "./Child.vue"; import { ref, provide } from "vue"; let money= ref("1000"); //祖先组件给后代组件提供数据 provide("MONEY", money); </script> //孙子组件:(可以对接受到的数据进行修改) <template> <div class="child1"> <h1>孙子组件</h1> <p>{{money}}</p> <button @click="updateMoney">更新数据</button> </div> </template> <script setup lang="ts"> import {inject} from 'vue'; //注入祖先组件提供数据 let money = inject('MONEY'); const updateMoney = ()=>{ money.value = '自行车'; } </script>