前言 最近看了看vue3,发现变化还是挺大的,写篇文章来记录一波
vite vite介绍和用法 Vite是一个由原生ESM驱动的Web开发构建工具。在开发环境下基于浏览器原生ES imports开发,在生产环境下基于Rollup打包。
其实简单来说,vite就是一个和webpack用处差不多的代码构建工具,但是它在代码开发阶段有着非常显著的优势,它大大降低了开启本地服务器和代码热更新需要的时间,他的主要优点有下面几个
那么怎么使用呢,我们直接运行下面的命令就可以了
1 2 3 4 $ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev 
 
这个命令会在本地临时安装vite,然后用vite创建一个新项目,所以每次运行的时候使用的都是最新的vite
而从生成的目录树中可以看出来,vite和vue-cli生成的代码并没有太大的差别
1 2 3 4 5 6 7 8 9 10 11 12 13 ├── index.html ├── package.json ├── public │   └── favicon.ico └── src     ├── App.vue     ├── assets     │   └── logo.png     ├── components     │   └── HelloWorld.vue     ├── index.css     └── main.js 
 
在开发时要注意在导入文件时除了导入的文件是js类型的,其他时候都要补全后缀
vite的原理 在运行npm run dev后,vite借用了koa启动了一个本地代理服务器,没有进行任何的编译和打包操作
1 2 3 4 5 6 [vite] Optimizable dependencies detected: vue   Dev server running at:   > Network:  http://192.168.2.67:3000/   > Local:    http://localhost:3000/ 
 
在访问http://localhost:3000/时,vite直接返回了项目的index.html文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <link  rel ="icon"  href ="/favicon.ico" />      <meta  name ="viewport"  content ="width=device-width, initial-scale=1.0" >      <title > Vite App</title >  </head > <body > <div  id ="app" > </div > <script  type ="module"  src ="/src/main.js" > </script > </body > </html > 
 
然后浏览器就会解析这段html文件,值的一提的是,这里的main.js是使用了module的方式引入的,所以天生支持import和export语句,浏览器会向本地服务器请求main.js
本地服务器返回处理过的main.js
1 2 3 4 5 import  {createApp} from  '/@modules/vue.js' import  App  from  '/src/App.vue' import  '/src/index.css?import' createApp (App ).mount ('#app' );
 
浏览器拿到main.js后,继续解析并请求依赖的文件,服务器对请求的文件进行编译和处理,返回给浏览器运行,一直重复这个过程
编译过后的index.css
再来看看vite的几个优点
快速的冷启动(开启服务器不进行打包和编译,只是开启一个服务器返回index.html) 
即时的模块热更新 
真正的按需编译(浏览器的import语法天生支持按需引入,服务器只对浏览器请求的文件进行编译) 
 
Api和数据响应式的变化 去掉了Vue构造函数 从刚刚vite搭建的项目中可以看到,vue3不再使用new Vue()的方式来创建vue应用,而是使用createApp()来创建,为什么要这么做呢,来看下面的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <div  id ="app1" > </div > <div  id ="app2" > </div > <script >   Vue .use (...);    Vue .mixin (...);    Vue .component (...);    new  Vue ({        }).$mount("#app1" )   new  Vue ({        }).$mount("#app2" ) </script > 
 
1 2 3 4 5 6 7 <div  id ="app1" > </div > <div  id ="app2" > </div > <script > 	createApp (根组件).use (...).mixin (...).component (...).mount ("#app1" )   	createApp (根组件).mount ("#app2" ) </script > 
 
可以看出,vue2的一些方法会影响所有的vue应用,而vue3就可以解决了这个问题,让开发者可以对不同的Vue应用进行不同的配置,方便了不同Vue应用的相互隔离
另外createApp创建的是一个Vue应用,Vue应用不是一个特殊的组件,这点是和Vue2不同的,Vue3对这两个概念进行了区分,一定程度上避免了可能造成的思维混乱
最后一点就是Vue2的构造函数集成了太多功能,不利于tree shaking,Vue3把这些功能使用普通函数导出,能够充分利用tree shaking优化打包体积,所以Vue3的打包体积是小于Vue2的
使用了proxy来进行数据响应式 优点:
使用proxy比递归对象设置访问器属性要高效 
可以观测到对象的新增属性和删除属性 
可以观测到数组下标的变化 
 
