为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

开满天机 2022-12-02 16:51:07
这更像是一个关于如何处理在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 wayconst json: Mappable = JSON.parse(data); // ignoring validation for nowconst mapped: Mapped = toMappedData(json);// What I would like to toconst mapped: Mappable = JSON.parse(data);mapData(mapped); // mapped is now of type Mapped原因,为什么我想同时改变对象属性和它的类型可能是:该json对象非常大,在内存中拥有它的 2 个副本会适得其反json将对象深拷贝到对象mapped中非常麻烦我不相信“我想做什么”下的代码可读性很强,不管它是否工作。我正在寻找一种干净且类型安全的方法来解决此问题。或者,或者,建议或想法来扩展打字稿的功能以解决此问题。非常感谢对此的任何建议、想法和评论!也许我对此太深入了,看不到真正简单的解决方案。
查看完整描述

2 回答

?
墨色风雨

TA贡献1853条经验 获得超6个赞

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


如果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 的数据,否则我敢打赌这真的没有必要。


查看完整回答
反对 回复 2022-12-02
?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

我目前所做的方式是:


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 structure

interface MyType<M ext boolean = true> {

    source: { [K in KeyList]: SomeNestedObject },

    sourceOrder: (M ? SomeNestedObject : (KeyList | SomeNestedObject))[];

}


function mapData(data: MyType<true>): MyType {

    const { source, sourceOrder } = data.sourceOrder

    for (let i = 0; i < sourceOrder.length; i++) {

        if (typeof sourceOrder[i] == 'string') {

            sourceOrder[i] = source[sourceOrder[i]];

        }

    }


    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 effectreturn type更清洁)


查看完整回答
反对 回复 2022-12-02
  • 2 回答
  • 0 关注
  • 101 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信