vue源码中computed和watch的解读

vue

免责声明

本文是通过对vue响应式computed计算属性,watch, watchEffect源码学习的一些笔记分享,会涉及到一些引用,原创不易,如商业用途谨慎转载。

computed

  • 会基于其内部的 响应式依赖 进行缓存。
  • 只在相关 响应式依赖发生改变 时 它们才会重新求值。
  • 可以在将模板中使用的常量放在计算属性中。

watch

  • 监听数据变化,并在监听回调函数中返回数据变更前后的两个值。
  • 用于在数据变化后执行 异步操作 或者开销较大的操作。

watchEffect

在 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

  1. computed -> let x = computed(()=> count.value + 3);
  2. watch -> watch(()=> count.value, (curVal, preVal) => {}, { deep, immediate })
  3. watchEffect -> let stop = watchEffect(()=> count.value + 3)

computed 

核心思路是

// 简单定义

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

// 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

watchEffect

  • stop方法的实现
  • 数组API响应式执行依赖更新
  • Vue.set的实现,数组索引加入代理中

// 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源码学习的一些笔记分享,会涉及到一些引用,出处不详,如商业用途谨慎转载。

以上是 vue源码中computed和watch的解读 的全部内容, 来源链接: utcz.com/z/376332.html

回到顶部