飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

vue源码中computed和watch的解读

时间:2022-01-02  作者:likme  
免责声明 本文是通过对vue响应式computed计算属性,watch, watchEffect源码学习的一些笔记分享,会涉及到一些引用,原创不易,如商业用途谨慎转载。

computed

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

watch

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

watchEffect

在 composition API中 watchEffect会在它所依赖的数据发生改变时立即执行,并且执行结果会返回一个函数,我们称它为stop函数

,可以用于停止监听数据变化,下面是示例代码演示:

const count = ref(0)

// -> log 0
const stop = watchEffect(() => {
	域名(域名e)
})

setTimeout(()=>{
	// -> log 1
	域名e++
},100)

// -> later
stop()

下面我们来实现以上介绍的几个composition API

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

computed 

核心思路是

// 简单定义

let computed = (fn) => {
    let value;
    return {
      get value() {
        return value
      }
    }
  }

// 调用

 let computedValue = computed(() => 域名e + 3)
 
 // 监听
 watchEffect(() => {
    域名lementById(\'computed\').innerText = 域名e
  });

下面我们在此基础之上实现依赖更新的操作

let computed = (fn) => {
  let value;
  return {
    get value() {
      // 5手动执行一次依赖
      value = fn()
      return value
    }
  }
}
let count = ref(1);
let computedValue = computed(() => 域名e + 3)

function add() {
  域名lementById(\'add\').addEventListener(\'click\',()=>{
    域名e++
  })
}

add()

watchEffect(() => {
  域名lementById(\'text\').innerText = 域名e
  域名lementById(\'computed\').innerText = 域名e
});

依赖缓存计算

 呈上页面 -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执行
    域名ons = options

    return effect
  }

  let watchEffect = function (cb) {
    let runner = effect(cb)
    runner()
  }
  // 需要有个队列来存储各项任务
  let queue = []
  // 通过微任务方式去执行队列中的任务
  let nextTick = (cb) => 域名lve().then(cb)
  // 将任务添加到队列
  let queueJob = (job) => {
    if (!域名udes(job)) {
      域名(job)
      nextTick(flushJobs)
    }
  }

  // 执行队列中的任务
  let flushJobs = () => {
    let job
    while ((job = 域名t()) !== undefined) {
      job()
    }
  }

  // 收集更多依赖
  class Dep {
    // 依赖收集,将响应依赖添加到deps中
    constructor() {
      域名 = new Set()
    }

    depend() {
      if (active) {
        域名(active)
      }
    }
    // 通知所有依赖更新
    notify() {
      // 将任务加到队列中
      域名ach((dep) => {
        域名ons && 域名dular && 域名dular()
        queueJob(dep)
      })
    }
  }

  let ref = (initValue) => {
    let value = initValue
    let dep = new Dep()

    return 域名neProperty({}, \'value\', {
      get() {
        域名nd()
        return value
      },
      set(newValue) {
        value = newValue
        域名fy()
      }
    })
  }

  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(() => 域名e + 3)

  function add() {
    域名lementById(\'add\').addEventListener(\'click\', () => {
      域名e++
    })
  }

  add()

  watchEffect(() => {
    域名lementById(\'text\').innerText = 域名e
    域名lementById(\'computed\').innerText = 域名e
  })
})()

watch

// watch(()=> 域名e, (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执行
        域名ons = options

        return effect
      }

      let watchEffect = function (cb) {
        let runner = effect(cb)
        runner()
      }
      // 需要有个队列来存储各项任务
      let queue = []
      // 通过微任务方式去执行队列中的任务
      let nextTick = (cb) => 域名lve().then(cb)
      // 将任务添加到队列
      let queueJob = (job) => {
        if (!域名udes(job)) {
          域名(job)
          nextTick(flushJobs)
        }
      }

      // 执行队列中的任务
      let flushJobs = () => {
        let job
        while ((job = 域名t()) !== undefined) {
          job()
        }
      }

      // 收集更多依赖
      class Dep {
        // 依赖收集,将响应依赖添加到deps中
        constructor() {
          域名 = new Set()
        }

        depend() {
          if (active) {
            域名(active)
          }
        }
        // 通知所有依赖更新
        notify() {
          // 将任务加到队列中
          域名ach((dep) => {
            域名ons && 域名dular && 域名dular()
            queueJob(dep)
          })
        }
      }

      let ref = (initValue) => {
        let value = initValue
        let dep = new Dep()

        return 域名neProperty({}, \'value\', {
          get() {
            域名nd()
            return value
          },
          set(newValue) {
            value = newValue
            域名fy()
          }
        })
      }

      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() {
        域名lementById(\'add\').addEventListener(\'click\', () => {
          域名e++
        })
      }

      add()

      watch(
        () => 域名e,
        (newValue, oldValue) => {
          域名(newValue, oldValue)
        },
        { immediate: true }
      )
    })()

参数1响应式更新,参数2使用schedular执行回调,参数3 如果存在时就默认执行回调2

watchEffect

  • stop方法的实现
  • 数组API响应式执行依赖更新
  • 域名的实现,数组索引加入代理中
// let stop = watchEffect(()=> 域名e + 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执行
    域名ons = options
    // 用于反向查找
    域名 = [];

    return effect
  }

  let cleanUpEffect = (effect) => {
    const { deps } = effect;
    域名ach(dep => 域名te(effect))
  }

  let watchEffect = function (cb) {
    let runner = effect(cb)
    runner()
    // 返回一个stop函数,清楚当前的监听
    return () => {
      cleanUpEffect(runner)
    }
  }
  // 需要有个队列来存储各项任务
  let queue = []
  // 通过微任务方式去执行队列中的任务
  let nextTick = (cb) => 域名lve().then(cb)
  // 将任务添加到队列
  let queueJob = (job) => {
    if (!域名udes(job)) {
      域名(job)
      nextTick(flushJobs)
    }
  }

  // 执行队列中的任务
  let flushJobs = () => {
    let job
    while ((job = 域名t()) !== undefined) {
      job()
    }
  }

  // 收集更多依赖
  class Dep {
    // 依赖收集,将响应依赖添加到deps中
    constructor() {
      域名 = new Set()
    }

    depend() {
      if (active) {
        域名(active)
        // 添加依赖时追加当前的deps, 实现双向互通。双向索引
        域名(域名)
      }
    }
    // 通知所有依赖更新
    notify() {
      // 将任务加到队列中
      域名ach((dep) => {
        域名ons && 域名dular && 域名dular()
        queueJob(dep)
      })
    }
  }

  let ref = (initValue) => {
    let value = initValue
    let dep = new Dep()

    return 域名neProperty({}, \'value\', {
      get() {
        域名nd()
        return value
      },
      set(newValue) {
        value = newValue
        域名fy()
      }
    })
  }

  let count = ref(1)

  function add() {
    域名lementById(\'add\').addEventListener(\'click\', () => {
      域名e++
    })
  }

  add()

  let stop = watchEffect(() => {
    域名lementById(\'text\').innerText = 域名e
  })

  setTimeout(() => {
    stop();
  }, 3000);

})()

免责声明

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

标签:vue编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。