在 composition API中 watchEffect会在它所依赖的数据发生改变时立即执行,并且执行结果会返回一个函数,我们称它为stop函数
,可以用于停止监听数据变化,下面是示例代码演示:
const count = ref(0) // -> log 0 const stop = watchEffect(() => { console.log(count.value) }) setTimeout(()=>{ // -> log 1 count.value++ },100) // -> later stop()
下面我们来实现以上介绍的几个composition API
核心思路是
// 简单定义 let computed = (fn) => { let value; return { get value() { return value } } } // 调用 let computedValue = computed(() => count.value + 3) // 监听 watchEffect(() => { document.getElementById('computed').innerText = computedValue.value });
下面我们在此基础之上实现依赖更新的操作
let computed = (fn) => { let value; return { get value() { // 5手动执行一次依赖 value = fn() return value } } } let count = ref(1); let computedValue = computed(() => count.value + 3) function add() { document.getElementById('add').addEventListener('click',()=>{ count.value++ }) } add() watchEffect(() => { document.getElementById('text').innerText = count.value document.getElementById('computed').innerText = computedValue.value });
依赖缓存计算
呈上页面 -html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vue3 - computed</title> </head> <body> <div id="app"> result: <span id="text">0</span> <br /> computed: <span id="computed">0</span> </div> <button id="add">add</button> </body> </html>
包含了computed的实现的完整js代码。
;(function () { let active /* * @params fn -> 要执行的函数 * @params option -> 可选参数 * @return effect -> 执行watchEffect */ let effect = (fn, options = {}) => { let effect = (...args) => { try { active = effect // 避免了死循环 return fn(...args) } finally { active = null } } // 更新数据时也需要让schedular执行 effect.options = options return effect } let watchEffect = function (cb) { let runner = effect(cb) runner() } // 需要有个队列来存储各项任务 let queue = [] // 通过微任务方式去执行队列中的任务 let nextTick = (cb) => Promise.resolve().then(cb) // 将任务添加到队列 let queueJob = (job) => { if (!queue.includes(job)) { queue.push(job) nextTick(flushJobs) } } // 执行队列中的任务 let flushJobs = () => { let job while ((job = queue.shift()) !== undefined) { job() } } // 收集更多依赖 class Dep { // 依赖收集,将响应依赖添加到deps中 constructor() { this.deps = new Set() } depend() { if (active) { this.deps.add(active) } } // 通知所有依赖更新 notify() { // 将任务加到队列中 this.deps.forEach((dep) => { dep.options && dep.options.schedular && dep.options.schedular() queueJob(dep) }) } } let ref = (initValue) => { let value = initValue let dep = new Dep() return Object.defineProperty({}, 'value', { get() { dep.depend() return value }, set(newValue) { value = newValue dep.notify() } }) } let computed = (fn) => { let value let dirty = true let runner = effect(fn, { // 通过钩子函数处理dirty参数 schedular: () => { if (!dirty) { dirty = true } } }) return { get value() { if (dirty) { value = runner() // 缓存标识 dirty = false // 这里在dirty改变为false之后需要在依赖发生变化时候重置为true, } return value } } } let count = ref(1) // 同93 数据发生更新时让dirty 重置 let computedValue = computed(() => count.value + 3) function add() { document.getElementById('add').addEventListener('click', () => { count.value++ }) } add() watchEffect(() => { document.getElementById('text').innerText = count.value document.getElementById('computed').innerText = computedValue.value }) })()
// watch(()=> count.value, (curVal, preVal) => {}, { deep, immediate }) ;(function () { let active /* * @params fn -> 要执行的函数 * @params option -> 可选参数 * @return effect -> 执行watchEffect */ let effect = (fn, options = {}) => { let effect = (...args) => { try { active = effect // 避免了死循环 return fn(...args) } finally { active = null } } // 更新数据时也需要让schedular执行 effect.options = options return effect } let watchEffect = function (cb) { let runner = effect(cb) runner() } // 需要有个队列来存储各项任务 let queue = [] // 通过微任务方式去执行队列中的任务 let nextTick = (cb) => Promise.resolve().then(cb) // 将任务添加到队列 let queueJob = (job) => { if (!queue.includes(job)) { queue.push(job) nextTick(flushJobs) } } // 执行队列中的任务 let flushJobs = () => { let job while ((job = queue.shift()) !== undefined) { job() } } // 收集更多依赖 class Dep { // 依赖收集,将响应依赖添加到deps中 constructor() { this.deps = new Set() } depend() { if (active) { this.deps.add(active) } } // 通知所有依赖更新 notify() { // 将任务加到队列中 this.deps.forEach((dep) => { dep.options && dep.options.schedular && dep.options.schedular() queueJob(dep) }) } } let ref = (initValue) => { let value = initValue let dep = new Dep() return Object.defineProperty({}, 'value', { get() { dep.depend() return value }, set(newValue) { value = newValue dep.notify() } }) } let watch = (source, cb, options = {}) => { const { immediate } = options const getter = () => { return source() } let oldValue const runner = effect(getter, { schedular: () => applyCbk() }) const applyCbk = () => { let newValue = runner() if (newValue !== oldValue) { cb(newValue, oldValue) oldValue = newValue } } // 有默认值时执行回调 if (immediate) { applyCbk() } else { oldValue = runner() } } let count = ref(1) function add() { document.getElementById('add').addEventListener('click', () => { count.value++ }) } add() watch( () => count.value, (newValue, oldValue) => { console.log(newValue, oldValue) }, { immediate: true } ) })()
参数1响应式更新,参数2使用schedular执行回调,参数3 如果存在时就默认执行回调2
// let stop = watchEffect(()=> count.value + 3) ;(function () { let active /* * @params fn -> 要执行的函数 * @params option -> 可选参数 * @return effect -> 执行watchEffect */ let effect = (fn, options = {}) => { // 包裹一次effect 避免对fn的污染,保证fn纯净 let effect = (...args) => { try { active = effect // 避免了死循环 return fn(...args) } finally { active = null } } // 更新数据时也需要让schedular执行 effect.options = options // 用于反向查找 effect.deps = []; return effect } let cleanUpEffect = (effect) => { const { deps } = effect; deps.forEach(dep => dep.delete(effect)) } let watchEffect = function (cb) { let runner = effect(cb) runner() // 返回一个stop函数,清楚当前的监听 return () => { cleanUpEffect(runner) } } // 需要有个队列来存储各项任务 let queue = [] // 通过微任务方式去执行队列中的任务 let nextTick = (cb) => Promise.resolve().then(cb) // 将任务添加到队列 let queueJob = (job) => { if (!queue.includes(job)) { queue.push(job) nextTick(flushJobs) } } // 执行队列中的任务 let flushJobs = () => { let job while ((job = queue.shift()) !== undefined) { job() } } // 收集更多依赖 class Dep { // 依赖收集,将响应依赖添加到deps中 constructor() { this.deps = new Set() } depend() { if (active) { this.deps.add(active) // 添加依赖时追加当前的deps, 实现双向互通。双向索引 active.deps.push(this.deps) } } // 通知所有依赖更新 notify() { // 将任务加到队列中 this.deps.forEach((dep) => { dep.options && dep.options.schedular && dep.options.schedular() queueJob(dep) }) } } let ref = (initValue) => { let value = initValue let dep = new Dep() return Object.defineProperty({}, 'value', { get() { dep.depend() return value }, set(newValue) { value = newValue dep.notify() } }) } let count = ref(1) function add() { document.getElementById('add').addEventListener('click', () => { count.value++ }) } add() let stop = watchEffect(() => { document.getElementById('text').innerText = count.value }) setTimeout(() => { stop(); }, 3000); })()
本文是通过对vue响应式computed计算属性,watch, watchEffect源码学习的一些笔记分享,会涉及到一些引用,出处不详,如商业用途谨慎转载。