当前位置:首页 > 技术分析 > 正文内容

一文带你搞懂Vue3 底层源码_vue3底层原理

ruisui883个月前 (02-09)技术分析15

作者:妹红大大

转发链接:
https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA

前言

vue3 出来有一段时间了。今天正式开始记录一下梗vue 3.0.0-beta 源码学习心得。

?

本文脚手架使用 vite-app 版本 0.20.0,内置 vue 3.0.0-beta.14。

?

ps: 可能大部分人都不清楚 vue3 的开发api,将源码之前先讲述 使用方法

环境搭建

最容易搭建 vue3 的方式就是使用作者的 vite《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite

通过 npm 安装

??$?npm?init?vite-app?
??$?cd?
??$?npm?install
??$?npm?run?dev

也可以通过 yarn 安装

??$?yarn?create?vite-app?
??$?cd?
??$?yarn
??$?yarn?dev

安装的过程中你可能遇到以下问题(反正本文遇到了)

  • 异常1:No valid exports main found for' C:\xxx\xxx\node_ modules\@rollup\pluginutils'
  • 异常2:The engine "node" is incompatible with this module. Expected version ">= 10.16.0". Got "10.15.3

异常1:本菜翻阅了 vite 的 issue,然后 google + baidu 一无所获, 最后发现是因为本菜 node 版本为 13.5.0导致的(版本过高),

异常2:很明显啦,node 版本太低了。

最后的解决方式是:本菜通过 nvm 将 node 版本切换到 12.12.0,至于 nvm 没使用过的童鞋们可以去尝试下哦。特别好用

vite 原理解析

当浏览器识别 type="module" 引入js文件的时候,内部的 import 就会发起一个网络请求,尝试去获取这个文件。

那么就可以通过通过拦截路由 / 和 .js 结尾的请求。然后通过 node 去加载对应的 .js 文件

????const?fs?=?require('fs')
????const?path?=?require('path')
????const?Koa?=?require('koa')
????const?app?=?new?Koa()

????app.use(async?ctx=>{
????????const?{request:{url}?}?=?ctx
????????//?首页
????????if(url=='/'){n
????????????ctx.type="text/html"
????????????ctx.body?=?fs.readFileSync('./index.html','utf-8')
????????}else?if(url.endsWith('.js')){
????????????//?js文件
????????????const?p?=?path.resolve(__dirname,url.slice(1))
????????????ctx.type?=?'application/javascript'
????????????const?content?=?fs.readFileSync(p,'utf-8')
????????????ctx.body?=?content
????????}
????})

????app.listen(3001,?()=>{
????????console.log('听我口令,3001端口,起~~')
????})

如果只是简单的代码,这样加载就可以了。完全是按需加载,比起 webpack 的语法解析性能当然会快非常多。

但是遇到第三方库以上代码就会找不到 .js 文件的位置了,此时 vite 会用 es-module-lexer 把文件解析成 ast,拿到 import 的地址。

通过分析 import 的内容,识别是不是第三方库(这个主要是看前面是不是相对路径)

如果是第三方库就去 node_modules 中查找,vite 中通过在第三方库中添加前缀 /@modules/,然后发现了 /@modules/ 后走 第三方库逻辑

????if(url.startsWith('/@modules/')){
????????//?这是一个node_module里的东西
????????const?prefix?=?path.resolve(__dirname,'node_modules',url.replace('/@modules/',''))
????????const?module?=?require(prefix+'/package.json').module
????????const?p?=?path.resolve(prefix,module)
????????const?ret?=?fs.readFileSync(p,'utf-8')
????????ctx.type?=?'application/javascript'
????????ctx.body?=?rewriteImport(ret)
????}

这样第三方库也可以解析了。然后是 .vue 单文件解析。

首先 xx.vue 返回的格式大概是这样的

const?__script?=?{
????setup()?{
????????...
????}
}
import?{render?as?__render}?from?"/src/App.vue?type=template&t=1592389791757"
__script.render?=?__render
export?default?__script

然后可以用 @vue/compiler-dom 把 html 解析成 render

解析 .css 就更加简单了。通过 document.createElement('style')然后再注入就好了

reactive

正式进入正题。

作为 vue2 的使用者最想知道的肯定是 vue3 的数据劫持和双向绑定了。在 vue3中,双向绑定和可选项,如果需要使用双向绑定的需要通过 reactive方法进万数据劫持。

在这之前你还需要知道一个函数 setup

  • setup 是使用 Composition API 的入口
  • setup 可以返回一个对象,该对象的属性会被合并到渲染上下文,并可以在模板中直接使用
  • setup 也可以返回 render 函数

