猜想距离Vue3稳定版发布也已半年过去,你的团队里开始尝试了吗?还是你只是了解一些重大变化(proxy、composition api)的基本定义?反正我们团队还没开始用,我也只是刚看过一些文档。此篇文章我就带着自己的理解视角一起跟大家探讨一下Vue3的变化,以及对于我们工作中的可能影响。欢迎大家发表不同的观点。
为什么第一部分叫猜想呢?因为我感觉当我们接收一份新的知识点之前,最好先宏观的去看一下过往,然后猜测她可能的发展方向,希望支持什么能力。带着这些疑问去看知识点更有目标感和带入感,也能更快地吸收。
既然是2.x到3.0的大版本升级,大版本一定不止是缝缝补补,有大的更新和新功能,也有之前版本留下来的优化问题,所以猜想的方向有:
- 更小:体积更小
- 更快:性能更好
- 更稳定:优化之前的不稳定issue
- 更合理:写法或者性能更优雅
- 一些新特性:新功能带来便捷
- 一些废弃:肯定也要废弃一些不好的设计
- 重要的原理:肯定也要理解一些重要原理
带着以上这些猜想在Vue3里找答案。
重要变化更小
这里的指的是Vue3在加载时的体积更小。作为一个应用框架,她的实际使用体积作为了一个很重要的衡量指标,尤其对于前端重体验的框架。想想有什么方法可以减少体积?
- 核心功能使用了更精简的实现方式:风险大,而且不明显
- 删除了很对冗余的无用功能:这个....
- 按需加载:结合es6模块化思路,比较靠谱
正房亮相
// vue2.ximport Vue from 'vue';// vue3.0import { withDirectives, vShow, createApp, ref, reactive } from 'vue';复制代码
果然,3.0使用了tree-shaking(不知道这个名词的,自行补习),新版本里将Vue源码进行了模块化,这样按需加载很大程度上减少了实际使用的体积。命中!
更快
我想这里大家都很关心新版本里有没有新的算法思路(好像很多人都关注这个,好像记得算法就很厉害,我总感觉搞错了重点),但是大版本里去探讨一下算法的优化思路是有必要的。没错,这个更快肯定就是性能的更快,算法优化。
那么猜想优化之前,我们看看目前的diff算法这部分有什么优化空间?
首先,2.x的diff算法是双端比较法,因篇幅问题,这个不清楚的自行去补习。这种双端比较法还有什么优化空间?
- 双端进行四次比较,有没有办法更快地明确当前的节点之间需要哪种比较形式,将大部分的比较次数减少成一次
- 有没有办法减少dom的移动成本
- 加上key会提升diff效率,那么会不会默认所有的vnode都加上key
- 减少dom创建和方法的绑定
- 没有key的不确定节点对比更简化一些
正房亮相,篇幅问题简化表述逻辑
- 将VNode节点进行含有key和不含有key的两种类型区分; --- 不含有Key的节点 ----- 取新旧节点的最小长度,依据旧节点遍历最小长度里的公用节点进行复用 ----- 如果新节点长于旧节点,那么创建剩下的节点 ----- 如果新节点短于旧节点,那么删除旧节点里的多余节点 --- 含有key的节点 ----- 从头部开始寻找相同的节点,一旦发现不同跳出寻找 ----- 从尾部开始寻找相同的节点,一旦发现不同跳出寻找 ----- 剩下的节点里进行三层逻辑 ------- 如果删除操作,直接删除老节点 ------- 如果是新增操作,新增操作即可 ------- 剩下的是不确定的关系,可能是替换或者位置变动 --------- 这部分先找到最长递增子序列,定义需要toBePatched数组,然后记录在老节点里index,这个数组的值取最长递增子序列,这个序列作为标准,然后剩下的就是判定是新增节点还是需要移动的节点复制代码
diss:通过这里,其实Vue在暗示绑定key性能更好,这很react,Vue你还是自行做好性能优化逻辑吧,减少用户的使用心智才是生存之道。
更稳定超速了,我没有想到有什么更稳定的Case,再结合2.x升级到3.0的阶段,稳定性不再是问题。
实际情况,我也确实没看到这方面的问题,如有漏下,请大神提醒。
更合理这里就不猜测了,简单列举一些常用的合理api。
- teleport
我们可以在组件内将某部分结构渲染到document文档流的任意位置,经典的场景就是在某个组件里需要渲染一个全局的结构(弹框),这确实更合理。react好像已经有了
- 自定义事件
// 声明组件的将事件名称暴露,对于使用者来说比较直观,特别适合放在封装起来的组件里用,当然事件名称需要易读,否则效果不佳app.component('custom-form', { emits: ['in-focus', 'submit'] })// 可以针对自定义事件做逻辑预处理,同时,原生事件会被自定义事件取代app.component('custom-form', { emits: { submit: (...args) => { // check// return boolean value} },methods: { submitForm() { this.$emit('submit', { ...args }) } } })复制代码
- 支持多个根节点
再也不需要去多写一个无谓的根节点
- 自定义指令
这里做了api的一个规范化,让用户使用的过程中心智负担更少
bind -> beforeMount inserted -> mounted beforeUpdate 新增 update 删除 componentUpdated updated beforeUnmount 新增 unbind -> unmounted复制代码
- unmounted取代destoryed
- data始终为function
- mixin支持浅合并
浅合并不多解释了,关于这个意图不知道这部分是不是想弱化mixin的能力,更加明确其使用范围,是不是也跟后面提到的composition api有关呢,毕竟二者有些相似点。
新变化——一些新增和废弃篇幅问题,这里也是简单介绍一些常用的
一些新增
- ts
是的,3.0支持ts了,并且是optional选项,不强制哦~
- createApp
这是个更细致的处理,将原本一个全局Vue实例拆分成各自组件的独立应用实例,这么做的好处是:一些全局的api不再因为一个局部组件需求的调整而影响其他组件,任何全局改变Vue行为的api都会移动到实例应用,各个应用实例使用全局能力各自引用配置,各自改造,互不影响。
- v-model
<user-name v-model:first-name="firstname" v-model:last-name="lastname"></user-name>// 支持修饰符<user-name v-model:first-name.capitalize="first-name"></user-name>复制代码
是的,支持传递参数,将使用更加规范、更加灵活。在此之上又增加了修饰符的功能,通过约定的this.modelModifiers判定修饰符的存在,进而进行相应地扩展处理。
- composition api
组合式api算是本次更新的最大亮点,也是对于开发者书写习惯挑战最大的部分。下面详细聊一下这部分的内容。
1、解决什么问题?
在2.x的开发习惯里,单文件组件是options的书写方式,这种的书写方式会有一个问题,变量和业务逻辑是分开的方式,这种情况下代码的复用性就很难做到,当组件越来越大的时候,代码就变得很冗长,并且排查问题也会带来麻烦,经常是翻到顶部看变量定义,然后回到底部去确认逻辑关系。那么composition api的出现就是为了解决这类问题的,实现变量与逻辑的绑定,进而实现复用的能力,同时也会让页面代码长度变短,提升代码的可读性。
2、怎么用?
// useUserRepositories.jsimport { ref, onMounted, watch } from 'vue'import { fetchUserRepositories } from '@/api/respositories'export default function useUserRepositories(user) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(user.value) } onMounted(getUserRepositories) watch(user, getUserRepositories) return { repositories, getUserRepositories } }// main.jsimport { toRefs } from 'vue'import useUserRepositories from '@/useUserRepositories'export default { props: {user: { type: String } }, setup(props) {const { user } = toRefs(props)const { repositories, getUserRepositories } = useUserRepositories(user) return { getUserRepositories } } }复制代码
关于逻辑复用的能力体现的十分明显,将变量和逻辑的复用能力下沉,同时支持变量透传,减少底层的耦合性。
3、与mixin的区别是什么?
composition api很容易与mixin联想到一起,都是在组件内部注入一部分逻辑。但实际二者还是有很大的差别:
a. 层级不同——composition api与组件是嵌套关系,而mixin与组件是同层级关系
b. 影响面不同——compostion api作为组件的被调用方,并且变量逻辑是组件控制,耦合性很低,而mixin是耦合在代码逻辑里,并且存在变量的互相引用,为将来的升级和维护埋下隐患。不清楚mixin的合并逻辑转化成浅比较是不是也是想要降低这种隐患做的处理。
- defineAsyncComponent
import { defineAsyncComponent } from 'vue'// 2.0const asyncPage = () => import('./NextPage.vue')// 3.0const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))// 3.0带选项const asyncPage = defineAsyncComponent({ loader: () => import('./NextPage.vue'), delay: 200, timeout: 3000, error: ErrorComponent, loading: LoadingComponent })复制代码
一些废弃
- v-on支持keycode
- on,off, $once
- Filter
- Destroy
- 内联模板
这部分知道就好,不熟悉不知道也不重要,反正不用。
重要原理这块咱就必须得唠唠很重要的两个概念,这关系到我们用3.0最重的一部分
ref vs reactive
1、先关注一下使用规范
import { ref, reactive } from 'vue';const counter = ref(0);console.log(counter); // { value: 0 }console.log(counter.value) // 0const state = reactive({ counter: 0})复制代码
基本看来,ref和reactive都是作用在响应式数据方面,ref主要作用在基础数据类型,reactive作用在引用类型上,那如果ref作用在引用类型上会怎么样
const refState = ref({ counter: 0})复制代码
这里的底层调用的是reactive,更底层是Proxy,这么看二者在使用方面怎么区分呢?为什么要造两个api?先看两种代码规范
// 风格一let x = 0let y = 0function updatePos(e) { x = e.pageX y = e.pageY }// 风格二const pos = { x:0, y:0 }function updatePos(e) { pos.x = e.pageX pos.y = e.pageY }复制代码
这部分内容自行体会。另外提一个点:多次定义的ref可以合并到一个reactive里,而一个reactive可以拆分成多个ref,这里依据需求可以自行斟酌。
关于ref还有两个重要的api需要介绍,首先看一下toRef
const state = reactive({ foo: 1, bar: 2})const fooRef = toRef(state, 'foo') fooRef.value++console.log(state.foo) // 2state.foo++console.log(fooRef.value) // 3复制代码
这个api适合放在父组件里使用,然后将某个字段传递到子组件里,并且保证字段的响应式能力。另一个看一下toRefs
function useFeatureX() { const state = reactive({foo: 1,bar: 2 }) // return state return toRefs(state) }export default { setup() {// const state = useFeatureX()const { foo, bar } = useFeatureX() // return {// foo: state.foo,// bar: state.bar// } return { foo, bar } } }复制代码
解构reactive进而保持其响应式的能力。
下面探讨一下这方面的原理实现。
export function ref(raw: unknown) { if (isRef(raw)) {return raw } raw = convert(raw) const r = {_isRef: true,get value() { track(r, OperationTypes.GET, '') return raw },set value(newVal) { raw = convert(newVal) trigger(r, OperationTypes.SET, '') } } return r as Ref }const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val复制代码
reactive的原理,简化的流程如下:
总结关于Vue3的细节和原理还有很多,这里只是展示了其中的一部分。整体感受使用的心智成本还是有一些的,目前团队里也还没尝试,坐等各位大佬先踩坑吧。欢迎交流~
时间紧急,写作思路比较跳跃,欢迎大佬指正。