首页 » 网络编程 » Vue

vue源码中computed和watch的解读

Vue 2022-01-02

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

vue源码中computed和watch的解读

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 打包的方式下一篇:一篇文章告诉你如何实现Vue前端分页和后端分页
程序园_程序员的世界 Copyright © 2020- www.580doc.com. Some Rights Reserved.