现在开始写一个简单的 vue

  

  

emmm。这样点击按钮就可以动态改变 dom 中的 count 值了。

现在开始解读 reactive 源码。

首先找到 reactivity.esm-browser.js 文件,找到 626 行。

function?reactive(target)?{
??//?if?trying?to?observe?a?readonly?proxy,?return?the?readonly?version.
??if?(target?&&?target.__v_isReadonly)?{
??????return?target;
??}
??return?createReactiveObject(target,?false,?mutableHandlers,?mutableCollectionHandlers);
}

上面的 __v_isReadonly 其实是一个 typescript 的枚举值

export?const?enum?ReactiveFlags?{
??skip?=?'__v_skip',
??isReactive?=?'__v_isReactive',
??isReadonly?=?'__v_isReadonly',
??raw?=?'__v_raw',
??reactive?=?'__v_reactive',
??readonly?=?'__v_readonly'
}

不同的枚举值对应了不同的数据劫持方式,例如 reactive、 shallowReactive 、readonly、 shallowReadonly

然后进入 createReactiveObject 在 649 行,意思就是:「创建响应式对象」

function?createReactiveObject(target,?isReadonly,?baseHandlers,?collectionHandlers)?{

????//?略...
????
????//?如果target已经代理了,?返回target
????if?(target.__v_raw?&&?!(isReadonly?&&?target.__v_isReactive))?{
????????return?target;
????}
????//?target?already?has?corresponding?Proxy
????if?(hasOwn(target,?isReadonly???"__v_readonly"?/*?readonly?*/?:?"__v_reactive"?/*?reactive?*/))?{
????????return?isReadonly???target.__v_readonly?:?target.__v_reactive;
????}
????
????if?(!canObserve(target))?{
????????return?target;
????}

????//?重点...
????// collectionHandlers:对引用类型的劫持,
????//?baseHandlers:?对进行基本类型的劫持
????const?observed?=?new?Proxy(target,?collectionTypes.has(target.constructor)???collectionHandlers?:?baseHandlers);
????def(target,?isReadonly???"__v_readonly"?/*?readonly?*/?:?"__v_reactive"?/*?reactive?*/,?observed);
????return?observed;
}

createReactiveObject 做了以下几件事

  1. 防止重复劫持
  2. 只读劫持
  3. 根据不同类型选择不同的劫持方式(collectionHandlers 或 baseHandlers)

实现劫持的主要方法是通过 Proxy 方法,(Proxy 使用可以看看阮老师的博客),顺藤摸瓜找到 mutableHandlers 定义的地方。在 338 行

const?mutableHandlers?=?{
????get,
????set,
????deleteProperty,
????has,
????ownKeys
};

//?229行
const?get?=?/*#__PURE__*/?createGetter();

//?251?行
function?createGetter(isReadonly?=?false,?shallow?=?false)?{
????return?function?get(target,?key,?receiver)?{

????????//?一些?__v_isReactive、__v_isReadonly、__v_raw的处理
????????//?略...

????????//?数组操作
????????const?targetIsArray?=?isArray(target);
????????if?(targetIsArray?&&?hasOwn(arrayInstrumentations,?key))?{
????????????return?Reflect.get(arrayInstrumentations,?key,?receiver);
????????}
????????//?非数组
????????const?res?=?Reflect.get(target,?key,?receiver);
????????
????????//?其他?调用?track?返回?res?的情况
????????//?略...
????????
????????//?如果可写,那么会调用?track
????????!isReadonly?&&?track(target,?"get"?/*?GET?*/,?key);

????????//?如果是对象呢。那么递归
????????return?isObject(res)
??????????????isReadonly
??????????????????//?need?to?lazy?access?readonly?and?reactive?here?to?avoid
????????????????????//?circular?dependency
????????????????????readonly(res)
????????????????:?reactive(res)
????????????:?res;
????};
}

mutableHandlers 主要是一个含有 Proxy 各种方法的常量。

get 指向了方法 createGetter, 「创建 get 劫持」

createGetter 主要做了以下事情

  1. 异常处理
  2. 如果是数组且hasOwn(arrayInstrumentations, key) 则调用 arrayInstrumentations 获取值
  3. 调用 track
  4. 对象迭代 reactive

那么数组的 arrayInstrumentations 是什么呢?我们来到源码的 第 234 行。

