第一章 部分基本概念
1.1 vue3中的侦听器
这里我们主要看组合式api的用法,因为选项式的用法是和vue2的使用差不多的。
1.1.1 基本用法:
import { ref, watch } from 'vue'
const message = ref('台下无敌')
watch(message, () => {
console.log('message变化了');
})
function changeMessage() {
if (message.value === '台下无敌') {
message.value = '天下无敌'
} else {
message.value = '台下无敌'
}
}
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、
一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
const a = ref(3)
const b= ref(1)
watch(a, () => {
console.log('a变化了');
})
watch(() => (a.value + b.value), (sum) => {
console.log('a+b的值:', sum);
})
watch([a, () => b.value], ([newA, newB]) => {
console.log('数组中的值变化了', `a现在的值:${newA},b现在的值:${newB}`);
})
注意:不能直接用watch去监听响应式对象的属性,如果确实需要侦听的话,应该去侦听该属性对应的getter函数:
import { reactive, watch } from 'vue'
const obj = reactive({ counter: 1, age: 12 })
watch(() => obj.counter, () => {
console.log('counter变化了');
})
1.1.2 深层监听
默认情况下,如果我们直接对响应式对象进行侦听,那个其实所有该对象下的属性变更都会触发侦听,也就是说默认已经实现了深度侦听。
而如果我们侦听的是对象的getter函数,那么就只有被侦听的这个属性被替换时才会触发函数,但是如果这个属性返回的也是响应式对象呢,这种情况下,就只有替换了这整个对象才能触发侦听了,所以,如果我们想进一步侦听整个响应式对象里的属性的话,可以通过将侦听器的deep属性设为true来实现强制的深度侦听。
const obj = reactive({ params: { age: 12, height: 178 } })
watch(() => obj.params, (newVal, oldVal) => {
// 其实下面的newVal和oldVal是一样的,因为params是对象,除非params被整个替换掉了
console.log('params变化了', `新值:${newVal.age},旧值:${oldVal.age}`);
},
{ deep: true, immediate: false }
)
1.1.3 watchEffect()
watchEffect也可以实现侦听,往其中传入一个方法即可,watchEffect()有点类似于computed,当传入的方法中的参数改变时,就会触发该监听方法,也就是说它是自动分析能跟踪到的响应式属性,而不像watch那样必须指定要监听的数据。另外,watchEffect()初始化就会执行一次。
const obj = reactive({ params: { age: 12, height: 178 } })
const sum = ref(0)
watchEffect(() => {
sum.value = obj.params.age + 2
console.log('sum的值:', sum.value);
})
watch与watchEffect的区别(摘自vue3官网2022/8/11):
-
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
-
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
1.1.4 侦听器的回调时机
默认情况下,侦听器会在vue组件更新前执行,那么如果我们想在vue组件更新后再执行侦听器中的操作,应该怎么办呢?答案是添加flush: 'post'
选项:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
// watchEffect有更方便的写法
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
1.1.5 取消侦听器的方法
一般情况下我们不会手动的取消侦听器,因为如果是常规情况下定义的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。
但是也会有意外,就比如异步加载的侦听器就不会自动停止,这时候就需要我们手动的取消了,但是最好不要异步加载侦听器,尽量还是使用同步的方式进行定义。
取消的方式就是调用 watch 或 watchEffect 返回的函数:
const unWatch = watch(prop, () => {
console.log('值变化了')
})
unWatch()
// watchEffect同理
第二章 常见使用技巧
2.1 如何获取ref对象的类型(仅限在非setup语法糖中)
在日常开发中,我们经常会使用到组件的ref属性,例如父页面需要调用子页面的方法时,以前在vue2+js环境中,我们可以直接用this.$refs[组件ref名称]来获取子组件的实例,那么在vue3中怎样获取呢?
先说结论:
<login-account ref="accountRef" />
<script lang="ts">
import { defineComponent, ref } from 'vue'
import loginAccount from './login-account.vue'
export default defineComponent({
setup() {
const accountRef = ref<InstanceType<typeof loginAccount>>()
return {
accountRef
}
}
)}
</script>
为什么要这样做呢?从逻辑上来说,我们直接在ref的泛型里传入loginAccount不就行了吗?就像下面这样。
const accountRef = ref<loginAccount>()
这种可以吗?其实是不行的,主要原因在于loginAccount其实不是个类型,它是个对象,是由defineComponent()函数返回的对象,那么我们在父页面使用的子组件是这个对象吗?也不是,我们在父组件中引入的组件实例其实是根据这个对象生成的组件实例。其实这就像是类与对象的关系,导出的这个组件对象就像是一个类,而其他页面每次调用这个对象来创建对应的组件实例的。
那么可能我们又有疑惑了,既然这个组件对象相当于是一个类,那我就可以把它当作类型啊,就像类那样。
// Car是类,car为对象
const car = ref<Car>()
但是问题在于我们导出的这个组件对象只是像类,本质上还是个对象,是不能作为类型的,所以我们只能另辟蹊径。好在我们还有另一种方法可以获取到组件的类型,也就是上面所说的:
const accountRef = ref<InstanceType<typeof loginAccount>>()
以上的语法可以帮助我们拿到组件对象对应的类型。
注意:如果使用了setup语法糖,那么我们应该用defineExpose()来抛出需要的变量,然后再在需要的地方获取该变量,具体操作方法就不列举了
2.2 vue3中使用vuex
2.2.1 常规用法
import { createStore } from 'vuex'
import { IRootState } from './types'
export default createStore<IRootState>({
state: {
name: 'HZW'
},
mutations: {},
actions: {},
modules: {
}
})
2.2.2 分模块用法
// 模块login.ts
import { Module } from 'vuex'
import { LoginType } from './types'
const loginModule: Module<LoginType, any> = {
state() {
return {
token: '',
userInfo: {}
}
},
actions: {},
mutations: {},
getters: {}
}
export default loginModule
// 总的出口文件引用
import { createStore } from 'vuex'
import { IRootState } from './types'
import Login from './login'
export default createStore<IRootState>({
state: {
name: 'HZW'
},
mutations: {},
actions: {},
modules: {
Login
}
})