【问题标题】:TypeScript: Recursive Deep Mutable with Generics. Error: T is not assignable to Mutable<T>TypeScript:具有泛型的递归深度可变。错误:T 不可分配给 Mutable<T>
【发布时间】:2019-05-18 20:50:52
【问题描述】:

我正在尝试编写一个深度递归的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&lt;Mutable&lt;T&gt;&gt;,而splice 现在需要Mutable&lt;T&gt; 值,而不是T。但我不知道如何解决它。

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

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

【问题讨论】:

  • TMutable&lt;T&gt; 显然不兼容,但是您想如何处理呢?简单的选择是将elem 键入为Mutable&lt;T&gt;。另一种方法是不要对数组进行深度可变...很大程度上取决于您的用例您想要做什么
  • 我不确定如何处理这个问题,这就是我创建这个问题的原因。 TMutable&lt;T&gt; 应该始终具有相同的结构:{ readonly foo: string } 变为 { foo: string }ReadonlyArray&lt;Foo&gt; 变为 Array&lt;Foo&gt;。用例就像我发布的代码中一样:mutate 函数。但在某些情况下,我想在另一个函数(如doSomething)中使用mutate 函数,然后它就不再起作用了。当然我可以写elem as Mutable&lt;T&gt;,它工作正常。但我希望找到一些开箱即用的解决方案。

标签: typescript generics mutable typescript3.0


【解决方案1】:

我的建议是这样做:

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 的对象,并对其进行深度可变版本的操作。

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

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

在这种情况下,我会说“谁在乎”,无论您是在 elem 上调用 mutate(),还是只是将 elem 断言到 doSomething() 内的可变对应项。另一方面,如果您的实现是涉及克隆的更高级的东西,那么您应该考虑在elem 上调用它是否有意义。

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

【讨论】:

  • 这也是一个可能的解决方案。谢谢你。基本上mutate 函数应该使 one 对象(或数组)可变。如果您只想改变数组,elem 不需要是可变的。如果你想改变elem,你可以调用mutate(elem, ...)。或者,如果你想同时改变两者,你可以为两者调用mutate(或者在你的代码中:创建一个新对象,两者都在里面)。有很多解决方案(你甚至可以在任何地方使用as any)。我只是认为有可能保持简单,让 TypeScript 在幕后工作。
  • "如果您只想改变数组,elem 不需要是可变的。"但是您不能将不可变元素添加到深度可变数组中,对吗?这就是它“深”的原因。如果你只想改变一个数组但不要求它的内容也是可变的,那么你只需要一个普通的Array&lt;T&gt;,而不是Array&lt;Mutable&lt;T&gt;&gt;,这就是Mutable&lt;ReadonlyArray&lt;T&gt;&gt; 的样子。
  • 在我看来你真的想要一些不一致的东西。您的选择是制作array 的浅可变版本(这意味着您不能像上面在array 上定义的那样调用mutate()),制作elem 的可变版本(这意味着您需要调用@ 987654351@ both arrayelem),或者您只想通过仅在 array 上调用 mutate() 并传入 elem as Mutable&lt;T&gt; 而不是elem。但在最后一种情况下,您将不得不故意使(有效的)编译器消息警告您正在尝试做一些不安全的事情。
  • 是的,这是真的。我还没有考虑过这种情况...... “如果你将不可变的elem 添加到可变的array 然后尝试改变elem 呢?” 我的第一个想法是: 禁止对elem 进行变异,即使将其添加到数组中也是如此。但是之前数组中的所有内容都应该是可变的。也许我需要再睡一晚 :D 不管怎样:谢谢你的提示!
猜你喜欢
  • 2018-09-19
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-10
  • 2019-01-08
  • 2017-12-29
相关资源
最近更新 更多