打字稿 - 如何操作不可变类?

时间:2017-05-12 06:17:30

标签: typescript

我想使用不可变类而不是不可变接口,原因有两个:

  • 以便我可以使用它们捆绑一些代码(例如doStuff方法)
  • 禁止"无关的财产" (参见下面的示例:设置otherThing的任何地方都有效,我希望这会导致编译错误)

这是我能够提出的最接近我想要的东西:

interface IData4 {
  readonly thing1: string;
  readonly thing2: string;
}

class Data4 implements IData4 {
  readonly thing1: string;
  readonly thing2: string;

  constructor(that: IData4, props?: Partial<IData4>){
    Object.assign(this, that);
    if( props ){
      Object.assign(this, props);
    }
  }

  doStuff(){
    return this.thing1 == this.thing2;
  }
}

// more verbose than a normal ctor parameter list, but I like it better anyway
// more readable, and it makes transposition errors less likely when all
// the params are the same type
// I write code to create instances rarely, reading and updating is more frequent
let data4 = new Data4({thing1: "t1", thing2: "t2"});

// GOOD!: error because of "otherThing"
// let other = new Data4({thing1: "t1", thing2: "t2", otherThing: 'blah'});

// GOOD!: error because missing "thing2"
// let other = new Data4({thing1: "t1"});

// this is the usual update case
let data4a = new Data4(data4, {thing2: "t2a"});
log.debug("data4a: " + JSON.stringify(data4a));

// GOOD!  error because of "otherThing"
// let other = new Data4(data4, {thing2: "t2b", otherThing: 'blah'});

// BAD!  want "otherThing" to cause error
// but I'm unlikely to use this construct, I wouldn't specify the spread 
// operator again because I already specified the thing to copy from
let bad2b = new Data4(data4, {...data4, thing2: "t2b", otherThing: 'blah'});

// BAD! want "otherThing" to cause error 
// easy to do accidentally because I'm used to using interfaces 
let bad2c = new Data4({...data4, thing2: "t2b", otherThing: 'blah'});

let iData: IData4 = {thing1: 't1', thing2: 't2'};
// BAD! want an error about "otherThing" 
let iData2 = {...iData, thing2: 't2a', otherThing: 'blah'};

// GOOD! error because of "otherThing"
// let other: IData4 = {thing1: 't1', thing2: 't2', otherThing: 'blah'};

// GOOD! error because lack of "thing2"
// let other: IData4 = {thing1: 't1'};

此构造的问题:

  • 整个Interface + Class构造很笨重,重复属性定义很愚蠢(加上容易出错)
  • 如果我使用&#34; spread&#34;它仍然允许我指定无关的属性运营商

那么,有没有更好的方法呢?

  • 有没有办法用我想要的接口打包函数?
  • 或者有更好的类型而不是&#34; Partial&#34;我应该用吗?
    • 是否有某种方法可以实现与Partial大致相同的功能但不允许设置无关的属性?
  • 或者在Typescript中有哪些方法我可以使用带有参数列表的普通构造函数来创建一个具有已更改属性的新对象,但不必指定每个属性?
    • 具体来说,我不想做:let data4a = new Data4(data4.thing1, "t2a"});

1 个答案:

答案 0 :(得分:0)

这不是很好,但我确实找到了一个问题的答案“有没有办法用接口打包功能”?

您可以使用"declaration merging"制作与功能近似的接口:

interface Data7{
  readonly thing1: string;
  readonly thing2: string;
}

namespace Data7{
  export function areThingsEqualLength(value: Data7): boolean {
    return value.thing1.length == value.thing2.length;
  }
}

let data7: Data7 = {thing1: "t1", thing2: "t2"};
let data7a = {...data7, thing2: "t2a"};
log.debug("data7a: " + JSON.stringify(data7a));

log.debug("same length thing?: " + Data7.areThingsEqualLength(data7));

// GOOD!
// error: "'otherThing' does not exist in type"
// let other7a: Data7 = {thing1: "t1", thing2: "t2a", otherThing: 'blah'};

// GOOD!
// error: "Property 'thing1' is missing in type '{ thing2: string; }'."
// let other7b: Data7 = {thing2: 't2b'};

// BAD!
// want it to fail on 'otherThing' 
let data7b = {...data7, otherThing: 'blah'};

// VERY BAD!
// allows creation of an invalid object.
// The function call will explode, even though it's written correctly
let badData7 = {...data7, thing2: undefined};
// log.debug("same length thing?: " + Data7.areThingsEqualLength(badData7));

如果愿意,您可以使用导入将Data7.areThingsEqualLength(data7)变为areThingsEqualLength(data7)

但我也意识到我可能想避免使用接口:使用它们附带的spread运算符,它们允许构造无效对象(请参阅“非常糟糕!”注释)。

所以标题问题仍然存在(也许这不应该是一个答案,也许它应该是对问题的编辑,但那时它会很长)。