This usage is very simple: as a contract, the function doSomething
declares it doesn't mutate the received parameter.
interface Counter {
name: string
value: number
}
function doSomething(c: Readonly<Counter>) {
// ...
}
let c = {
name: "abc",
value: 123
}
doSomething(c)
// Here we are sure that 'c.name' is "abc" and 'c.value' is '123'
With this code:
interface Counter {
readonly name: string
readonly value: number
inc(): number
}
function counterFactory(name: string): Counter {
let value = 0
return {
get name() {
return name
},
get value() {
return value
},
inc() {
return ++value
}
}
}
We have here a member readonly value
that cannot be modified directly from the outside. But a member inc()
can mutate the value. Also, the member value
is declared as readonly
but its value is changing.
I would like to know if this use of readonly
on the member value
is a good way to proceed. The syntax is OK. But is this example semantically correct in TypeScript? Is that what the modifier readonly
is for?
The readonly
keyword on a property does not ensure that the property value is constant. There's no TypeScript equivalent for that. The only things we can be sure with a readonly property are:
counterFactory
return type is not defined, it is inferred exactly like the Counter
interface, see (A) in the code below.
Code example:
// (A) Usage 2 using type inference
const counter = counterFactory('foo');
type Counter = typeof counter; // Produce the exact same type as the previous `Counter` interface
counter.value = 10; // [Ts Error] Can not assign to 'value' because it is a constant or read-only property
// (B) Readonly property initialization
// (B1) With an object literal + interface
const manualCounter: Counter = { name: 'bar', value: 2, inc: () => 0 };
manualCounter.value = 3; // [Ts Error] Can not assign...
// (B2) With a class
class Foo {
constructor(public name: string, public readonly value: number) { }
inc() {
return ++this.value; // [Ts Error] Can not assign...
}
}
const foo = new Foo('bar', 3);
foo.value = 4; // [Ts Error] Can not assign...
// (C) Circumvent TypeScript
Object.assign(foo, { value: 4 }); // → No error neither in Ts, nor at runtime
It's really confusing because it's almost like a constant property! The usage 2 and the case C prove it's not.
What creates the confusion is the "double" usage of the word `value' in your factory function.
To clarify, rewrite it this way (please note the _ in front of the _value
variable):
interface Counter {
readonly name: string
readonly value: number
inc(): number
}
function counterFactory(name: string): Counter {
let _value = 0
return {
get name() {
return name
},
get value() {
return _value
},
inc() {
return ++_value
}
}
}
_value
is just a local var than you can mutateget value()
implements the interface definition readonly value: number
: counter.value
is readonlyIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With