计算属性-computed
什么是计算属性: computed函数,是用来定义计算属性的,计算属性不能修改。
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性还可以依赖多个Vue 实例的数据,只要其中任一数据变化,计算属性就会重新执行,视图也会更新。
methods和computed看起来都可以实现我们的功能, 那么为什么还要多一个计算属性这个东西呢? 原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次;而方法会使用一次则调用一次,因此计算属性相对而言性能更好。
vue2:
computed:{
sum(){
return this.num1+ this.num2
}
}
vue3的简单使用:
<template>
<div>
<div>计算属性</div>
<hr>
<button @click='age=25'>点击</button>
<div>今年:{{age}}岁了</div>
<div>明年:{{nextAge}}岁了</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup () {
// 计算属性:简化模板
// 应用场景:基于已有的数据,计算一种数据
const age = ref(18)
// 计算属性基本使用
const nextAge = computed(() => {
// 回调函数必须return,结果就是计算的结果
// 如果计算属性依赖的数据发生变化,那么会重新计算
return age.value + 1
})
return { age, nextAge }
}
}
</script>
总结:Vue3中计算属性也是组合API风格
-
回调函数必须return,结果就是计算的结果
-
如果计算属性依赖的数据发生变化,那么会重新计算
-
不要在计算中中进行异步操作
高级:computed有两个方法,分别是set()和get()。
<template>
<div>
<div>计算属性</div>
<hr>
<button @click='age=25'>点击</button>
<button @click='nextAge=28'>点击修改</button>
<div>今年:{{age}}岁了</div>
<div>明年:{{nextAge}}岁了</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'App',
setup () {
// 计算属性:简化模板
// 应用场景:基于已有的数据,计算一种数据
const age = ref(18)
// // 计算属性基本使用
// const nextAge = computed(() => {
// // 回调函数必须return,结果就是计算的结果
// // 如果计算属性依赖的数据发生变化,那么会重新计算
// return age.value + 1
// })
// 修改计算属性的值
const nextAge = computed({
get () {
// 如果读取计算属性的值,默认调用get方法
return age.value + 1
},
set (v) {
// v是计算属性下传递的实参
// 如果要想修改计算属性的值,默认调用set方法
age.value = v - 1
}
})
return { age, nextAge }
}
}
</script>
<style lang="less">
</style>
总结:
-
计算属性可以直接读取或者修改
-
如果要实现计算属性的修改操作,那么computed的实参应该是对象
-
读取数据触发get方法
-
修改数据触发set方法,set函数的形参就是你赋给他的值
vue2和vue3组合式api
在vue2中如何组织代码的
,我们会在一个vue文件中methods,computed,watch,data中等等定义属性和方法,共同处理页面逻辑,
我们称这种方式为Options API
缺点: 一个功能往往需要在不同的vue配置项中定义属性和方法,比较分散
,项目小还好,清晰明了,但是项目大了后,一个methods中可能包含20多个方法
,你往往分不清哪个方法对应着哪个功能
vue3中的Composition API就是用来解决这个问题的
在vue3 Composition API 中,我们的代码是根据逻辑功能来组织的,一个功能所定义的所有api会放在一起(更加的高内聚,低耦合),这样做,即时项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有API,而不像vue2 Options API 中一个功能所用到的API都是分散的,需要改动功能,到处找API的过程是很费劲的
vue3之watch和watchEffect实战总结
watch
和watchEffect
都是vue3
中的监听器,但是在写法和使用上是有区别的,主要是介绍一下watch
和watchEffect
的使用方法以及他们之间的区别。
watch 的工作原理:侦听特定的数据源,并在回调函数中执行副作用。它默认是惰性的——只有当被侦听的源发生变化时才执行回调,不过,可以通过配置 immediate 为 true 来指定初始时立即执行第一次。可以通过配置 deep 为 true,来指定深度监视。
immdiate: 默认情况下,侦听器需要 data 后面值改变了才会生效,若需要侦听器一进入页面就生效,那就需要使用 immediate。 deep: 默认情况下,侦听器只会监听数据本身的改变,若要进行深度监听,那就需要使用 deep。 immediate 和 deep 配置在第三个参数对象里。
第一个参数:监听谁,第二个参数:回调函数,第三个参数:配置对象
watch监听单个数据
<template><input type="text" v-model="text1" />
</template>
<script setup>
import { ref, watch } from 'vue'
const text1 = ref('')
watch(text1, (newVal, oldVal) => {
console.log('监听单个数据', newVal, oldVal)
})
</script>
监听多个数据(初始值为空,并没有进行打印)
<template>
<input type="text" v-model="text1" placeholder="text1值" />
<hr />
<input type="text" v-model="text2" placeholder="text2值" />
</template>
<script setup>
import { ref, watch, reactive } from 'vue'
const text1 = ref('')
const text2 = ref('')
watch([text1,text2], (newValue, oldValue) => {
console.log('监听一组数据变化', newValue, oldValue)
})
// { immediate: true }
</script>
坑:
1.监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
<template>
<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>
<hr />
<input type="text" v-model="text1" />
</template>
<script>
import { reactive, watch, ref } from 'vue'
export default {
setup() {
//数据
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
const text1 = ref('')
/* 情况三:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)-不管嵌套有多深
*/
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
}, { deep: false }) //此处的deep配置无效 */
watch(text1, (newVal, oldVal) => {
console.log('监听单个数据', newVal, oldVal)
})
//返回一个对象(常用)
return {
person, text1
}
}
}
</script>
2.监视reactive定义的响应式数据中某个属性时:deep配置有效
<template>
<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>
<hr />
<input type="text" v-model="text1" />
</template>
<script>
import { reactive, watch, ref } from 'vue'
export default {
setup() {
//数据
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
const text1 = ref('')
/* 情况三:监视reactive所定义的一个响应式数据的全部属性
1.注意:此处无法正确的获取oldValue
2.注意:强制开启了深度监视(deep配置无效)-不管嵌套有多深
*/
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
}, { deep: false }) //此处的deep配置无效 */
watch(text1, (newVal, oldVal) => {
console.log('监听单个数据', newVal, oldVal)
})
watch(() => person.age, (newValue, oldValue) => {
console.log('person的age变化了', newValue, oldValue)
}, { deep: true }) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
//返回一个对象(常用)
return {
person, text1
}
}
}
</script>
使用ref定义数组时:(触发changeArr的时候,监听不到)
<template>
<button @click="changeArr">按钮</button>
</template>
<script>
import { reactive, watch, ref } from 'vue'
export default {
setup() {
const array = ref([1, 2, 3]);
const changeArr = () => {
console.log('触发');
array.value = [];
}
watch(array.value, (now, old) => {
console.log(now, old); // 触发changeArr的时候,监听不到
})
return {
array, changeArr
}
}
}
</script>
解决方案:
<template>
<button @click="changeArr">按钮</button>
</template>
<script>
import { reactive, watch, ref } from 'vue'
export default {
setup() {
const array = ref([1, 2, 3]);
const changeArr = () => {
console.log('触发');
array.value = [];
}
// watch(array.value, (now, old) => {
// console.log(now, old); // 触发changeArr的时候,监听不到
// })
watch(() => [array.value], (now, old) => {
console.log(now, old)
})
return {
array, changeArr
}
}
}
</script>
watchEffect
watchEffect 函数的特点:
-
优点:
-
会自动收集依赖,不需要手动传递侦听内容——自动侦听回调函数中使用到的响应式数据。
-
默认 immdiate 是 true,所以初始化时会立即执行。
-
-
缺点:
-
无法获得变化前的值(oldVal)。
-
watch()
是懒执行的:当数据源发生变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。
watchEffect
相当于将watch
的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch
,watchEffect
的回调函数会被立即执行(即 { immediate: true }
)
简单来说,watchEffect 是 Vue3 中的一个响应式 API,它允许你监听响应式状态的变化,并在其发生变化时触发副作用函数。这个特性非常有用,在我们需要对响应式数据进行操作的时候,我们可以在监听到变化后马上做出反应。
举例:
<template>
name: <input type="text" v-model="student.name" />
age: <input type="number" v-model="student.age" />
</template>
<script setup>
import { reactive, watchEffect } from 'vue'
const student = reactive({name: '',age: ''
})
watchEffect(() =>
{console.log('name: ',student.name, 'age: ', student.age)
})
</script>
watcheffect停止监听
<template>
<div>
<input type="text" v-model="obj.name">
<button @click="stopWatchEffect">停止监听</button>
</div>
</template>
<script>
import { reactive, watchEffect } from 'vue';
export default {
setup() {
let obj = reactive({
name: 'zs'
});
const stop = watchEffect(() => {
console.log('name:', obj.name)
})
const stopWatchEffect = () => {
console.log('停止监听')
stop();
}
return {
obj,
stopWatchEffect,
}
}
}
</script>
watchEffect的副作用
什么是副作用(side effect
),简单的说副作用就是执行某种操作,如对外部可变数据或变量的修改,外部接口的调用等。watchEffect
的回调函数就是一个副作用函数,因为我们使用watchEffect
就是侦听到依赖的变化后执行某些操作。
Vue3
的watchEffect
侦听副作用传入的函数可以接收一个 onInvalidate
函数作为入参,用来注册清理失效时的回调
当以下情况发生时,这个失效回调会被触发:
-
副作用即将重新执行时(即依赖的值改变)
-
侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听)
import { watchEffect, ref } from 'vue'
const count = ref(0)
watchEffect((onInvalidate) => {
console.log(count.value)
onInvalidate(() => {
console.log('执行了onInvalidate')
})
})
setTimeout(()=> {
count.value++
}, 1000)
上述代码打印的顺序为: 0
-> 执行了onInvalidate,最后执行
-> 1
分析:初始化时先打印count
的值0
, 然后由于定时器把count
的值更新为1
, 此时副作用即将重新执行,因此onInvalidate
的回调函数会被触发,打印执行了onInvalidate
,然后执行了副作用函数,打印count
的值1
。
import { watchEffect, ref } from 'vue'
const count = ref(0)
const stop = watchEffect((onInvalidate) => {
console.log(count.value)
onInvalidate(() => {
console.log('执行了onInvalidate')
})
})
setTimeout(()=> {
stop()
}, 1000)
上述代码:当我们显示执行stop
函数停止侦听,此时也会触发onInvalidate
的回调函数。同样,watchEffect
所在的组件被卸载时会隐式调用stop
函数停止侦听,故也能触发onInvalidate
的回调函数。
【注意】:
watchEffect
会在 Vue3 开发中大量使用,这里说几个注意点:
-
如果有多个负效应,不要粘合在一起,建议写多个
watchEffect
。
watchEffect(() => {
setTimeout(() => console.log(a.val + 1), 1000);
setTimeout(() => console.log(b.val + 1), 1000);
});
//错误的
这两个 setTimeout 是两个不相关的效应,不需要同时监听 a 和 b,分开写吧
watchEffect(() => {
setTimeout(() => console.log(a.val + 1), 1000);
});
watchEffect(() => {
setTimeout(() => console.log(b.val + 1), 1000);
});
2.watchEffect
也可以放在其他生命周期函数内
onMounted(() => {
watchEffect(() => {
// access the DOM or template refs
});
}
利用watchEffect
的非惰性执行,以及传入的onInvalidate
函数,我们可以做什么事情了?
场景:平时我们定义一个定时器,或者监听某个事件,我们需要在mounted
生命周期钩子函数内定义或者注册,然后组件销毁之前在beforeUnmount
钩子函数里清除定时器或取消监听。这样做我们的逻辑被分散在两个生命周期,不利于维护和阅读。
如果我利用watchEffect
,创造和销毁逻辑放在了一起,此时代码更加优雅易读~
总结
watch 懒执行副作用——需要手动指明侦听的内容,也要指明侦听的回调。 默认 immdiate 是 false,所以初始化时不会执行,仅在侦听的源数据变更时才执行回调。 不需要有返回值。 可以获得变化前的值(oldVal)。 watchEffect 自动收集依赖,不需要手动传递侦听内容——自动侦听回调函数中使用到的响应式数据 默认 immdiate 是 true,所以初始化时会立即执行,同时源数据变更时也会执行回调。 不需要有返回值。 无法获得变化前的值(oldVal)。 computed 注重的计算出来的值(回调函数的返回值), 所以必须要写返回值。