Vue3.x 深入浅出系列(连载三)
大家好,我是Mokou,最近一直在做 vue3 相关内容,比如源码解析和mini-vue3的开发。
回顾下前几章的内容,在前几章中主要讲述了以下内容。
- 新构建工具
vite
的原理和从零开始实现 vue3
使用新姿势- 新api:
reactive
使用和源码解析 - 追踪收集
track
实现和源码解析 - 追踪触发器
trigger
实现和源码解析 - 响应式核心
effect
与track、trigger
工作原理和源码解析
好的,这章的目标:从零开始完成一个 Vue3 !
必须要知道的前置知识 effect
与 track、trigger
工作原理,具体详情请看公众号 -> 前端进阶课
,一个有温度且没有广告的前端技术公众号。
在这里还是简单解析下这3个函数的作用吧
- track: 收集依赖,存入
targetMap
- trigger:触发依赖,使用
targetMap
- effect:副作用处理
本章源码请看 uuz 急需 star 维持生计。
前两章连载内容:
- Vue3.x深入浅出系列(连载一)
- Vue3.x深入浅出系列(连载二)
手摸手实现 Vue3
首先。我们2个全局变量,用来存放和定位追踪的依赖,也就是给 track
和 trigger
使用的仓库。
let targetMap = new WeakMap();
let activeEffect;
复制代码
所以第一个需要设计的方法就是 track
,还记得该track
在vue3是如何调用的吗?
track(obj, 'get', 'x');
复制代码
track
会去找 obj.x
是否被追踪,如果没找到就将obj.x放入targetMap
(完成追踪任务),将 obj.x
作为 map 的 key 将 activeEffect 作为 map 的 value。
抛开取值异常处理之类的,track
只做了一件事,将activeEffect
塞入targetMap
;
function track(target, key) {// 首先找 obj 是否有被追踪let depsMap = targetMap.get(target);if (!depsMap) {// 如果没有被追踪,那么添加一个targetMap.set(target, (depsMap = new Map()));}// 然后寻找 obj.x 是否被追踪let dep = depsMap.get(key);if (!dep) {// 如果没有被追踪,那么添加一个depsMap.set(key, (dep = new Set()));}// 如果没有添加 activeEffect 那么添加一个if (!dep.has(activeEffect)) {dep.add(activeEffect);}
}
复制代码
然后就是写一个 trigger
,还记得trigger
在vue是如何调用的吗?
trigger(obj, 'set', 'x')
复制代码
trigger
只会去 targetMap
中寻找obj.x
的追踪任务,如果找到了就去重,然后执行任务。
也就是说:抛开取值异常相关,trigger
也只做了一件事:从 targetMap
取值然后调用该函数值。
function trigger(target, key) {// 寻找追踪项const depsMap = targetMap.get(target);// 没找到就什么都不干if (!depsMap) return;// 去重const effects = new Set()depsMap.get(key).forEach(e => effects.add(e))// 执行effects.forEach(e => e())
}
复制代码
最后就是 effect
,还记得该打工仔的api在vue3中是如何调用的吗?
effect(() => {console.log('run cb')
})
复制代码
effect
接收一个回调函数,然后会被送给 track
。所以我们可以这么完成 effect
- 定义一个内部函数
_effect
,并执行。 - 返回一个闭包
而内部 _effect
也做了两件事
- 将自身赋值给
activeEffect
- 执行
effect
回调函数
优秀的代码呼之欲出。
function effect(fn) {// 定义一个内部 _effect const _effect = function(...args) {// 在执行是将自身赋值给 activeEffectactiveEffect = _effect;// 执行回调return fn(...args);};_effect();// 返回闭包return _effect;
}
复制代码
所有的前置项都完成了,现在开始完成一个 reactive
,也就是对象式响应式的api。还记得vue3中如何使用 reactive
吗?
<template><button @click="appendName">{{author.name}}</button>
</template>setup() {const author = reactive({name: 'mokou',})const appendName = () => author.name += '优秀';return { author, appendName };
}
复制代码
通过上面的的优秀代码,很轻易的实现了vue3的响应式操作。通过回顾前几章的内容,我们知道 reactive
是通过 Proxy 代理数据实现的。
这样我们就可以通过 Proxy
来调用 track
和 trigger
,劫持 getter
和 setter
完成响应式设计
export function reactive(target) {// 代理数据return new Proxy(target, {get(target, prop) {// 执行追踪track(target, prop);return Reflect.get(target, prop);},set(target, prop, newVal) {Reflect.set(target, prop, newVal);// 触发effecttrigger(target, prop);return true;}})
}
复制代码
好了。一切就绪,那么我们挂载下我们的 fake vue3
吧
export function mount(instance, el) {effect(function() {instance.$data && update(el, instance);})instance.$data = instance.setup();update(el, instance);
}function update(el, instance) {el.innerHTML = instance.render()
}
复制代码
用 mini-vue3 写一个 demo
测试一下。参照 vue3 的写法。定义个 setup
和 render
。
const App = {$data: null,setup () {let count = reactive({ num: 0 })setInterval(() => {count.num += 1;}, 1000);return {count};},render() {return `<button>${this.$data.count.num}</button>`}
}mount(App, document.body)
复制代码
执行一下,果然是优秀的代码。响应式正常执行,每次 setInterval
执行后,页面都重写刷新了 count.num
的数据。
源码请看 uuz,ps:7月23日该源码已经支持 jsx 了。
以上通过 50+
行代码,轻轻松松的实现了 vue3
的响应式。但这就结束了吗?
还有以下问题
Proxy
一定需要传入对象render
函数 和h
函数并正确(Vue3的h函数现在是2个不是以前的createElement
了)- 虚拟 dom 的递归
- 别再说了
- -!
,我不听。
ref
使用 reactive 会有一个缺点,那就是,Proxy 只能代理对象,但不能代理基础类型。
如果你调用这段代码 new Proxy(0, {})
,浏览器会反馈你 Uncaught TypeError: Cannot create proxy with a non-object as target or handler
所以,对于基础类型的代理。我们需要一个新的方式,而在 vue3
中,对于基础类型的新 api 是 ref
<button >{{count}}</button>export default {setup() {const count = ref(0);return { count };}
}
复制代码
实现 ref 其实非常简单:利用 js 对象自带的 getter 就可以实现
举个栗子:
let v = 0;
let ref = {get value() {console.log('get')return v;},set value(val) {console.log('set', val)v= val;}
}ref.value; // 打印 get
ref.value = 3; // 打印 set
复制代码
那么通过前面几章实现的 track
和 trigger
可以轻松实现 ref
直接上完成的代码
function ref(target) {let value = targetconst obj = {get value() {track(obj, 'value');return value;},set value(newVal) {if (newVal !== value) {value = newVal;trigger(obj, 'value');}}}return obj;
}
复制代码
computed
那么该怎么实现 computed
?
首先:参考 vue3
的 computed
使用方式
let sum = computed(() => {return count.num + num.value + '!'
})
复制代码
盲猜可以得到一个想法,通过改造下 effect
可以实现,即在 effect
调用的那一刻不执行 run
方法。所以我们可以加一个 lazy
参数。
function effect(fn, options = {}) {const _effect = function(...args) {activeEffect = _effect;return fn(...args);};// 添加这段代码if (!options.lazy) {_effect();}return _effect;
}
复制代码
那么 computed
可以这么写
- 内部执行
effect(fn, {lazy: true})
保证computed
执行的时候不触发回调。 - 通过对象的
getter
属性,在computed
被使用的时候执行回调。 - 通过
dirty
防止出现内存溢出。
优秀的代码呼之欲出:
function computed(fn) {let dirty = true;let value;let _computed;const runner = effect(fn, {lazy: true});_computed = {get value() {if (dirty) {value = runner();dirty = false;}return value;}}return _computed;
}
复制代码
那么问题来了 dirty
在第一次执行后就被设置为 false
如何重置?
此时 vue3
的解决方法是,给 effect
添加一个 scheduler
用来处理副作用。
function effect(fn, options = {}) {const _effect = function(...args) {activeEffect = _effect;return fn(...args);};if (!options.lazy) {_effect();}// 添加这行_effect.options = options;return _effect;
}
复制代码
既然有了 scheduler
那就需要更改 trigger
来处理新的 scheduler
。
function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) return;const effects = new Set()depsMap.get(key).forEach(e => effects.add(e))// 更改这一行effects.forEach(e => scheduleRun(e))
}// 添加一个方法
function scheduleRun(effect) {if (effect.options.scheduler !== void 0) {effect.options.scheduler(effect);} else {effect();}
}
复制代码
然后,把上面代码合并一下,computed
就完成了
function computed(fn) {let dirty = true;let value;let _computed;const runner = effect(fn, {lazy: true,scheduler: (e) => {if (!dirty) {dirty = true;trigger(_computed, 'value');}}});_computed = {get value() {if (dirty) {value = runner();dirty = false;}track(_computed, 'value');return value;}}return _computed;
}
复制代码
总结
- reactive 的核心是
track
+trigger
+Proxy
- ref 是通过对象自有的
getter
和setter
配合track
+trigger
实现的 - computed 其实是一个在
effect
基础上的改进
下章内容:vue3
该怎么结合 jsx
?
最后
原创不易,给个三连安慰下弟弟吧。
- 源码请看 uuz
- 本文内容出自 github.com/zhongmeizhi…
- 欢迎关注公众号「前端进阶课」认真学前端,一起进阶。回复
全栈
或Vue
有好礼相送哦
Vue3.x 深入浅出系列(连载三)相关推荐
- 手机WAPI功能检测常见问题分析(系列连载三):预共享密钥功能
原文地址:http://www.wapia.org/topic/standards/detail_5073.shtml 1 预共享密钥功能测试中常见问题及解决方法 A. 问题一:预共享密钥长度不符合要 ...
- 深入浅出python系列(三):逻辑判断语句
深入浅出python系列: 深入浅出python系列(一):基本数据类型 深入浅出python系列(二):运算符 [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权): 本博客的 ...
- recyclerview 滚动冲突_如何处理手势冲突 | 手势导航连载 (三)
作者 / Chris Banes, Android 开发者关系团队工程师 我们将在近期为大家带来一个关于 "手势导航" 的系列连载,本文是手势导航连载的第三篇,如果您希望查看前两篇 ...
- 二十一世纪“新元宇宙”奇科幻小说原创作品系列连载【第一部】第二回 登峰时刻
二十一世纪"新元宇宙"奇科幻小说原创作品系列连载[第一部] <地球人奇游"天球"记> 第二回 登峰时刻 1.静沐"新哲学" ...
- 自媒体赚钱系列连载03:音乐人有收益自媒体平台大全
今天跟大家讲讲一个冷门的自媒体领域,这就是音乐人领域,这个音乐人领域说的不是其他自媒体中的音乐领域,其他的自媒体中的音乐领域是分享好的音乐视频,好的明星音乐那种,那种是专门评论明星或者发明星有关音乐视 ...
- 深入浅出系列1:词向量
深入浅出系列1:词向量 0.文章结构 词向量简介 one-hot编码 统计语言模型 分布式表征和SVD分解 神经网络语言模型 word2vec fastText(新增文章补充,敬请期待) GloVe( ...
- [深度][PyTorch] DDP系列第三篇:实战与技巧
[深度][PyTorch] DDP系列第三篇:实战与技巧 转自:https://zhuanlan.zhihu.com/p/250471767 零. 概览 想要让你的PyTorch神经网络在多卡环境上跑 ...
- 二十一世纪“新元宇宙”奇幻小说作品系列连载
二十一世纪"新元宇宙"奇幻小说系列连载[第一部] <地球人奇游"天球"记> 第一回 冰雪跨年 1.静沐"新哲学"思想: 水在世间 ...
- 单反相机的传奇—佳能单反50年辉煌之路(连载三)
德国--徕卡与35mm 单反相机的传奇-佳能单反50年辉煌之路(连载三) 作者:木木 由于前面两篇写的都是佳能的"前传",对当时的历史环境交待较少,为了让大家尽可能多地了解当时德国 ...
- Mybatis深入浅出系列
2019独角兽企业重金招聘Python工程师标准>>> Mybatis实战之TypeHandler高级进阶 南轲梦 2017-02-20 23:02 阅读:890 评论:4 Myba ...
最新文章
- 洛谷 P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
- Python3 如何优雅地使用正则表达式(详解五)
- html项目答辩开场白,毕业论文答辩演讲稿开场白范例
- 用高精度方法计算n! ,并显示n!(阶乘)的值。
- HarmonyOS之AI能力·二维码的生成和使用
- 如何为火狐浏览器添加附加组件?火狐浏览器附加组件管理器使用教程
- c++二叉树的层序遍历_leetcode 103. 二叉树的锯齿形层序遍历
- 日期处理——日期差值
- the server is not ready for publishing.Please check if the Publishing Tools on the server
- LINQ分组查询统计
- 51job导出的简历是php,前程无忧简历导出
- BT.656标准简介
- Flutter发送表情接收表情库
- node短信接口开发
- RedHat认证介绍
- java绘图-绘制图片
- 记一次摸不着头脑的FullGC问题 (Thumbnails压缩图片占用巨大内存)
- banner设圆角_C4D和PS如何制作banner
- Excel地址 C语言
- 学习大神作品(vue源码三)
热门文章
- 淘宝商品比价定向爬虫实例介绍
- Unity 用ml-agents机器学习造个游戏AI吧(2)(入门DEMO)
- 训练好的神经网络 如何预测_【家长必看】如何帮助孩子训练好口才?
- usb右下角有显示,计算机没显示,U盘显示在计算机的右下角,但无法打开
- mysql 省份城市县区数据表SQL(包含经纬度)
- UCenter 通信
- 取消Word自动首字母大写步骤
- 无损连接,函数依赖性判定
- 1111111111
- SX1308锂电池升压1.5—3.7升5v1.5A 专为太阳能灯开发的DC/DC直流升压IC