const?arrayInstrumentations?=?{};
['includes',?'indexOf',?'lastIndexOf'].forEach(key?=>?{
????arrayInstrumentations[key]?=?function?(...args)?{
????????//?
????????const?arr?=?toRaw(this);
????????for?(let?i?=?0,?l?=?this.length;?i?

通过 arrayInstrumentations 得到 hasOwn(arrayInstrumentations, key) 就是指 ['includes', 'indexOf', 'lastIndexOf']

arrayInstrumentations 中还是调用了 track 方法,那么 track 方法就更加神秘了。来看看它的源码吧?源码在 126 行

function?track(target,?type,?key)?{
????if?(!shouldTrack?||?activeEffect?===?undefined)?{
????????return;
????}
????let?depsMap?=?targetMap.get(target);
????if?(!depsMap)?{
????????targetMap.set(target,?(depsMap?=?new?Map()));
????}
????let?dep?=?depsMap.get(key);
????if?(!dep)?{
????????depsMap.set(key,?(dep?=?new?Set()));
????}
????if?(!dep.has(activeEffect))?{
????????dep.add(activeEffect);
????????activeEffect.deps.push(dep);
????????if?(?activeEffect.options.onTrack)?{
????????????activeEffect.options.onTrack({
????????????????effect:?activeEffect,
????????????????target,
????????????????type,
????????????????key
????????????});
????????}
????}
}

首先 track 需要 shouldTrack 和 activeEffect 为真。

在不考虑 activeEffect 的情况下。track 所做的事情就是

  1. 创建包含自身的 map
  2. 将 activeEffect 赛道 map 中
  3. 触发 onTrack

然后 activeEffect 又是什么呢?找到 3687 行,这里有个 createReactiveEffect 函数。

function?createReactiveEffect(fn,?options)?{
????const?effect?=?function?reactiveEffect(...args)?{
????????return?run(effect,?fn,?args);
????};
????effect._isEffect?=?true;
????effect.active?=?true;
????effect.raw?=?fn;
????effect.deps?=?[];
????effect.options?=?options;
????return?effect;
}

createReactiveEffect 是在 effect 中被调用的

而 effect 分别在以下地方被使用了

  • trigger 通过 scheduleRun 调用 effect:源码 3756 行
  • mountComponent 通过 setupRenderEffect 调用 effect:源码 6235 行
    • PS 该阶段在 createComponentInstance 之后
  • doWatch 通过 scheduler 调用 effect

先开始讲述 trigget 相关的代码(核心哦)

function?trigger(target,?type,?key,?extraInfo)?{
????const?depsMap?=?targetMap.get(target);
????
????//?略...

????const?effects?=?new?Set();
????const?computedRunners?=?new?Set();
????if?(type?===?"clear"?/*?CLEAR?*/)?{
??????//?collection?being?cleared,?trigger?all?effects?for?target
??????depsMap.forEach(dep?=>?{
????????addRunners(effects,?computedRunners,?dep);
??????});
????}

????//?略...?
????const?run?=?(effect)?=>?{
??????scheduleRun(effect,?target,?type,?key,?extraInfo);
????};
????
????computedRunners.forEach(run);
????effects.forEach(run);
??}

trigger 最终是在 set 函数中被使用,源码 3855 行,这个 set 就是数据劫持所用的 set

function?set(target,?key,?value,?receiver)?{
????value?=?toRaw(value);
????const?oldValue?=?target[key];
????if?(isRef(oldValue)?&&?!isRef(value))?{
????????oldValue.value?=?value;
????????return?true;
????}
????const?hadKey?=?hasOwn(target,?key);
????const?result?=?Reflect.set(target,?key,?value,?receiver);
????????{
????????const?extraInfo?=?{?oldValue,?newValue:?value?};
????????if?(!hadKey)?{
????????????trigger(target,?"add"?/*?ADD?*/,?key,?extraInfo);
????????}
????????else?if?(hasChanged(value,?oldValue))?{
????????????trigger(target,?"set"?/*?SET?*/,?key,?extraInfo);
????????}
????????}
????}
????return?result;
}

在源码 3900 行中,被 mutableHandlers、readonlyHandlers 等函数中被使用。

还记得吗?mutableHandlers 是什么?可以回到文章开头部分 reactive源码讲解之初的 createReactiveObject 方法。在通过 Proxy 劫持数据的时候用的就是 mutableHandlers

reactive 总结

所以,这里就成环了。

  1. 其实 effect 才是响应式的核心,在 mountComponent、doWatch、reactive 中被调用。
  2. 在 reactive 中 通过 Proxy 实现劫持。
  3. 在 Proxy 劫持set时调用 trigger。
  4. 然后在 targger 中清除收集并触发目标的所有 effects
  5. 最终触发 patch 游戏结束。

推荐Vue学习资料文章:

9个优秀的 VUE 开源项目

细聊Single-Spa + Vue Cli 微前端落地指南「实践」

通俗易懂的Vue异步更新策略及 nextTick 原理

通俗易懂的Vue响应式原理以及依赖收集

原生JS +Vue实现框选功能

Vue.js轮播库热门精选

一文带你搞懂vue/react应用中实现ssr(服务端渲染)

Vue+CSS3 实现图片滑块效果

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)

vue实现一个6个输入框的验证码输入组件

一用惊人的Vue实践技巧「值得推荐」

Vue常见的面试知识点汇总(上)「附答案」

Vue常见的面试知识点汇总(下)「附答案」

Kbone原理详解与小程序技术选型

为什么我不再用Vue,改用React?

让Jenkins自动部署你的Vue项目「实践」

20个免费的设计资源 UI套件背景图标CSS框架

Deno将停止使用TypeScript,并公布五项具体理由

前端骨架屏都是如何生成的

Vue原来可以这样写开发效率杠杠的

用vue简单写一个音乐播放组件「附源码」

为什么Vue3.0不再使用defineProperty实现数据监听?

「干货」学会这些Vue小技巧,可以早点下班和女神约会

探索 Vue-Multiselect

细品30张脑图带你从零开始学Vue

Vue后台项目中遇到的技术难点以及解决方案

手把手教你Electron + Vue实战教程(五)

手把手教你Electron + Vue实战教程(四)

手把手教你Electron + Vue实战教程(三)

手把手教你Electron + Vue实战教程(二)

手把手教你Electron + Vue实战教程(一)

收集22种开源Vue模板和主题框架「干货」

如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」

手把手教你实现一个Vue自定义指令懒加载

基于 Vue 和高德地图实现地图组件「实践」

一个由 Vue 作者尤雨溪开发的 web 开发工具—vite

是什么让我爱上了Vue.js

1.1万字深入细品Vue3.0源码响应式系统笔记「上」

1.1万字深入细品Vue3.0源码响应式系统笔记「下」

「实践」Vue 数据更新7 种情况汇总及延伸解决总结

尤大大细说Vue3 的诞生之路「译」

提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器

大厂Code Review总结Vue开发规范经验「值得学习」

Vue3 插件开发详解尝鲜版「值得收藏」

带你五步学会Vue SSR

记一次Vue3.0技术干货分享会

Vue 3.x 如何有惊无险地快速入门「进阶篇」

「干货」微信支付前后端流程整理(Vue+Node)

带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」

「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分

「干货」Vue+Element前端导入导出Excel

「实践」Deno bytes 模块全解析

细品pdf.js实践解决含水印、电子签章问题「Vue篇」

基于vue + element的后台管理系统解决方案

Vue仿蘑菇街商城项目(vue+koa+mongodb)

基于 electron-vue 开发的音乐播放器「实践」

「实践」Vue项目中标配编辑器插件Vue-Quill-Editor

基于 Vue 技术栈的微前端方案实践

消息队列助你成为高薪 Node.js 工程师

Node.js 中的 stream 模块详解

「干货」Deno TCP Echo Server 是怎么运行的?

「干货」了不起的 Deno 实战教程

「干货」通俗易懂的Deno 入门教程

Deno 正式发布,彻底弄明白和 node 的区别

「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台

「实践」深入对比 Vue 3.0 Composition API 和 React Hooks

前端网红框架的插件机制全梳理(axios、koa、redux、vuex)

深入Vue 必学高阶组件 HOC「进阶篇」

深入学习Vue的data、computed、watch来实现最精简响应式系统

10个实例小练习,快速入门熟练 Vue3 核心新特性(一)

10个实例小练习,快速入门熟练 Vue3 核心新特性(二)

教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」

2020前端就业Vue框架篇「实践」

详解Vue3中 router 带来了哪些变化?

Vue项目部署及性能优化指导篇「实践」

Vue高性能渲染大数据Tree组件「实践」

尤大大细品VuePress搭建技术网站与个人博客「实践」

10个Vue开发技巧「实践」

是什么导致尤大大选择放弃Webpack?【vite 原理解析】

带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】

带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】

实践Vue 3.0做JSX(TSX)风格的组件开发

一篇文章教你并列比较React.js和Vue.js的语法【实践】

手拉手带你开启Vue3世界的鬼斧神工【实践】

深入浅出通过vue-cli3构建一个SSR应用程序【实践】

怎样为你的 Vue.js 单页应用提速

聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总

【新消息】Vue 3.0 Beta 版本发布,你还学的动么?

Vue真是太好了 壹万多字的Vue知识点 超详细!

Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

手把手教你深入浅出vue-cli3升级vue-cli4的方法

Vue 3.0 Beta 和React 开发者分别杠上了

手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件

Vue3 尝鲜

总结Vue组件的通信

Vue 开源项目 TOP45

2020 年,Vue 受欢迎程度是否会超过 React?

尤雨溪:Vue 3.0的设计原则

使用vue实现HTML页面生成图片

实现全栈收银系统(Node+Vue)(上)

实现全栈收银系统(Node+Vue)(下)

vue引入原生高德地图

Vue合理配置WebSocket并实现群聊

多年vue项目实战经验汇总

vue之将echart封装为组件

基于 Vue 的两层吸顶踩坑总结

Vue插件总结【前端开发必备】

Vue 开发必须知道的 36 个技巧【近1W字】

构建大型 Vue.js 项目的10条建议

深入理解vue中的slot与slot-scope

手把手教你Vue解析pdf(base64)转图片【实践】

使用vue+node搭建前端异常监控系统

推荐 8 个漂亮的 vue.js 进度条组件

基于Vue实现拖拽升级(九宫格拖拽)

手摸手,带你用vue撸后台 系列二(登录权限篇)

手摸手,带你用vue撸后台 系列三(实战篇)

前端框架用vue还是react?清晰对比两者差异

Vue组件间通信几种方式,你用哪种?【实践】

浅析 React / Vue 跨端渲染原理与实现

10个Vue开发技巧助力成为更好的工程师

手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

1W字长文+多图,带你了解vue的双向数据绑定源码实现

深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】

干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现

手把手教你D3.js 实现数据可视化极速上手到Vue应用

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】

Vue3.0权限管理实现流程【实践】

后台管理系统,前端Vue根据角色动态设置菜单栏和路由

作者:妹红大大

转发链接:
https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1835.html

标签: vue $nexttick
分享给朋友:

“一文带你搞懂Vue3 底层源码_vue3底层原理” 的相关文章

【Vue3 基础】05.组件化

这是 Vue3 + Vite + Pinia +TS + Element-Plus 实战系列文档。最近比较忙没什么时间写文章,争取早日把这个系列完结吧~生命周期和模板引用在本章之前,我们通过响应式 api 和声明式渲染,处理了 DOM 的更新,但光是这些,对于一些复杂的需要手动操作 DOM 的情况,...

代码管理-9-gitlab的使用和设置

gitlab使用1、外观设置完成后保存,返回登录页面查看关于注册,有些公司是不允许打开的,,有些人数非常多的公司就需要打开注册的功能,让人员自己注册,我们来给他特定的权限就可以,毕竟人非常多的时候还由我们来给她们注册就非常不现实了,工作量会很大2、自动注册3、组&用户&项目创建组设置组名称、描述等创...

《暗黑破坏神 2:重制版》PC 版 2.3 版本发布,支持英伟达 DLSS

IT之家 12 月 3 日消息,暴雪为《暗黑破坏神 2:重制版》PC 版发布了更新 2.3 版本,添加了“离线难度缩放”滑块(玩家可以在单人游戏时增加挑战和奖励的级别)、多项辅助功能和用户界面改进,以及英伟达 DLSS 支持。玩法改进:玩家现在可以在离线游戏的选项菜单中使用“游戏难度等级”,它提供与...

vue2中路由的使用步骤,你学会了吗?

今天我们来整理下关于vue2中路由的使用步骤:1. 导入 vue 文件和Vue-router文件(注意:vue-router是依赖vue运行的,所以一定在vue后引入vue-router)2. 定义路由组件模板3. 创建路由实例并定义路由规则4. 将路由实例挂载给Vue实例5. 在结构区域定义控制路...

Ruoyi-vue第五十二章:Uniapp小程序配置tabbar底部导航栏

一、功能实现效果如下图底部的tabbar二、uniapp的tabBar如果应用是一个多 tab 应用,可以通过 tabBar 配置项指定一级导航栏,以及 tab 切换时显示的对应页。在 pages.json 中提供 tabBar 配置,不仅仅是为了方便快速开发导航,更重要的是在App和小程序端提升性...

快来看看重构了 365 天的 vue3.5 这次到底更新了啥

新人求关注?,点击右上角 ↗? 关注,博主日更,全年无休,您的关注是我的最大的更新的动力~ 感谢大家了 就在 9 月 1 号,迭代了一年多的 Vue 3.5 终于发布了,这次发布的代号是 "天元突破 红莲螺岩"。这是一个机器人动画片的名字,相信喜欢看动漫的小伙伴应该很熟悉从更新的 C...