缺点:
模板的变化 组件允许多个根节点 如图,下图的模板语法现在被支持了,在一个组件里允许了多个根结点而存在
1 2 3 4 5 6 7 8 <template >     <div >          wow     </div >      <div >          funny     </div >  </template > 
 
实现原理非常easy
 
编译后
1 2 3 4 5 6 7 8 9 10 import  { createElementVNode as  _createElementVNode, Fragment  as  _Fragment, openBlock as  _openBlock, createElementBlock as  _createElementBlock } from  "vue" export  function  render (_ctx, _cache, $props, $setup, $data, $options ) {  return  (_openBlock (), _createElementBlock (_Fragment, null , [     _createElementVNode ("div" ),     _createElementVNode ("div" )   ], 64  )) } 
 
v-model的升级 vue2提供了两种双向绑定:v-model和.sync,在vue3中,去掉了.sync修饰符,只需要使用v-model进行双向绑定即可。
为了让v-model更好的针对多个属性进行双向绑定,vue3作出了以下修改
当对自定义组件使用v-model指令时,绑定的属性名由原来的value变为modelValue,事件名由原来的input变为update:modelValue 
 
1 2 3 4 5 6 7 8 9 10 11 12 <ChildComponent  :value ="pageTitle"  @input ="pageTitle = $event"  /> <ChildComponent  v-model ="pageTitle"  /> <ChildComponent    :modelValue ="pageTitle"    @update:modelValue ="pageTitle = $event"  /> <ChildComponent  v-model ="pageTitle"  /> 
 
去掉了.sync修饰符,它原本的功能由v-model的参数替代 
 
1 2 3 4 5 6 7 8 9 <ChildComponent  :title ="pageTitle"  @update:title ="pageTitle = $event"  /> <ChildComponent  :title.sync ="pageTitle"  /> <ChildComponent  :title ="pageTitle"  @update:title ="pageTitle = $event"  /> <ChildComponent  v-model:title ="pageTitle"  /> 
 
详细写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template >     <div  class ="ChildView" >          <input  type ="text"  :value ="inputValue"  @input ="updateInputValue" >      </div >  </template > <script  lang ="ts" > import  {defineComponent} from  'vue' ;export  default  defineComponent ({    name : 'Child' ,     props : {         inputValue : {             type : String          }     },     methods : {         updateInputValue ($event : any ) {             this .$emit('update:inputValue' , $event.target .value )         }     } }); </script > 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template >     <div  class ="wrapper" >          <div >              <Child  v-model:inputValue ="childValue" > </Child >          </div >      </div >  </template > <script  lang ="ts" > import  {defineComponent} from  'vue' ;import  Child  from  "@/components/Child.vue" ;export  default  defineComponent ({    name : 'HelloWorld' ,     components : {         Child ,     },     data ( ) {         return  {             childValue : ""          }     } }); </script > 
 
相关文档:vue3 : v-model 
修改v-if和v-for的优先级 vue2的优先级是v-for高于v-if
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template >     <div  class ="about" >          <h1 > This is an about page</h1 >          <ul >              <li  v-for ="(item, index) in items"  v-if ="item.flag" > {{item.value}}</li >          </ul >      </div >  </template > <script  lang ="ts" >     import  {Component , Vue } from  'vue-property-decorator' ;     @Component ({})     export  default  class  About  extends  Vue  {         public items : Array <{             flag : boolean,             value : string         }> = [{             flag : false ,             value : "rua"          }, {             flag : true ,             value : "qaq"          }]     } </script > 
 
