vue源码中computed和watch的解读
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
- computed -> let x = computed(()=> count.value + 3);
- watch -> watch(()=> count.value, (curVal, preVal) => {}, { deep, immediate })
- 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的解读的更多相关文章
- 【Vue】VUE源码中的一些工具函数
Vue源码-工具方法 /* */ //Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性. var emptyObject = Object.freeze({}); // th ...
- vue 源码解析computed
计算属性 VS 侦听属性 Vue 的组件对象支持了计算属性 computed 和侦听属性 watch 2 个选项,很多同学不了解什么时候该用 computed 什么时候该用 watch.先不回答这个问 ...
- 了解一下vue源码中vue 的由来
我们之前提到过 Vue.js 构建过程,在 web 应用下,我们来分析 Runtime + Compiler 构建出来的 Vue.js,它的入口是 src/platforms/web/entry-r ...
- Vue源码中compiler部分逻辑梳理(内有彩蛋)
目录 一. 简述 二. 编译流程 三. 彩蛋环节 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目录 ...
- nodeType属性在vue源码中的使用
每个节点都有一个 nodeType 属性,用于表明节点的类型,节点类型由 Node 类型中定义12个常量表示: nodeType在vue中的应用 在vue编译的过程中需要查找html结构中的双大括号 ...
- 从Vue源码中我学到了几点精妙方法
话不多说,赶快试试这几个精妙方法吧!在工作中肯定会用得到. 立即执行函数 页面加载完成后只执行一次的设置函数. (function (a, b) { console.log(a, b); // 1,2 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Vue源码解析---数据的双向绑定
本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...
- Vue源码学习(一):调试环境搭建
最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...
随机推荐
- express系列(1)概述
在 Node.js 出现之前,前后端的开发必须使用不同的语言进行.为此你需要学习多种的语言和框架.有了 Node.js 之后,你就可以使用一门语言在前后端开发中自由切换,这是最吸引人的地方. 什么是 ...
- 在 windows 系统上 安装与配置 PHP + Apache
参考:http://www.cnblogs.com/pharen/archive/2012/02/06/2340628.html 在大学时候上过一门PHP课时,因为课堂需要配置过一次PHP+Mysql ...
- 【STM32】WS2812介绍、使用SPI+DMA发送数据
这篇要使用到SPI+DMA,需要了解的话,可以参考我另两篇博客 时钟:https://www.cnblogs.com/PureHeart/p/11330967.html SPI+DMA通信:https ...
- android TabLayout设置选项卡之间的距离无效已解决
根据下面的链接设置完距离后无法生效 https://www.jb51.net/article/131304.htm layout <com.google.android.material.tab ...
- 【Python】【Basic】【数据类型】基本数据类型
1.数字 int(整型) 在32位机器上,整数的位数为32位,取值范围为-2**31-2**31-1,即-2147483648-2147483647 在64位系统上,整数的位数为64位,取值范围为-2 ...
- Linux后台启动服务
systemctl 启动/关闭/启用/禁用服务 总结 启动服务 systemctl start test.service 关闭服务 systemctl stop test.service 重启服务 s ...
- entfrm开发平台,一个免费开源可视化的无代码开发平台
简介 entfrm开发平台,是一个以模块化为核心的无代码开发平台,是一个集PC和APP快速开发.系统管理.运维监控.开发工具.OAuth2授权.可视化数据源管理与数据构建.API动态生成与统计.工作流 ...
- 学习Oracle遇到的实际问题(持续更新)
有三个用户参与这个事情: system用户,拥有表manager. sys create了一个用户item,并赋予权限: SQL> GRANT SELECT ON SYSTEM.MANAGER ...
- kubeadm安装k8s集群
安装kubeadm kubectl kubelet 对于Ubuntu/debian系统,添加阿里云k8s仓库key,非root用户需要加sudo apt-get update && a ...
- gitlab配置免密拉取推送
目录 一.简介 二.配置 一.简介 gitlab默认提供HTTP/SSH两种请求方式下载代码 测试用的gitlab账号 账号:abc 密码:123456 二.配置 1.生成秘钥,一路回车即可 cd ~ ...