TypeScript: Recursive Deep Mutable with Generics. Error: T is not assignable to Mutable<T>

TypeScript: Recursive Deep Mutable with Generics. Error: T is not assignable to Mutable<T>

我正在尝试编写深度递归 Mutable 类型:

Mutable<T> = ...
    // remove all "readonly" modifiers
    // convert all ReadonlyArray to Array
    // etc.
    // and do it all recursively

const mutate = <T>(val: T, mutateFn: (mutableVal: Mutable<T>) => void): T => {
  return null as any
}

只要我不使用泛型就可以正常工作:

// works fine
mutate([] as ReadonlyArray<string>, mutableVal => {
  mutableVal.splice(42, 0, "test");
});

但是在通用函数中使用它时出现错误:

// Error: Argument of type 'T' is not assignable to parameter of type 'Mutable<T>'.
const doSomething = <T>(array: ReadonlyArray<T>, index: number, elem: T) => {
  mutate(array, mutableVal => {
                      // Here's the error
                      //         v
    mutableVal.splice(index, 0, elem);
  });
}

我知道,可变数组的类型是 Array<Mutable<T>>splice 现在需要一个 Mutable<T> 值,而不是 T。但是我不知道怎么解决。

你知道如何解决这个问题吗?

我创建了一个 TypeScript Playground,因此您可以玩代码: Link to TypeScript Playground

我的建议是这样做:

const doSomething = <T>(array: ReadonlyArray<T>, index: number, elem: T) => {
    mutate({ array: array, elem: elem }, mutableVal => {
        mutableVal.array.splice(index, 0, mutableVal.elem);
    });
}

想法是您需要 elem 可变才能将其添加到深度可变数组中,但您的原始调用并没有这样做。因为你想同时改变 array 和可能的 elem,最直接的解决方案是传入一个同时包含 arrayelem 的对象,然后对 deep-mutable 进行操作那个版本。

只有您知道在 elemarray 上调用 mutate() 是否可以接受,因为 mutate() 的实现被排除在外。我猜它会涉及这样的断言:

const mutate = <T>(val: T, mutateFn: (mutableVal: Mutable<T>) => void): T => {
    mutateFn(val as Mutable<T>); //‍♀️
    return val;
}

在这种情况下,我会说 "who cares" 你是在 elem 上调用 mutate(),还是只是将 elem 断言到 [=24] 中的可变对象=].另一方面,如果您的实现涉及克隆,那么您应该考虑在 elem 上调用它是否有意义。

好的,希望对您有所帮助。祝你好运!

使用来自 utility-types 的 DeepNonNullable 作为灵感和我想出的 99% 的代码:

import { Mutable } from 'utility-types';

export type DeepMutable<T> = T extends (...args: any[]) => any
  ? T
  : T extends any[]
  ? DeepMutableArray<T[number]>
  : T extends object
  ? DeepMutableObject<T>
  : T;
/** @private */
export type DeepMutableArray<T> = Array<DeepMutable<Mutable<T>>>;
/** @private */
export declare type DeepMutableObject<T> = {
  [P in keyof T]-?: DeepMutable<Mutable<T[P]>>;
};