距离Vue3稳定版发布也已半年过去,你的团队里开始尝试了吗?还是你只是了解一些重大变化(proxy、composition api)的基本定义?反正我们团队还没开始用,我也只是刚看过一些文档。此篇文章我就带着自己的理解视角一起跟大家探讨一下Vue3的变化,以及对于我们工作中的可能影响。欢迎大家发表不同的观点。
猜想
为什么第一部分叫猜想呢?因为我感觉当我们接收一份新的知识点之前,最好先宏观的去看一下过往,然后猜测她可能的发展方向,希望支持什么能力。带着这些疑问去看知识点更有目标感和带入感,也能更快地吸收。
既然是2.x到3.0的大版本升级,大版本一定不止是缝缝补补,有大的更新和新功能,也有之前版本留下来的优化问题,所以猜想的方向有:
- 更小:体积更小
- 更快:性能更好
- 更稳定:优化之前的不稳定issue
- 更合理:写法或者性能更优雅
- 一些新特性:新功能带来便捷
- 一些废弃:肯定也要废弃一些不好的设计
- 重要的原理:肯定也要理解一些重要原理
带着以上这些猜想在Vue3里找答案。
重要变化
更小
这里的指的是Vue3在加载时的体积更小。作为一个应用框架,她的实际使用体积作为了一个很重要的衡量指标,尤其对于前端重体验的框架。想想有什么方法可以减少体积?
- 核心功能使用了更精简的实现方式:风险大,而且不明显
- 删除了很对冗余的无用功能:这个....
- 按需加载:结合es6模块化思路,比较靠谱
正房亮相
// vue2.x
import Vue from 'vue';
// vue3.0
import { 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.js
import { 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.js
import { 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.0
const asyncPage = () => import('./NextPage.vue')
// 3.0
const 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
- 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) // 0
const state = reactive({
counter: 0
})
基本看来,ref和reactive都是作用在响应式数据方面,ref主要作用在基础数据类型,reactive作用在引用类型上,那如果ref作用在引用类型上会怎么样
const refState = ref({
counter: 0
})
这里的底层调用的是reactive,更底层是Proxy,这么看二者在使用方面怎么区分呢?为什么要造两个api?先看两种代码规范
// 风格一
let x = 0
let y = 0
function 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) // 2
state.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的细节和原理还有很多,这里只是展示了其中的一部分。整体感受使用的心智成本还是有一些的,目前团队里也还没尝试,坐等各位大佬先踩坑吧。欢迎交流~
时间紧急,写作思路比较跳跃,欢迎大佬指正。