TIP
不推荐使用reactive()
的泛型参数,因为处理了深层次ref
解包的返回值与泛型参数的类型不同。
这段时间总算是得空可以把早些时候快速开发时期挖的坑填一下了,所以准备着手改造一下前人遗留下来的 useTable
这个组合式函数。
期望可以在使用 useTable
的时候可以正确推导出来 tableData
和 queryParams
对应的数据类型。
一开始的改造非常顺利,使用泛型可以正确的推导出来我需求的属性类型,直到我遇到了下面这两个TS告警:
Cannot assign type 'T[]' to type 'UnwrapRefSimple<T>[]'. ts(2322)
不能将类型“T[]”分配给类型“UnwrapRefSimple<T>[]”。ts(2322)
Argument of type 'T' is not assignable to parameter of type 'UnwrapRefSimple<T>'.ts(2345)
类型“T”的参数不能赋给类型“UnwrapRefSimple<T>”的参数。ts(2345)
当然可以选择简单暴力的使用 as
来解决这个问题,但是我总觉得是不是有一些其他更好的方式来解决。
从原因上来看是因为:
The issue seems to stem from the fact that it is impossible for TypeScript to assert at compile time if the generic type
T
inUnwrapRefSimple<T>
is going to be aBuiltin
,Ref
, etc. which would affect the conditional type branching and thus generate a different type, so it leaves theUnwrapRefSimple<T>
alone.该问题似乎源于 TypeScript 无法在编译时断言泛型类型
T
在UnwrapRefSimple<T>
中是否会成为Builtin
、Ref
等类型,这种类型判断会影响条件类型的分支选择进而生成不同的类型,因此编译器会保持UnwrapRefSimple<T>
的原样不做处理。
Parameters of type ‘T’ cannot be assigned to parameters of type ‘UnwrapRefSimple‘. ts(2345) · Issue #13755 · vuejs/core
- 更具体的分析过程可以看 深入vue3为啥不推荐reactive使用泛型最近重新翻刷了官方文档,有那么一句话,不推荐在 reactive 使用泛型 这篇文章。
所以如果不需要保持 ref/reactive
深层响应来触发视图更新,那么可以使用 shallowRef/shallowReactive
来替换。这样就可以避免因为类型解包问题导致的类型不匹配。
以下是一个简易 Demo:
export interface TableOptionType<T = unknown, Q = Record<string, unknown>> {
queryForm: Q,
tableData: T[],
}
export function useList<
T = unknown,
Q = Record<string, unknown>
>(option: TableOptionType<T, Q>) {
// const state = reactive<TableOptionType<T, Q>>(option);
const state = shallowReactive<TableOptionType<T, Q>>(option); // 使用 shallowReactive 替换 reactive
const setData = (data: T[]) => {
state.tableData = data;
};
return {
state,
setData,
};
}
但是我不确定项目中是否有一些特殊的业务做了表格行编辑功能,或者未来是否会增加这个功能。为了向后兼容和避免造成开发上的困扰,我没有选择使用 shallowRef/shallowReactive
来替换。
当前的处理方式
因为需要保持内部属性的深层响应式,所以暂时还是利用 as
断言来解决当前类型不匹配的问题。
但是仍然会考虑在未来继续调整。
🚧 其他
在查阅各种资料的时候,如果是简单的非嵌套对象,比如说下面这个 Demo
import { ref } from 'vue'
function test<T>() {
const list = ref<T[]>([]);
const li: T[] = [];
// Argument of type 'T' is not assignable to parameter of type 'UnwrapRefSimple<T>'.ts(2345)
list.value.push(...li);
}
可以调整类型声明的方式来解决 UnwrapRefSimple<T>
的类型问题:
import { ref } from 'vue'
import type { Ref } from 'vue'
function test<T>() {
const list: Ref<T[]> = ref([]);
const li: T[] = [];
list.value.push(...li);
}
📚 相关资源
- 为什么 UnwrapRefSimple 会困扰你的 Vue + TS 项目?对 Vue 3 和 TypeScript - 掘金
- 深入vue3为啥不推荐reactive使用泛型最近重新翻刷了官方文档,有那么一句话,不推荐在 reactive 使用泛型 - 掘金
- typescript - vue ref property not able to infer type correctly - Stack Overflow
- Type UnwrapRefSimple
[] is not assignable to type T[] · vuejs · Discussion #9619 - Parameters of type ‘T’ cannot be assigned to parameters of type ‘UnwrapRefSimple
‘. ts(2345) · Issue #13755 · vuejs/core - Generic array type infer incorrect · Issue #2136 · vuejs/core