TypeScrip不会自动分发超过union types个的分析.因此,Array.from(x)
其中x
是类型T[] | U[]
不会是T[] | U[]
;您可以期望的最好结果是类型(T | U)[]
的结果,这将需要显式指定类型参数(因为编译器不希望在推理期间使用synthesize个新的联合类型):
const reorderArray = (
list: number[] | JSX.Element[], startIndex: number, endIndex: number
) => {
const result = Array.from<number | JSX.Element>(list);
// --------------------> ^^^^^^^^^^^^^^^^^^^^^^
// manually specify type argument
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// return type is (number | JSX.Element)[]
也许这是你可以接受的.
如果不是,并且您想要区分number[]
输出和JSX.Element[]
输出,则建议使用generics而不是联合:
const reorderArray = <T extends JSX.Element | number>(
list: T[], startIndex: number, endIndex: number
): T[] => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
现在,编译器理解关于单个泛型类型参数T
的所有操作.当您仅使用number[]
或JSX.Element[]
调用该函数时,编译器可以推断出T
的类型参数,这将产生一个单一缩小类型的数组:
const n = [1, 2, 3];
const o = reorderArray(n, 1, 2);
// const o: number[]
const p = [<div />, <p />, <h1 />]
const q = reorderArray(p, 1, 2);
// const q: JSX.Element[]
这仍然不会为您提供联合输入的联合输出:
const r = Math.random() < 0.5 ? n : p;
const s = reorderArray(r, 1, 2); // error, just like original Array.from error
const t = reorderArray<number | JSX.Element>(r, 1, 2);
// const t: (number | JSX.Element)[] // array of unions, not union of arrays
但是,如果您计划只对已知元素类型的数组(而不是数组类型的联合)调用reorderArray()
,那么这并不重要.
如果您希望need的输出是数组的并集,那么事情就会变得更复杂.归根结底,该语言不能很好地支持correlated union types.请参见microsoft/TypeScript#30581.处理这一问题的方法通常是使用type assertion,然后继续:
const reorderArray2 = (
list: number[] | JSX.Element[], startIndex: number, endIndex: number
): number[] | JSX.Element[] => {
const result = Array.from<number | JSX.Element>(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result as number[] | JSX.Element[]; // <-- assert
};
或者编写冗余代码以强制编译器处理以下情况:
const reorderArray3 = (
list: number[] | JSX.Element[], startIndex: number, endIndex: number
): number[] | JSX.Element[] => {
if (list.every((i): i is number => typeof i === "number")) {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
} else {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
}
};
或者像microsoft/TypeScript#47109中描述的那样重构到特定风格的泛型,这是许多难以解释的 skip :
interface TypeMap {
n: number;
e: JSX.Element
}
type ArrayType<K extends keyof TypeMap> = { [P in K]: Array<TypeMap[P]> }[K]
const reorderArray4 = <K extends keyof TypeMap>(
list: ArrayType<K> & {}, startIndex: number, endIndex: number
): ArrayType<K> & {} => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const reorderArray5 = reorderArray4<keyof TypeMap>
// const reorderArray5: (
// list: number[] | JSX.Element[],
// startIndex: number,
// endIndex: number
// ) => number[] | JSX.Element[]
最后一个函数正是您所要求的,但是让编译器遵循逻辑的过程非常复杂,除非您的用例需要,否则我不推荐使用它.
Playground link to code个