渲染结果
vue3的优先级是v-if高于v-for
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template >     <div >          <p  v-for ="(item, index) in items"  v-if ="item.flag" > {{item.value}}</p >      </div >  </template > <script > export  default  {    data ( ) {         return  {             items : [                 {                     flag : false ,                     value : "rua"                  },                 {                     flag : true ,                     value : "qaq"                  }             ]         }     } } </script > 
 
这段代码会报错
Vue效率提升 静态节点提升 编译时期会对静态节点和静态属性进行提升,用于这减少render函数中创建VNode的消耗
1 2 3 4 5 <template >     <div  id ="app" >          <div > Hello world</div >      </div >  </template > 
 
编译结果
1 2 3 4 5 6 7 8 9 10 11 12 13 import  {createVNode as  _createVNode, openBlock as  _openBlock, createBlock as  _createBlock} from  "/@modules/vue.js" const  _hoisted_1 = {    id : "app"  } const  _hoisted_2 = _createVNode ("div" , null , "Hello world" , -1  ) export  function  render (_ctx, _cache ) {    return  (_openBlock (),     _createBlock ("div" , _hoisted_1, [_hoisted_2])) } 
 
预字符串化 当编译器遇到大量连续的静态内容,将这些静态节点序列化为字符串并生成一个Static类型的VNode,静态节点在运行时会通过 innerHTML来创建真实节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template >     <div  class ="container" >          <div  class ="logo" >              <h1 > logo</h1 >          </div >          <ul  class ="nav" >              <li > <a  href ="" > menu1</a > </li >              <li > <a  href ="" > menu2</a > </li >              <li > <a  href ="" > menu3</a > </li >              <li > <a  href ="" > menu4</a > </li >              <li > <a  href ="" > menu5</a > </li >          </ul >          <div  class ="user" >              <span > {{ user.name }}</span >          </div >      </div >  </template > 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  {createVNode as  _createVNode, toDisplayString as  _toDisplayString, createStaticVNode as  _createStaticVNode, openBlock as  _openBlock, createBlock as  _createBlock} from  "/@modules/vue.js" const  _hoisted_1 = {    class : "container"  } const  _hoisted_2 = _createStaticVNode ("<div class=\"logo\"><h1>logo</h1></div><ul class=\"nav\"><li><a href=\"\">menu1</a></li><li><a href=\"\">menu2</a></li><li><a href=\"\">menu3</a></li><li><a href=\"\">menu4</a></li><li><a href=\"\">menu5</a></li></ul>" , 2 )const  _hoisted_4 = {    class : "user"  } export  function  render (_ctx, _cache ) {    return  (_openBlock (),     _createBlock ("div" , _hoisted_1, [_hoisted_2, _createVNode ("div" , _hoisted_4, [_createVNode ("span" , null , _toDisplayString (_ctx.user .name ), 1       )])])) } 
 
预字符串化在下面两种情况出现:
如果节点没有属性,有连续20个及以上的静态节点存在 
连续的节点中有5个及以上的节点是有属性绑定的节点 
 
事件处理函数缓存 对下面的模板
1 2 3 4 5 <template >     <div  class ="container" >          <button  @click ="handleClick" > </button >      </div >  </template > 
 
编译结果
1 2 3 4 5 6 7 8 9 10 11 12 import  {createVNode as  _createVNode, openBlock as  _openBlock, createBlock as  _createBlock} from  "/@modules/vue.js" const  _hoisted_1 = {    class : "container"  } export  function  render (_ctx, _cache, $props, $setup, $data, $options ) {    return  (_openBlock (),     _createBlock ("div" , _hoisted_1, [_createVNode ("button" , {         onClick : _cache[1 ] || (_cache[1 ] = (...args )=> ($setup.handleClick (...args)))     })])) } 
 
相比于vue2的编译结果
1 2 3 4 5 6 7 render (ctx ){  return  createVNode ("button" , {     onClick : function ( ){            }   }) } 
 
