处理 TypeScript 中类型改变的副作用

Ara*_*ker 7 javascript side-effects code-readability typescript typescript-typings

这更多是一个关于如何处理具有类型改变副作用的函数的开放问题TypeScript。我知道并且强烈同意这样的观点:函数应该尽可能减少副作用(如果有的话)。

但有时,需要就地更改对象(及其类型) ,而不是使用另一个静态类型创建它的新副本。我最常遇到的原因是可读性效率减少行数

由于我最初的示例过于复杂且过于复杂(希望如此),这里是一个非常基本的示例:

type KeyList = 'list' | 'of' | 'some' | 'keys';

// Original type (e.g. loaded from a JSON file)
interface Mappable {
    source: { [K in KeyList]: SomeNestedObject },
    sourceOrder: KeyList[];
}

// Mapped Type (mapped for easier access)
interface Mapped {
    source: { [K in KeyList]: SomeNestedObject },
    sourceOrder: SomeDeepObject[];
}

// What I have to do to keep suggestions and strict types all the way
const json: Mappable = JSON.parse(data); // ignoring validation for now
const mapped: Mapped = toMappedData(json);

// What I would like to to
const mapped: Mappable = JSON.parse(data);
mapData(mapped); // mapped is now of type Mapped
Run Code Online (Sandbox Code Playgroud)

我想就地改变对象属性及其类型的原因可能是:

  • json对象非常大,在内存中拥有它的 2 个副本会适得其反
  • 创建对象的深拷贝到对象json中是很麻烦的mapped

我不相信“我想做什么”下的代码非常可读,无论它不起作用。我正在寻找一种干净且类型安全的方法来解决此问题。或者,也可以提出扩展打字稿功能以解决此问题的建议或想法。

对此的任何建议、想法和意见都非常感谢!也许我对此太过深入,看不到真正简单的解决方案。

Ale*_*yne 0

我不认为你想做的事是可能的。您要求打字稿更改变量类型作为副作用。但这会带来各种并发症。

如果mapData函数有条件运行怎么办?

const mapped: Mappable = JSON.parse(data);
if (Math.random() > 0.5) {
  mapData(mapped); // mapped is now of type Mapped
}
// What type is `mapped` here?
Run Code Online (Sandbox Code Playgroud)

或者如果在转换该对象之前传递对此对象的引用会怎么样?

function doAsyncStuff(obj: Mappable) {}
doAsyncStuff(mapped)
mapData(mapped)
// Then later, doAsyncStuff(obj) runs but `obj` is a different type than expected
Run Code Online (Sandbox Code Playgroud)

我认为您在这里可以得到的最接近的是一个类型保护器,它可以转换为中间类型,该中间类型支持转换前类型和转换后类型的联合,您可以在其中实际进行转换。

interface A {
  foo: string
}

interface B {
  foo: string
  bar: string
}

interface A2B {
  foo: string
  bar?: string
}

function transform(obj: A): obj is B {
  const transitionObj: A2B = obj
  transitionObj.bar = "abc" // Mutate obj in place from type A to type B
  return true
}

const obj: A = { foo: 'foo' }
if (transform(obj)) {
  obj // type is B in this scope
}
obj // but obj is still type A here, which could lead to bugs.
Run Code Online (Sandbox Code Playgroud)

但如果您实际上在该条件之外使用objas 类型A,则可能会导致运行时错误,因为类型错误。因此,具有副作用的类型保护功能也是一个非常糟糕的主意,因为类型保护可以让您覆盖打字稿的正常输入。


我真的认为您已经在这里采用了最好的方法,特别是如果输出类型与输入类型不同。不可变地构造新对象作为新类型。

const mapped: Mapped = toMappedData(json);
Run Code Online (Sandbox Code Playgroud)

如果性能或内存是一个巨大的问题,那么您可能不得不为此牺牲类型安全。编写强大的单元测试,将其投射到任何测试,添加关于那里发生的事情的非常突出的评论。但除非您一次处理数百 MB 的数据,否则我敢打赌这确实没有必要。