Typescript should be able to generate immutable objects with accessor properties and value semantics, similar to the "records" proposal for C# 7.0
Currently I have to write a lot of boilerplate code to create an object with accessor properties and value semantics. I think this can easily be streamlined by the TypeScript compiler.
class Person {
private _firstName: string;
private _lastName: string;
constructor(firstName: string = null, lastName: string = null) {
this._firstName = firstName;
this._lastName = lastName;
}
public get firstName(): string {
return this._firstName;
}
public get lastName(): string {
return this._lastName;
}
public equals(other: Person): boolean {
return other !== void 0
&& other !== null
&& this._firstName === other._firstName
&& this._lastName === other._lastName;
}
}
class Person {
constructor(
public get firstName: string = null,
public get lastName: string = null) {
}
}
Note the use of get in the constructor
Either of the examples above should produce this (the top example did produce this)
var Person = (function () {
function Person(firstName, lastName) {
if (firstName === void 0) { firstName = null; }
if (lastName === void 0) { lastName = null; }
this._firstName = firstName;
this._lastName = lastName;
}
Object.defineProperty(Person.prototype, "firstName", {
get: function () {
return this._firstName;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Person.prototype, "lastName", {
get: function () {
return this._lastName;
},
enumerable: true,
configurable: true
});
Person.prototype.equals = function (other) {
return other !== void 0
&& other !== null
&& this._firstName === other._firstName
&& this._lastName === other._lastName;
};
return Person;
}());
The following is succinct and does most of what you are asking for:
class Person {
constructor(
public readonly firstName: string = null,
public readonly lastName: string = null) {
Object.freeze(this); // this adds runtime immutability as well
}
}
Since equals is no sort of standard method in JavaScript, I can't imagine that part being implemented by the TS compiler.
@yortus
Emiting ES5 accessor properties with only a getter would naturally create immutable properties...swings and roundabouts as to whether emitting getters or using Object.freeze would be better.
_In general I just thing the whole property syntax in TypeScript sucks._
readonly is a 2.0 feature. Read about it in #6532. It's purely for static type-checking and doesn't affect the emit, so the JavaScript output contains ordinary mutable fields.
I do agree with @yortus, the intended scenarios should be covered by readonly properties.
Emiting ES5 accessor properties with only a getter would naturally create immutable properties.
that too is the case in TS 2.0
In general I just thing the whole property syntax in TypeScript sucks.
it is really JS syntax/semantics.
Perhaps also worth noting that if all you need is a simple record, classes are overkill.
const p1 = { firstName: "Bart", lastName: "Simpson };
And we're done. If you want to centrally enforce the type that a person conforms to, e.g. to stop writing to the properties:
interface Person {
readonly firstName: string;
readonly lastName: string;
}
Then assign your object literals to that. To get freezing, write a simple constructor-like function:
function person(firstName: string, lastName: string): Person {
return Object.freeze({ firstName, lastName });
}
const p = person("Homer", "Simpson");
p.firstName = "Marge"; // stopped at compile-type
(p as any).firstName = "Marge"; // stopped at runtime
The following is succinct and does most of what you are asking for:
class Person { constructor( public readonly firstName: string = null, public readonly lastName: string = null) { Object.freeze(this); // this adds runtime immutability as well } }
@yortus that is excellent.
I would like to add that it can be made even more concise and idiomatic
class Person {
constructor(
readonly firstName?: string,
readonly lastName?: string) {
Object.freeze(this); // this adds runtime immutability as well
}
}
Also for record equality you can use this function : https://www.npmjs.com/package/deep-equal when feeling lazy (like I've done a few times e.g in alm) :rose:
@series0ne 'readonly' emits read-write property in JS.
It is not the same as getter-only property.
Unfortunately there is still no shorthand for getter-only property.
@NN Yes I know. readonly is rather useful (provided one does not play with the emitted JS). I also find it quite useful to use readonly in conjuction with Object.freeze(...)
For example:
class Foo {
constructor(readonly bar: any) {
Object.freeze(this);
}
}
Now it's really readonly!
Actually, readonly can be useful for creating immutable classes if you
return Object.freeze(this);
from the constructor. The advantage over using accessors is that you can leverage inline initialization, parameter properties, and the resulting object is spreadable.
All in all though, I much prefer factories to classes. Especially with the addition of Object Rest/Spread, object literals are better than ever.
Most helpful comment
Perhaps also worth noting that if all you need is a simple record, classes are overkill.
And we're done. If you want to centrally enforce the type that a person conforms to, e.g. to stop writing to the properties:
Then assign your object literals to that. To get freezing, write a simple constructor-like function: