2016-03-29 244 views
1

我想在打字稿小的不可变类:打字稿:联合构造类型

import * as _ from "lodash"; 

export class Immutable<T> { 

    constructor(public data:T) { 
    Object.freeze(data); 

    _.each(_.keysIn(data), (key) => { 
     Object.defineProperty(this, key, <PropertyDescriptor> {get:() => this.data[key]}) 
    }) 
    } 

    set(key:string, val:any):Immutable<T> { 
    if (_.isEqual(this.data[key], val)) { 
     return this; 
    } 

    let newData = {}; 
    newData[key] = val; 
    return new Immutable<T>(_.defaults<T>(newData, this.data)) 
    } 

    update(data:T) { 
    let newVal = _.defaults<T>(data, this.data); 

    return _.isEqual(this.data, newVal) ? this : new Immutable<T>(newVal); 
    } 

    get(key):any { 
    return this.data[key]; 
    } 

    toJson():T { 
    return this.data; 
    } 
} 

现在,我必须手动添加创建一个不可变的这样const instance =<Immutable<{x: number}> & {x: number}> new Immutable({x: 1});当T,让我可以访问X与instance.x。我知道有一种解决方法,将new(): Immutable<T> & T定义为某处的构造函数方法,但我只是找不到我记忆中的资源。任何人都可以指引我正确的方向?

谢谢你,罗宾

编辑

有趣的是,现在通过不可改变的工作访问的T性质,但我真的不明白为什么(不打字稿了解Object.defineProperty在构造函数?)。

我更新了类,使子类和设置的默认值存在,如果有人有兴趣:

import * as _ from "lodash"; 

export class Immutable<T> { 
    constructor(public data:T) { 
    Object.freeze(data); 


    _.each(_.keysIn(data), (key) => { 
     Object.defineProperty(this, key, <PropertyDescriptor> {get:() => this.data[key]}) 
    }) 
    } 

    set(key:string, val:any):this { 
    if (_.isEqual(this.data[key], val)) { 
     return this; 
    } 

    const newData = _.defaults<T>(_.fromPairs([[key, val]]), this.data); 
    return this.new(newData) 
    } 

    update(data:T):this { 
    const newData = _.defaults<T>(data, this.data); 
    return _.isEqual(this.data, newData) ? this : this.new(newData); 
    } 

    new(...args:any[]):this { 
    return <this> (new (<any>this.constructor)(...args)); 
    } 

    get(key):any { 
    return this.data[key]; 
    } 

    toJson():T { 
    return this.data; 
    } 
} 

这使得某事物像这样的可能:

class Child extends Immutable<{x:number}> { 
    constructor(data = {x: 1}) { 
    super(data) 
    } 
} 

我离开的问题开放,不过,因为我还是想知道如何让打字稿知道一个导出类具有比定义更多的属性答案(或许在外部添加,或通过像我这样的构造函数)

+0

是您的编辑说,你可以做'新的孩子({x:3})。x'? – Alex

+0

Jep,这个测试工作在那里我完全是这样的:https://gist.github.com/rweng/ab0fa6f28b522c267ab2fe0e41c0f1a2 – rweng

+0

该测试是为运行时行为,它没有说太多关于打字稿的类型。我假设你想要正确的打字? – Alex

回答

0

正如你已经发现类型系统不允许你说这个类本身扩展了T类。你可能会使用function来破解它。但请不要。例如。如果上存在update,则它将与您的Immutable.update冲突。

0

您不能定义构造函数的返回类型,但是,您可以使用一个静态工厂方法来达到你需要的东西:

export class Immutable<T> { 
 
    // You can protect your constructor. 
 
    protected constructor(public data: T) { 
 
     Object.freeze(data); 
 

 
     _.each(_.keysIn(data), (key) => { 
 
      Object.defineProperty(this, key, <PropertyDescriptor>{ get:() => this.data[key] }) 
 
     }) 
 
    } 
 

 
    static create<T>(data: T): Immutable<T> & T { 
 
     return new (<any>Immutable)(data); 
 
    } 
 

 
    // Another super useful new feature in TypeScript 2.1: keyof, refer to the usage below. 
 
    get<K extends keyof T>(key: K): T[K] { 
 
     return this.data[key]; 
 
    } 
 

 
    // ... 
 
} 
 

 
// Usage: 
 
var obj = Immutable.create({ x: 1 }); 
 
console.log(obj.x); 
 
console.log(obj.get("x")); 
 

 
// TypeScript will generate compilation error: 
 
// Argument of type '""' is not assignable to parameter of type '"x"'. 
 
console.log(obj.get("y"));