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

这更像是一个关于如何处理在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

原因,为什么我想同时改变对象属性和它的类型可能是:

  • json对象非常大,在内存中拥有它的 2 个副本会适得其反

  • json将对象深拷贝到对象mapped中非常麻烦

我不相信“我想做什么”下的代码可读性很强,不管它是否工作。我正在寻找一种干净且类型安全的方法来解决此问题。或者,或者,建议或想法来扩展打字稿的功能以解决此问题。

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


开满天机
浏览 102回答 2
2回答

墨色风雨

我不认为你想做什么是可能的。您要求打字稿更改变量类型作为副作用。但这会带来各种并发症。如果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?或者,如果您在转换之前传递对此对象的引用怎么办?function doAsyncStuff(obj: Mappable) {}doAsyncStuff(mapped)mapData(mapped)// Then later, doAsyncStuff(obj) runs but `obj` is a different type than expected我认为你能在这里得到的最接近的是一个转换为中间类型的类型保护,它支持转换前类型和转换后类型的联合,你可以在其中实际进行转换。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.但是,如果您实际上在该条件之外使用objas 类型A,那么这可能会导致运行时错误,因为类型是错误的。所以带有副作用的类型保护函数也是一个非常糟糕的主意,因为类型保护可以让你覆盖类型脚本的正常输入。我真的认为你已经在这里做了最好的方法,特别是如果输出类型与输入类型不同。将新对象不可变地构造为新类型。const mapped: Mapped = toMappedData(json);如果性能或内存是一个大问题,那么您可能不得不为此牺牲类型安全。编写健壮的单元测试,将其投射到任何单元测试,添加关于那里发生的事情的非常突出的评论。但是除非您一次处理数百 MB 的数据,否则我敢打赌这真的没有必要。

梵蒂冈之花

我目前所做的方式是:type KeyList = 'list' | 'of' | 'some' | 'keys';// Merged the types to keep the code a little shorter// Also makes clear, that I don't alter the type's structureinterface MyType<M ext boolean = true> {&nbsp; &nbsp; source: { [K in KeyList]: SomeNestedObject },&nbsp; &nbsp; sourceOrder: (M ? SomeNestedObject : (KeyList | SomeNestedObject))[];}function mapData(data: MyType<true>): MyType {&nbsp; &nbsp; const { source, sourceOrder } = data.sourceOrder&nbsp; &nbsp; for (let i = 0; i < sourceOrder.length; i++) {&nbsp; &nbsp; &nbsp; &nbsp; if (typeof sourceOrder[i] == 'string') {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sourceOrder[i] = source[sourceOrder[i]];&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return (data as unknown) as MyType;}const json: MyType<true> = JSON.parse(data);const mapped: MyType = mapData(json);// mapped now references json instead of being a clone我不喜欢这种方法的地方:它不是类型安全的。打字稿无法检查我是否正确地改变了类型因此,我必须转换为未知,这并不理想json类型不那么严格。可能会对代码建议产生负面影响该功能有一个side effect以及一个return type(排他性side effect或return type更清洁)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript