ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

vue源码中computed和watch的解读

2022-01-01 23:03:22  阅读:164  来源: 互联网

标签:count vue return dep watch effect value 源码 let


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

标签:count,vue,return,dep,watch,effect,value,源码,let
来源: https://www.cnblogs.com/likme/p/15756371.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有