vue3
发布挺久了,目前看在实际业务中使用问题不大,不过对于一些跑得好好的2.x
老项目而言,很难直接升到3.x
,在不升级 3.x
的情况下还想使用 composition-api
的话,有两种方式,一是使用 @vue/composition-api
,二是直接升级 vue2.7
,v2.7
版本内置了 @vue/composition-api
,所以不用手动引入了
本人参与的一个实际业务项目,引入了 vue2.7
,但是在使用 ref
等响应式 api
的时候发现效果与预期不符合,例如下述基于 vue2.7
的代码
<template><div><p>{
{ list[0] }}-{
{ data }}</p><button @click="add">Add</button></div>
</template>
<script>
import { ref } from "vue"
export default {setup() {const list = ref([0])const data = ref(0)return {data,list,add() {list.value[0] = list.value[0] + 1data.value = data.value + 1conosle.log(list.value, data.value)}}}
}
</script>
当点击 Add
按钮的时候,期望 list[0]
和 data
的值自增 1
,且页面上展示的值也能同时更新,然而点击之后,控制台打印出来的 list.value
、data.value
都没啥问题,但页面上展示的值只有 data
更新了,list[0]
却一直固定为初始值,展示出来的值依旧是之前的值,除非我将 list.value
指向一个新的地址,页面才能正常更新
add() {list.value[0] = list.value[0] + 1// 重新指定引用地址list.value = list.value.slice()
}
鄙人不才,之前一直以为只要2.x
项目引入了 @vue/composition-api
,哪怕底层不一样,用法和表现应该是和 vue3.x
差不多才对,但是碰到这个问题后我才意识到二者还是有差别的,因为我的这种写法是比较常见的,作为一个成熟的框架,vue3.x
应该不会存在这种问题,然后试了下 vue3.x
,相同的写法,确实如我所料,不需要重新指定 list.value
的引用地址,页面也能正常更新
已经很长时间没看过源码了,正好借着这个问题简单看下,从源码上搞清楚区别
vue 2.7 的实现
ref 不起作用的原因
源码基于
v2.7.13
问题是由 ref
引起的,那么就从它看起
// src/v3/reactivity/ref.ts
export function ref<T extends object>(value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {return createRef(value, false)
}
ref
的类型声明有三个,第一个的意思是如果传入了一个 Ref
类型的参数,则返回这个参数的类型;第二个的意思是传入一个任意类型的参数,返回一个包装了此任意类型的 Ref
类型,第三个的意思是可以不传入任何参数,这个时候 ts
无法自动推导类型,但你可以传入一个类型来告诉 ts
这个值的类型是什么
// src/v3/reactivity/ref.ts
function createRef(rawValue: unknown, shallow: boolean) {if (isRef(rawValue)) {return rawValue}const ref: any = {}def(ref, RefFlag, true)def(ref, ReactiveFlags.IS_SHALLOW, shallow)def(ref,'dep',defineReactive(ref, 'value', rawValue, null, shallow, isServerRendering()))return ref
}
createRef
中首先判断传入值是不是已经是一个