我有以下在Array.from(List)上出错的函数;

类型‘Element[]’不能赋值给类型‘ArrayLike’

如何解决这一问题并使函数同时适用于这两种类型?

const reorderArray = (list: JSX.Element[] | number[], startIndex: number, endIndex: number): JSX.Element[] | number[] => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
    return result;
  };

推荐答案

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

Typescript相关问答推荐

在Switch陈述中输入保护类

如何以Angular 形式显示验证错误消息

TS2339:类型{}上不存在属性消息

<;T扩展布尔值>;

如何避免推断空数组

如何在方法中正确地传递来自TypeScrip对象的字段子集?

Typescript 类型守卫一个类泛型?

避免混淆两种不同的ID类型

将超类型断言为类型脚本中的泛型参数

如何解决&Quot;类型不可分配给TypeScrip IF语句中的类型&Quot;?

如何创建内部和外部组件都作为props 传入的包装器组件?

TS不能自己推断函数返回类型

通过辅助函数获取嵌套属性时保留类型

React-跨组件共享功能的最佳实践

从 npm 切换到 pnpm 后出现Typescript 错误

在Nestjs中,向模块提供抽象类无法正常工作

是否可以使用元组中对象的键来映射类型

如何在没有空接口的情况下实现求和类型功能?

使用 Next.js 路由重定向到原始 URL 不起作用

如何为对象创建类型,其中键是只读且动态的,但值是固定类型?