在这篇博文中, 我们将要聊聊如下几个内容:
vite
脚手架的原理(对比webpack
)vue3
在性能上的进一步突破(如: 静态提升, 预字符串化, patch flag等)vue3
在组件, API和数据代理上的新变化vue3
推出的reactivity api
和composition api
vite
脚手架的原理(对比webpack
)
vite
的使用我就不怎么说了, 官网直接手把手教你怎么用
我们主要来看看vite
究竟快在哪里
打开vite
官网我们会发现映入眼帘的第一句话就直接交代了vite
的作用:
vite
官方说vite
能够给开发者带来更加好的开发体验, 聊到这儿, 我们得想想, 我们使用vue-cli
的时候, 开发体验就很差吗? 这里因为看博文的每个同学所接触到项目和团队规模不一致, 我相信有的同学可能能够直接肯定的回答yes, 有的还有疑惑, 没事, 我们来看看webpack
他的一个运作原理之后我们在聊聊使用vue-cli
可能会出现影响开发体验的点
webpack的编译原理
其实说高大上一点是编译原理, 但是事实上我们要聊得就是使用vue-cli
搭建的工程当你敲下yarn start / dev
命令去要求开启localhost:8000
开发服务器的这个过程中都发生了什么
根据上图, 我们可以能够基本窥探到webpack
他在开启开发服务器时候的基本流程, 那么我们来总结一下步骤:
webpack
根据入口模块找寻所有的依赖并进行打包(这个打包过程还蛮繁琐的, 我画了一张图大家可以看一下)
- 走完上面的编译过程以后,
webpack
会去走一个生成chunk的流程, 然后还会去走合并chunk和输出目录的流程, 这些细节都非常繁琐, 这里就不多作赘述了, 这一步和上一步我们称之为打包 - 当走完打包步骤以后,
webpack
才会利用webpack-dev-server
插件来帮助我们开启localhost:8080
端口, 这个时候我们就可以访问开发服务器了 - 在开发过程中, 如果有文件产生了变化,
webpack
会直接重新收集该文件所依赖的所有依赖并重新走打包流程
那么webpack
这样做有什么好处呢?
这样做又有什么坏处呢?
vite的编译原理
vite
的快就是快在编译上, 话不多说, 我们直接来看看vite
的编译原理
步骤总结:
- 当我们在terminal敲下
yarn dev / start
的时候,vite
无需等待(其实也需要, 但是他的等待是一个做缓存的过程), 直接开启localhost:8080
服务器环境, 开发者可以直接访问 webpack
是将所有的module
打包成一个js然后引入index.html
, 而vite
直接开启localhost
并打开index.html
, 但是我们会发现但凡你使用vite
构建的工程,index.html
在一开始就被引入了main.js
, 这样意味着index.html
一旦打开浏览器就会再次去请求main.js
- 当发现
main.js
依赖了其他的文件, 比如App.vue
, 浏览器又会再次向开发服务器请求App.vue
, 这个时候开发服务器会将App.vue
编译成JS给到浏览器, 如此反复, 有依赖就请求, 没依赖到的模块鸟都不鸟你 - 当需要热替换的时候,
vite
只会让浏览器重新请求被更改的文件, 而不是所有依赖的文件全部重新打包
这样做好在哪里?
这样做有什么缺陷?
- 一旦你是不支持
es6module
的老版本浏览器, 那么你将永远只能引入最基础的main.js
, 不支持浏览器模块化的话他根本不认识import
是什么东西 - 你将不能使用其他模块化规范的代码, 因为同理, 浏览器只认识
es6 module
,require
是啥他都不知道
但是这两个缺陷致不致命? 不致命, 首先你作为开发者你肯定是用最新版本的浏览器进行开发, 其次我上面也聊过了, 企业压根就不允许你精神分裂一下这个规范一下那个规范的, 所以这两个问题都不是很致命, 但是vite
对于构建速度的提升是非常大的, 特别是项目越大他越明显
可能有的同学会说, 那既然是考虑到import
一个模块就会发送一个请求, 那比如有的node_modules
中的模块依赖了特别多的模块不是要发几千上百个请求? 还有的node_modules
中的模块是commonjs
导出的又怎么办?vite
在内部其实也用到了webpack
来处理这些问题, 你可以去看看官网的依赖预构建章节
vite的功能还不远止于此, 如果有兴趣的同学可以去vite官网看看
vue3
在性能上的进一步突破(如: 静态提升, 预字符串化, patch flag等)
每一次新版本的发布, vue
总是再给我们带来新的惊喜, 在vue3
中, 尤雨溪又将性能考虑到了极致, 具他在b站分享的时候说: vue3的客户端渲染效率较vue2提升了1.3-2倍, ssr的渲染效率提升了2-3倍
静态提升和预字符串化
我们知道哈, 最终不管你在模板里写的是什么, 都会被vue
编译成render
函数去进行渲染, 而这些性能优化多数都是在render
函数中做的文章
静态节点提升
首先什么是静态节点, 静态节点必须满足以下两个条件
- 必须是
vue
元素节点 - 元素节点上不能动态绑定状态和属性
<div>i am a static node</div> // 静态节点
<div class="wrapper">{{ info }}</div> // 动态节点
<div :class="{
'diabled': isDisable
}">hello</div> // 这也是动态节点
那么什么叫做静态节点提升呢?
// Test.vue
// vue3支持多根节点, 这个我们后续会说原理
<template>
<div>i am static node</div>
<div>{{ infos }}</div>
</template>
<script>
export default {
data: () => ({
infos: "i am dynamics node"
})
}
</script>
我们可以打开浏览器, 查看network
, 我们之前有说过, 使用vite
后会有一个按需加载的机制, 所以我们可以在network
中看到整个vite
给到我们的组件文件, 我们可以看到给到我们的文件是已经经过编译的, 而使用到的编译工具是@vue/compiler-sfc
, 有兴趣的同学可以去了解一下, 我就不做多赘述了
上面的代码最终会被vue
编译成如下形式:
// 我们会看到第一个静态节点的div, 并不是在render函数内部去创建的
// 而是在外部创建的, 这样也意味着, render函数中永远使用到的都是这
// 同样一个引用, 这就是静态节点的提升
// 其他的代码我们不用看, 只用看这一行就能够知道静态节点提升的含义了
const _hoisted_1 = createVNode("div", null, "i am static node", -1)
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (openBlock(),createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.infos), 1)
], 64))
}
静态属性提升
上面我们说到了静态节点, 那么什么是静态属性呢?
无非就是没有动态绑定属性值的属性呗
// class是静态属性 disable是动态属性
<div class="wrapper"></div>
<div :disable="isDisable"></div>
跟静态节点一样, 静态属性将会被缓存到render
函数外部
假设我们有模板是这样的:
<template>
<div class="wrapper">{{ infos }}</div>
<div :disable="isDisable"></div>
</template>
<script>
export default {
data: () => ({
isDisable: false,
infos: "hello"
})
}
</script>
打开浏览器, 我们会发现上面的代码会被编译成
// 我们会发现即使第一个div是动态节点, 但是只要他上面有静态属性
// 则静态属性就会被提升到render函数外部
const _hoisted_1 = { class: "wrapper" }
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", _hoisted_1, _toDisplayString(_ctx.infos), 1 /* TEXT */),
_createVNode("div", { disable: _ctx.isDisable }, null, 8 /* PROPS */, ["disable"])
], 64 /* STABLE_FRAGMENT */))
}
注意如果你的节点上又有动态节点, 又有静态节点, 那你是跑不掉的, 一定会被放进render函数内部的
预字符串化
假设我们的模板是这样
<template>
<div class="wrapper"></div>
<div class="wrapper"></div>
<div class="wrapper"></div>
<div class="wrapper"></div>
<div class="wrapper"></div>
<div :disable="isDisable"></div>
</template>
<script>
export default {
data: () => ({
isDisable: false,
infos: "hello"
})
}
</script>
被编译后的结果如下:
// 我们会看到vue直接调用了createStaticNodes方法去生成静态节点, 而
// 生成的静态节点也不会进入vue的虚拟dom树比对环节
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<div class=\"wrapper\"></div><div class=\"wrapper\"></div><div class=\"wrapper\"></div><div class=\"wrapper\"></div><div class=\"wrapper\"></div>", 5)
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", { disable: _ctx.isDisable }, null, 8 /* PROPS */, ["disable"])
], 64 /* STABLE_FRAGMENT */))
}
我们可以大致理解一下上面的代码vue2和3各自生成的虚拟dom树如下图:
如图所示, 当需要进行patch比对的时候, vue2
没有动静态节点的区分, 都会进行比对, 而vue3
有了静态节点, 静态节点他看都不看, 直接去比对动态节点, 这样怎么看效率都会提高不少
缓存事件处理函数
<template>
<div @click="clickHandler"></div>
<div @click="secHandler"></div>
</template>
<script>
export default {
methods: {
clickHandler() {
},
secHandler() {
}
}
}
</script>
上面的代码会被编译成如下结果
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", {
// 我们会发现_cache作为一个缓存池会被作为参数传递给render函数
// 当我们首次创建虚拟dom的时候, 会往_cache中缓存我们的事件
// 后续再进行虚拟dom创建时就不会再重新创建引用了
onClick: _cache[1] || (_cache[1] = (...args) => ($options.clickHandler && $options.clickHandler(...args)))
}),
_createVNode("div", {
onClick: _cache[2] || (_cache[2] = (...args) => ($options.secHandler && $options.secHandler(...args)))
})
], 64 /* STABLE_FRAGMENT */))
}
有点写累了, 我打王者荣耀去了, 下周再续更吧~
patch flag 和 block tree
loading
vue3
在组件, API和数据代理上的新变化
loading
vue3
推出的reactivity api
和composition api
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!