Block Tree 在线尝试:https://vue-next-template-explorer.netlify.app/ 
vue2在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层比较,这就浪费了大部分时间在比对静态节点上。
vue3新增了block tree这一个概念,block tree会对动态变化的节点进行标记,从而减少了vue2一层层diff的时间,在更新时也只要查找动态的节点就可以了(动态的节点会存到一个数组里用于查找),也就是说,把一个树的diff拍平成了数组的diff
你可以从vNode的dynamicChildren属性里看到动态节点,动态节点的标记是在createVNode时标记的
结构不稳定(比如v-if)和结构数量(v-for)不一样的,会重新创建block节点
比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <div >   <template  v-if ="flag" >      <div > {{name}}</div >      <div > Sakura</div >    </template >    <template  v-else >      <div > {{age}}</div >      <div > Snow</div >    </template >  </div > <div >   <span  v-for ="item in arr" > {{item}}</span >  </div > 
 
编译后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  { toDisplayString as  _toDisplayString, createElementVNode as  _createElementVNode, Fragment  as  _Fragment, openBlock as  _openBlock, createElementBlock as  _createElementBlock, createCommentVNode as  _createCommentVNode, renderList as  _renderList } from  "vue" export  function  render (_ctx, _cache, $props, $setup, $data, $options ) {  return  (_openBlock (), _createElementBlock (_Fragment, null , [     _createElementVNode ("div" , null , [       (_ctx.flag )         ? (_openBlock (), _createElementBlock (_Fragment, { key : 0  }, [             _createElementVNode ("div" , null , _toDisplayString (_ctx.name ), 1  ),             _createElementVNode ("div" , null , "Sakura" )           ], 64  ))         : (_openBlock (), _createElementBlock (_Fragment, { key : 1  }, [             _createElementVNode ("div" , null , _toDisplayString (_ctx.age ), 1  ),             _createElementVNode ("div" , null , "Snow" )           ], 64  ))     ]),     _createElementVNode ("div" , null , [       (_openBlock (true ), _createElementBlock (_Fragment, null , _renderList (_ctx.arr , (item ) =>  {         return  (_openBlock (), _createElementBlock ("span" , null , _toDisplayString (item), 1  ))       }), 256  ))     ])   ], 64  )) } 
 
PatchFlag vue2在对比每一个节点时,并不知道这个节点哪些相关信息会发生变化,因此要将节点的所有属性进行一次比对
PatchFlag可以标记一个节点具体哪些内容更新了,比如说 数字 1:代表节点有动态的 textContent 数字 2:代表元素有动态的 class 绑定 数字 3:代表xxxxx
比如下面这段代码
1 2 3 4 5 <template >     <div  class ="container"  :id ="id" >          {{name}}     </div >  </template > 
 
编译结果
1 2 3 4 5 6 7 8 export  function  render (_ctx, _cache, $props, $setup, $data, $options ) {    return  (_openBlock (),     _createBlock ("div" , {         class : "container" ,         id : $setup.id      }, _toDisplayString ($setup.name ), 9       , ["id" ])) } 
 
9这个数字就标记了div标签里可能变化的内容
Composition Api setup setup函数会在所有生命周期函数前执行,这个函数是使用Composition API的入口
1 2 3 4 5 6 7 export  default  {  setup (props, context ){                  } } 
 
context对象的成员
成员 
类型 
说明 
 
 
attrs 
对象 
同vue2的this.$attrs 
 
slots 
对象 
同vue2的this.$slots 
 
emit 
方法 
同vue2的this.$emit 
 
数据响应式的创建 
API 
传入 
返回 
备注 
 
 
reactive 
plain-object 
对象代理 
深度代理对象中的所有成员 
 
readonly 
plain-object or proxy 
对象代理 
只能读取代理对象中的成员,不可修改 
 
ref 
any 
{ value: ... } 
对value的访问是响应式的 如果给value的值是一个对象, 则会通过reactive函数进行代理 如果已经是代理,则直接使用代理 
 
computed 
function 
{ value: ... } 
当读取value值时, 会根据情况 决定是否要运行函数 
 
