vue作者尤雨溪在开发 vue3.0 的时候开发的一个基于浏览器原生 ES imports 的开发服务器(开发构建工具)。那么我们先来了解一下vite
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。它做到了本地快速开发启动, 用 vite 文档上的介绍,它具有以下特点:
使用 npm:
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest -- --template vue
$ cd
$ npm install
$ npm run dev
或者 yarn:
$ yarn create vite --template vue
$ cd
$ yarn
$ yarn dev
{{ msg }}
被编译成:
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("span", null, "static"),
_createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
首先静态节点进行提升,会提升到 render 函数外面,这样一来,这个静态节点永远只被创建一次,之后直接在 render 函数中使用就行了。
Vue在运行时会生成number(大于0)值的PatchFlag,用作标记,仅带有PatchFlag标记的节点会被真正追踪,无论层级嵌套多深,它的动态节点都直接与Block根节点绑定,无需再去遍历静态节点,所以处理的数据量减少,性能得到很大的提升。
{{msg}}
优化前:
import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("span", { onClick: _ctx.onClick }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["onClick"])
]))
}
onClick会被视为PROPS动态绑定,后续替换点击事件时需要进行更新。
优化后:
import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("span", {
onClick: _cache[1] || (_cache[1] = $event => (_ctx.onClick($event)))
}, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
会自动生成一个内联函数,这个内联函数里面再去引用当前组件最新的onclick,然后把这个内联函数cache起来,第一次渲染的时候会创建内联函数并且缓存,后续的更新就直接从缓存里面读同一个函数,既然是同一个函数就没有再更新的必要,就变成了一个静态节点
3. SSR速度提高
当有大量静态的内容时,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板 插值嵌入进去,这样会比通过虚拟dom来渲染的快很多。vue3.0 当静态文件大到一定量的时候,会用_ceratStaticVNode方法在客户端去生成一个static node, 这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染
tree-shakinng 原理
主要依赖es6的模块化的语法,es6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,
分析程序流,判断哪些变量未被使用、引用,进而删除对应代码
前提是所有的东西都必须用ES6 module的import来写
按照作者的原话解释,Tree-shaking其实就是:把无用的模块进行“剪枝”,很多没有用到的API就不会打包到最后的包里
在Vue2中,全局 API 如 Vue.nextTick() 是不支持 tree-shake 的,不管它们实际是否被使用,都会被包含在最终的打包产物中。
而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果你不使用其某些功能,它们将不会包含在你的基础包中
5. compositon Api
没有Composition API之前vue相关业务的代码需要配置到option的特定的区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高
compositon api提供了以下几个函数:
vue2使用全局api 如 Vue.component, Vue.mixin, Vue.use等,缺点是会导致所创建的根实例将共享相同的全局配置(从相同的 Vue 构造函数创建的每个根实例都共享同一套全局环境。这样就导致一个问题,只要某一个根实例对 全局 API 和 全局配置做了变动,就会影响由相同 Vue 构造函数创建的其他根实例。)
vue3 新增了createApp,调用createApp返回一个应用实例,拥有全局API的一个子集,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上
setup (入口函数,接收两个参数(props,context))
ref (将一个原始数据类型转换成一个带有响应式特性)
reactive (reactive 用来定义响应式的对象)
watchEffect
watch
computed
toRefs (解构响应式对象数据)
生命周期的hooks
如果用ref处理对象或数组,内部会自动将对象/数组转换为reactive的代理对象
ref内部:通过给value属性添加getter/setter来实现对数据的劫持
reactive内部:通过使用proxy来实现对对象内部所有数据的劫持,并通过Reflect反射操作对象内部数据
ref的数据操作:在js中使用ref对象.value获取数据,在模板中可直接使用
import { useRouter } from 'vue-router'
import { reactive, onMounted, toRefs } from 'vue'
// setup在beforeCreate 钩子之前被调用
// setup() 内部,this是undefined,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆
// props 是响应式的,当传入新的 prop 时,它将被更新(因为props是响应式的,所以不能使用 ES6 解构,因为它会消除 prop 的响应性。)
// props参数:包含组件props配置声明且传入了的所有props的对象
// attrs参数:包含没有在props配置中声明的属性对象,相当于this.$attrs
// slots参数:包含所有传入的插槽内容的对象,相当于this.$slots
// emit参数:可以用来分发一个自定义事件,相当于this.$emit
setup (props, {attrs, slots, emit}) {
const state = reactive({
userInfo: {}
})
const getUserInfo = async () => {
state.userInfo = await GET_USER_INFO(props.id)
}
onMounted(getUserInfo) // 在 `mounted` 时调用 `getUserInfo`
// setup的返回值
// 一般都是返回一个对象,为模板提供数据,就是模板中可以直接使用此对象中所有属性/方法
// 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
// 返回对象中的方法会与methods中的方法合并成组件对象的方法
// 若有重名,setup优先
return {
...toRefs(state),
getUserInfo
}
}
灵活的逻辑组合与复用
可与现有的Options API一起使用
与选项API最大的区别的是逻辑的关注点
选项API这种碎片化使得理解和维护复杂组件变得困难,在处理单个逻辑关注点时,我们必须不断地上下翻找相关代码的选项块。
compositon API将同一个逻辑关注点相关代码收集在一起
6. Fragment(碎片)
...
...
Vue 3不再限于模板中的单个根节点,它正式支持了多根节点的组件,可纯文字,多节点,v-for等
render 函数也可以返回数组
7. Teleport(传送门)
这个组件的作用主要用来将模板内的 DOM 元素移动到其他位置。
允许我们控制在 DOM 中哪个父节点下渲染了 HTML
I'm a teleported modal!
(My parent is "body")
更好的Typescript支持
vue3是基于typescipt编写的,可以享受到自动的类型定义提示
vue官方实现的 createApp 会给我们的 template 映射生成 html 代码,但是要是你不想渲染生成到 html ,而是要渲染生成到 canvas 之类的不是html的代码的时候,那就需要用到 Custom Renderer API 来定义自己的 render 渲染生成函数了。
意味着以后可以通过 vue, Dom 编程的方式来进行canvas、webgl 编程
默认的目标渲染平台
响应原理的变化
vue2对象响应化:遍历每个key,通过 Object.defineProperty API定义getter,setter 进而触发一些视图更新
数组响应化:覆盖数组的原型方法,增加通知变更的逻辑
vue2响应式痛点
递归,消耗大
新增/删除属性,需要额外实现单独的API
数组,需要额外实现
Map Set Class等数据类型,无法响应式
修改语法有限制
vue3响应式方案: 使用ES6的Proxy进行数据响应化,解决上述vue2所有痛点,Proxy可以在目标对象上加一层拦截/代理,外界对目标对象的操作,都会经过这层拦截。Proxy可以在目标对象上加一层拦截/代理,外界对目标对象的操作,都会经过这层拦截,相比 Object.defineProperty ,Proxy支持的对象操作十分全面