携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 28 天,点击查看活动详情
start
- 今天学习一下 vue3中的 watch的用法
vue2中的 watch
在学习vue3的watch的配置的时候,先复习一下 vue2中 watch的用法
<script>
export default {
data() {
return {
a: 1,
b: 2,
c: {
d: 4,
},
e: 5,
f: 6,
}
},
watch: {
// 1. 侦听根级属性
a(val, oldVal) {
console.log(`new: ${val}, old: ${oldVal}`)
},
// 2. 字符串方法名称
b: 'someMethod',
// 3. 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
c: {
handler(val, oldVal) {
console.log('c changed')
},
deep: true,
},
// 4. 侦听单个嵌套属性:
'c.d': function (val, oldVal) {
// do something
},
// 5. 该回调将会在侦听开始之后立即调用
e: {
handler(val, oldVal) {
console.log('e changed')
},
immediate: true,
},
// 6. 你可以传入回调数组,它们将会被逐一调用
f: [
'handle1',
function handle2(val, oldVal) {
console.log('handle2 triggered')
},
{
handler: function handle3(val, oldVal) {
console.log('handle3 triggered')
},
/* ... */
},
],
},
methods: {
someMethod() {
console.log('b changed')
},
handle1() {
console.log('handle 1 triggered')
},
},
}
</script>
简单总结一下,
- 传入对象的形式,对象的属性名为需要监听的数据
- 属性值为一个对象,包含
handler
触发的事件;deep 是否深度监听;immediate 是否首次加载;
vue3中的watch
1.监听单个 ref:正常监听
var a = ref(0)
watch(a, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
/* 1. 修改 a 由 0 变为 1 */
// 1 0
2.监听多个 ref:数组的形式
var a = ref('0')
var b = ref('1')
watch([a, b], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
/* 1. 修改 a 由 0 变为 01 */
// ['01', 1] [0, 1]
3.直接监听 reactive:
var a = reactive({ name: '1', hobby: { game: 'cs' } })
watch(a, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
/* 1. 修改 a.name 由 1 变为 12 */
// {name: '12',hobby: { game: 'cs' }} {name: '12',hobby: { game: 'cs' }}
// !! 注意这里的 oldVal 失效了
/* 2. 修改 a.hobby.game 由 cs 变为 csgo */
// {name: '1',hobby: { game: 'csgo' }} {name: '1',hobby: { game: 'csgo' }}
// !! 注意这里默认开启了深度监听,及deep:true
4.监听 reactive的属性
var a = reactive({ name: '1', hobby: { game: 'cs' } })
watch('a.name', (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 和选项式的 watch 不一样,组合式的 watch 直接 `对象.属性 ,不生效
watch(
() => a.name,
(newVal, oldVal) => {
console.log(newVal, oldVal)
}
)
/* 1. 修改 a.name 由 1 变为 12 */
// 12 1
// 采用函数返回值的形式去实现,成功监听
watch(
() => {
return a.hobby
},
(newVal, oldVal) => {
console.log(newVal, oldVal)
}
)
/* 2. 修改 a.hobby.game 由 cs 变为 csgo */
// 无反应
watch(
() => {
return a.hobby
},
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ deep: true }
)
/* 3. 添加上配置项{ deep: true }; 修改 a.hobby.game 由 cs 变为 csgo ;*/
// {game: 'cs'} {game: 'csgo'}
// 添加上 { deep: true } 才能深度监听。
watch(
() => {
return a.hobby
},
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ immediate: true }
)
/* 4. 添加上配置项{ immediate: true }; */
// {game: 'cs'} undefined
// 立即执行
watch(
() => {
return [a.name, a.hobby]
},
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ deep: true }
)
/* 5. 修改 a.name 由 1 变为 12 */
// ['12', {game: 'cs'}] (2) ['1', {game: 'cs'}]
// 可以是 一个函数,函数的返回值是一个数组
watch(
[
() => {
return a.name
},
() => {
return a.hobby
},
],
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{ deep: true }
)
/* 6. 修改 a.name 由 1 变为 12 */
// ['12', {game: 'cs'}] (2) ['1', {game: 'cs'}]
// 也可以是 一个数组,数组中多个函数的返回值
vue3 watch的源码浅显的读一下
上面总结了一大堆,说实话,不方便记忆,我们直接在 watch 前加一个 debugger,利用浏览器的调试工具,看一看vue3中有关这个 watch的源码;
1.watch
function watch(source, cb, options) {
// 1.判断第二个参数是不是函数
if (process.env.NODE_ENV !== 'production' && !isFunction(cb)) {
warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
`supports \`watch(source, cb, options?) signature.`
)
}
// 2.执行 doWatch 这个函数,参数和我们传入 watch 相同
return doWatch(source, cb, options)
}
2.doWatch
function doWatch(
source,
cb,
{ immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ
) {
/* ...省略其他代码 */
const instance = currentInstance
let getter
let forceTrigger = false
let isMultiSource = false
// 1. 判断传入的第一个参数 是否是 ref
if (isRef(source)) {
// 1.1 如果是ref, get直接获取 .value的值
getter = () => source.value
forceTrigger = isShallow$1(source)
// 2.判断传入的第一个参数 是否是 reactive
} else if (isReactive(source)) {
// 2.1 如果是 reactive, get直接获取传入的数据
getter = () => source
// 2.2 直接传入一个响应式对象,默认开启了deep。 **验证了上述示例的 `3.直接监听 reactive:`**
deep = true
// 3. 判断传入的第一个参数 是否是数组
} else if (isArray(source)) {
// 3.1 传入数组 标识 isMultiSource有多个参数需要监听
isMultiSource = true
forceTrigger = source.some((s) => isReactive(s) || isShallow$1(s))
// 3.2 可以看到这里又对 getter做了处理,直接一个map
getter = () =>
source.map((s) => {
// 3.3 这里可以看到 分别对 ref;reactive;函数;其他;都做了处理,后续试一试大杂烩 ! (具体的函数实现暂时不细究了)
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */)
} else {
process.env.NODE_ENV !== 'production' && warnInvalidSource(s)
}
})
// 4. 判断传入的第一个参数 是否是函数
} else if (isFunction(source)) {
// 4.1 如果是函数 使用 callWithErrorHandling处理
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */)
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
3 /* WATCH_CALLBACK */,
[onCleanup]
)
}
}
// 5. 其他情况
} else {
getter = NOOP
process.env.NODE_ENV !== 'production' && warnInvalidSource(source)
}
/* ....其他逻辑处理 */
}
验证 3.3:
var a = ref(1)
var b = reactive({ name: 'bbbb' })
var c = reactive({ name: 'cccc' })
var d = reactive({ name: 'dddd', age: 18 })
watch(
() => [d.name, d.age],
function (newVal, oldVal) {
console.log(newVal, oldVal)
}
)
/*
[
"12",
{
"name":"bbbb"
},
"cccc",
[
"dddd",
18
]
]
[
1,
{
"name":"bbbb"
},
"cccc",
[
"dddd",
18
]
]
*/
第一个参数,支持已数组的形式,传递不同类型的参数。
watch源码阅读总结:
发现 vue3的 watch源码这里,会对一个参数,进行很多if判断,用来处理传入参数的不同情况;
主要支持:
- ref对象;
- reactive对象;
- 数组;
- 函数;
- 其他;
总结: vue3 watch的使用:
ref 的数据
- 单个 ref:正常传递;
- 多个 ref:传入一个数组,callback共用,newVal和oldVal也是数组;
reactive 的数据
- 传入 reactive 对象,默认深度监听,oldVal失效;
- 传入 reactive 属性:需要以函数返回值的形式返回。默认不开启深度监听,oldVal生效;
支持 传入一个数组,数组中多个函数,函数分别返回 reactive 属性; 支持 传入一个函数,函数返回一个数组,数组中包含多个reactive 属性; 不支持 直接传入 reactive 属性
多个数据
支持已数组的形式,传递不同类型的参数
其他
watchEffect()
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
怎么理解:
- 传入的一个函数,当依赖项变化的时候,重新执行改函数。
- watchEffect会在初始化的时候调用一次,与watch的immediate类似
示例代码:
var a = ref('')
watchEffect(() => {
/* 注意一下 ref需要用 .value去触发 */
console.log('a属性修改啦', a.value)
})
// a属性修改啦
a.value = a.value + '改变'
// a属性修改啦 改变
参数:
var a = ref('')
watchEffect((onInvalidate) => {
// 代码一
console.log('执行一些代码', a.value)
console.log('执行更多的代码')
// 代码二
onInvalidate(() => {
console.log(
'除了在初始运行时不被调用,我总是在【执行一些代码】之前被执行(调用)'
)
})
})
/*
执行一些代码
执行更多的代码
*/
a.value = a.value + '改变'
/*
除了在初始运行时不被调用,我总是在【执行一些代码】之前被执行(调用)
执行一些代码
执行更多的代码
*/
停止监听:
const stop = watchEffect(() => {})
// 当不再需要此侦听器时:
stop()
对比 watch 和 computed,watchEffect:
-
watch 关注监听谁,做什么;
-
computed 用到谁监听谁,注重返回结果;
-
watchEffect 用到谁监听谁,注重过程;
end
- 加油啦