应用:
如果想要让一个对象变为响应式数据,可以使用reactive或ref 
如果想要让一个对象的所有属性只读,使用readonly 
如果想要让一个非对象数据变为响应式数据,使用ref 
如果想要根据已知的响应式数据得到一个新的响应式数据,使用computed 
 
响应式数据判断 
响应式数据的转化 
API 
含义 
 
 
unref 
等同于:isRef(val) ? val.value : val 
 
toRef 
得到一个响应式对象某个属性的ref格式 
 
toRefs 
把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object中返回 
 
数据监听 watchEffect
1 2 3 4 5 6 const  stop = watchEffect (() =>  {   }) stop (); 
 
watch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const  state = reactive ({ count : 0  })watch (() =>  state.count , (newValue, oldValue ) =>  {   }, options) const  countRef = ref (0 );watch (countRef, (newValue, oldValue ) =>  {   }, options) watch ([() =>  state.count , countRef], ([new1, new2], [old1, old2] ) =>  {   }); 
 
注意:无论是watchEffect还是watch,当依赖项变化时,回调函数的运行都是异步的(微队列) 
应用:除非遇到下面的场景,否则均建议选择watchEffect
不希望回调函数一开始就执行 
数据改变时,需要参考旧值 
需要监控一些回调函数中不会用到的数据 
 
生命周期函数 
vue2 option api 
vue3 option api 
vue 3 composition api 
 
 
beforeCreate 
beforeCreate 
不再需要,代码可直接置于setup中 
 
created 
created 
不再需要,代码可直接置于setup中 
 
beforeMount 
beforeMount 
onBeforeMount 
 
mounted 
mounted 
onMounted 
 
beforeUpdate 
beforeUpdate 
onBeforeUpdate 
 
updated 
updated 
onUpdated 
 
beforeDestroy 
==改== beforeUnmount 
onBeforeUnmount 
 
destroyed 
==改==unmounted 
onUnmounted 
 
errorCaptured 
errorCaptured 
onErrorCaptured 
 
- 
==新==renderTracked 
onRenderTracked 
 
- 
==新==renderTriggered 
onRenderTriggered 
 
新增钩子函数说明:
钩子函数 
参数 
执行时机 
 
 
renderTracked 
DebuggerEvent 
渲染vdom收集到的每一次依赖时 
 
renderTriggered 
DebuggerEvent 
某个依赖变化导致组件重新渲染时 
 
DebuggerEvent:
target: 跟踪或触发渲染的对象 
key: 跟踪或触发渲染的属性 
type: 跟踪或触发渲染的方式 
 
vue-router的使用 应该很好理解吧orz
1 2 3 4 5 6 7 8 9 10 11 import  {useRoute, useRouter} from  "vue-router" ;export  default  defineComponent ({    name : 'Home' ,     setup ( ) {                  let  router = useRouter ();                  let  route = useRoute ();     } }); 
 
vuex的使用 应该很好理解吧orz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  {createStore} from  'vuex' export  default  createStore ({    state : {         name : "sena"      },     mutations : {         updateName (store, payload ) {             store.name  = payload;         }     },     actions : {},     modules : {} }) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <template >     <div  class ="home" >          <input  type ="text"  v-model ="msg" >          <button  @click ="showState" > 点我获取state</button >          <button  @click ="initState" > 初始化state</button >      </div >  </template > <script  lang ="ts" > import  {defineComponent, ref, watchEffect, reactive} from  'vue' ;import  {useStore} from  "vuex" ;export  default  defineComponent ({    name : 'Home' ,     setup ( ) {         let  store = useStore ();         let  msgRef = ref (store.state .name );         watchEffect (() =>  {                          store.commit ("updateName" , msgRef.value )         });         watchEffect (() =>  {                          msgRef.value  = store.state .name ;         })         return  {             msg : msgRef,             showState ( ) {                 console .log (store.state )             },             initState ( ) {                 store.commit ("updateName" , "" )             },         }     } }); </script > 
 
refer Vite JavaScript modules 模块 Vue3 Compiler 优化细节,如何手写高性能渲染函数