2017-05-12 22 views
2

我想使用一成不变的类,而不是不可变的界面,有两个原因: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'}; 

问题与这个结构:

  • 全接口+级结构是笨拙和复制的属性定义是愚蠢的(加上容易出错)
  • 它仍然允许我,如果我使用指定无关性“传播”运营商

那么,有没有更好的方法来做到这一点?

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

大多数重视不变性的函数式语言都有一个构造,它创建一个对象的新副本,并改变某些字段,例如[F#'s'with'](https://docs.microsoft.com/en-us/docs.microsoft。COM/EN-US/DOTNET /用品/ fsharp /语言参考/复制和更新记录表达式)。 JS/TS不是强调不变性的函数式语言。你可以用'WithX(x:X)'方法来模拟它,但是如果你的对象有很多属性,那就很乏味了。一些项目([例如C#编译器](https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Syntax/Syntax.xml))使用代码生成器 –

回答

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运算符,它们允许构造无效对象(请参阅“非常糟糕!”注释)。

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