大家好,我是Mokou,最近一直在做 vue3 相关内容,比如源码解析和mini-vue3的开发。

回顾下前几章的内容,在前几章中主要讲述了以下内容。

  1. 新构建工具 vite 的原理和从零开始实现
  2. vue3 使用新姿势
  3. 新api:reactive 使用和源码解析
  4. 追踪收集 track 实现和源码解析
  5. 追踪触发器 trigger 实现和源码解析
  6. 响应式核心 effecttrack、trigger 工作原理和源码解析

好的,这章的目标:从零开始完成一个 Vue3 !

必须要知道的前置知识 effecttrack、trigger 工作原理,具体详情请看公众号 -> 前端进阶课,一个有温度且没有广告的前端技术公众号。

在这里还是简单解析下这3个函数的作用吧

  1. track: 收集依赖,存入 targetMap
  2. trigger:触发依赖,使用 targetMap
  3. effect:副作用处理

本章源码请看 uuz 急需 star 维持生计。

前两章连载内容:

  • Vue3.x深入浅出系列(连载一)
  • Vue3.x深入浅出系列(连载二)

手摸手实现 Vue3

首先。我们2个全局变量,用来存放和定位追踪的依赖,也就是给 tracktrigger 使用的仓库。

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

  1. 定义一个内部函数 _effect,并执行。
  2. 返回一个闭包

而内部 _effect 也做了两件事

  1. 将自身赋值给 activeEffect
  2. 执行 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 来调用 tracktrigger,劫持 gettersetter 完成响应式设计

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 的写法。定义个 setuprender

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的响应式。但这就结束了吗?

还有以下问题

  1. Proxy 一定需要传入对象
  2. render 函数 和 h 函数并正确(Vue3的h函数现在是2个不是以前的createElement了)
  3. 虚拟 dom 的递归
  4. 别再说了- -!,我不听。

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
复制代码

那么通过前面几章实现的 tracktrigger 可以轻松实现 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

首先:参考 vue3computed 使用方式

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 可以这么写

  1. 内部执行 effect(fn, {lazy: true}) 保证 computed 执行的时候不触发回调。
  2. 通过对象的 getter 属性,在 computed 被使用的时候执行回调。
  3. 通过 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;
}
复制代码

总结

  1. reactive 的核心是 track + trigger + Proxy
  2. ref 是通过对象自有的 gettersetter 配合 track + trigger 实现的
  3. computed 其实是一个在 effect 基础上的改进

下章内容:vue3 该怎么结合 jsx

最后

原创不易,给个三连安慰下弟弟吧。

  1. 源码请看 uuz
  2. 本文内容出自 github.com/zhongmeizhi…
  3. 欢迎关注公众号「前端进阶课」认真学前端,一起进阶。回复 全栈Vue 有好礼相送哦

Vue3.x 深入浅出系列(连载三)相关推荐

  1. 手机WAPI功能检测常见问题分析(系列连载三):预共享密钥功能

    原文地址:http://www.wapia.org/topic/standards/detail_5073.shtml 1 预共享密钥功能测试中常见问题及解决方法 A. 问题一:预共享密钥长度不符合要 ...

  2. 深入浅出python系列(三):逻辑判断语句

    深入浅出python系列:   深入浅出python系列(一):基本数据类型   深入浅出python系列(二):运算符 [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权): 本博客的 ...

  3. recyclerview 滚动冲突_如何处理手势冲突 | 手势导航连载 (三)

    作者 / Chris Banes, Android 开发者关系团队工程师 我们将在近期为大家带来一个关于 "手势导航" 的系列连载,本文是手势导航连载的第三篇,如果您希望查看前两篇 ...

  4. 二十一世纪“新元宇宙”奇科幻小说原创作品系列连载【第一部】第二回 登峰时刻

    二十一世纪"新元宇宙"奇科幻小说原创作品系列连载[第一部] <地球人奇游"天球"记> 第二回    登峰时刻 1.静沐"新哲学" ...

  5. 自媒体赚钱系列连载03:音乐人有收益自媒体平台大全

    今天跟大家讲讲一个冷门的自媒体领域,这就是音乐人领域,这个音乐人领域说的不是其他自媒体中的音乐领域,其他的自媒体中的音乐领域是分享好的音乐视频,好的明星音乐那种,那种是专门评论明星或者发明星有关音乐视 ...

  6. 深入浅出系列1:词向量

    深入浅出系列1:词向量 0.文章结构 词向量简介 one-hot编码 统计语言模型 分布式表征和SVD分解 神经网络语言模型 word2vec fastText(新增文章补充,敬请期待) GloVe( ...

  7. [深度][PyTorch] DDP系列第三篇:实战与技巧

    [深度][PyTorch] DDP系列第三篇:实战与技巧 转自:https://zhuanlan.zhihu.com/p/250471767 零. 概览 想要让你的PyTorch神经网络在多卡环境上跑 ...

  8. 二十一世纪“新元宇宙”奇幻小说作品系列连载

    二十一世纪"新元宇宙"奇幻小说系列连载[第一部] <地球人奇游"天球"记> 第一回 冰雪跨年 1.静沐"新哲学"思想: 水在世间 ...

  9. 单反相机的传奇—佳能单反50年辉煌之路(连载三)

    德国--徕卡与35mm 单反相机的传奇-佳能单反50年辉煌之路(连载三) 作者:木木 由于前面两篇写的都是佳能的"前传",对当时的历史环境交待较少,为了让大家尽可能多地了解当时德国 ...

  10. Mybatis深入浅出系列

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis实战之TypeHandler高级进阶 南轲梦 2017-02-20 23:02 阅读:890 评论:4 Myba ...

最新文章

  1. 洛谷 P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
  2. Python3 如何优雅地使用正则表达式(详解五)
  3. html项目答辩开场白,毕业论文答辩演讲稿开场白范例
  4. 用高精度方法计算n! ,并显示n!(阶乘)的值。
  5. HarmonyOS之AI能力·二维码的生成和使用
  6. 如何为火狐浏览器添加附加组件?火狐浏览器附加组件管理器使用教程
  7. c++二叉树的层序遍历_leetcode 103. 二叉树的锯齿形层序遍历
  8. 日期处理——日期差值
  9. the server is not ready for publishing.Please check if the Publishing Tools on the server
  10. LINQ分组查询统计
  11. 51job导出的简历是php,前程无忧简历导出
  12. BT.656标准简介
  13. Flutter发送表情接收表情库
  14. node短信接口开发
  15. RedHat认证介绍
  16. java绘图-绘制图片
  17. 记一次摸不着头脑的FullGC问题 (Thumbnails压缩图片占用巨大内存)
  18. banner设圆角_C4D和PS如何制作banner
  19. Excel地址 C语言
  20. 学习大神作品(vue源码三)

热门文章

  1. 淘宝商品比价定向爬虫实例介绍
  2. Unity 用ml-agents机器学习造个游戏AI吧(2)(入门DEMO)
  3. 训练好的神经网络 如何预测_【家长必看】如何帮助孩子训练好口才?
  4. usb右下角有显示,计算机没显示,U盘显示在计算机的右下角,但无法打开
  5. mysql 省份城市县区数据表SQL(包含经纬度)
  6. UCenter 通信
  7. 取消Word自动首字母大写步骤
  8. 无损连接,函数依赖性判定
  9. 1111111111
  10. SX1308锂电池升压1.5—3.7升5v1.5A 专为太阳能灯开发的DC/DC直流升压IC