A record is similar to a JS object, but enforces a specific set of allowed string keys, and has default values.
type Record<TProps>
The Record()
function produces new Record Factories, which when called
create Record instances.
const { Record } = require('immutable')
const ABRecord = Record({ a: 1, b: 2 })
const myRecord = ABRecord({ b: 3 })
Records always have a value for the keys they define. remove
ing a key
from a record simply resets it to the default value for that key.
myRecord.get('a') // 1
myRecord.get('b') // 3
const myRecordWithoutB = myRecord.remove('b')
myRecordWithoutB.get('b') // 2
Values provided to the constructor not found in the Record type will be ignored. For example, in this case, ABRecord is provided a key "x" even though only "a" and "b" have been defined. The value for "x" will be ignored for this record.
const myRecord = ABRecord({ b: 3, x: 10 })
myRecord.get('x') // undefined
Because Records have a known set of string keys, property get access works as expected, however property sets will throw an Error.
Note: IE8 does not support property access. Only use get()
when
supporting IE8.
myRecord.b // 3
myRecord.b = 5 // throws Error
Record Types can be extended as well, allowing for custom methods on your Record. This is not a common pattern in functional environments, but is in many JS programs.
However Record Types are more restricted than typical JavaScript classes. They do not use a class constructor, which also means they cannot use class properties (since those are technically part of a constructor).
While Record Types can be syntactically created with the JavaScript class
form, the resulting Record function is actually a factory function, not a
class constructor. Even though Record Types are not classes, JavaScript
currently requires the use of new
when creating new Record instances if
they are defined as a class
.
class ABRecord extends Record({ a: 1, b: 2 }) {
getAB() {
return this.a + this.b;
}
}
var myRecord = new ABRecord({b: 3})
myRecord.getAB() // 4
Flow Typing Records:
Immutable.js exports two Flow types designed to make it easier to use
Records with flow typed code, RecordOf<TProps>
and RecordFactory<TProps>
.
When defining a new kind of Record factory function, use a flow type that
describes the values the record contains along with RecordFactory<TProps>
.
To type instances of the Record (which the factory function returns),
use RecordOf<TProps>
.
Typically, new Record definitions will export both the Record factory function as well as the Record instance type for use in other code.
import type { RecordFactory, RecordOf } from 'immutable';
// Use RecordFactory<TProps> for defining new Record factory functions.
type Point3DProps = { x: number, y: number, z: number };
const defaultValues: Point3DProps = { x: 0, y: 0, z: 0 };
const makePoint3D: RecordFactory<Point3DProps> = Record(defaultValues);
export makePoint3D;
// Use RecordOf<T> for defining new instances of that Record.
export type Point3D = RecordOf<Point3DProps>;
const some3DPoint: Point3D = makePoint3D({ x: 10, y: 20, z: 30 });
Flow Typing Record Subclasses:
Records can be subclassed as a means to add additional methods to Record instances. This is generally discouraged in favor of a more functional API, since Subclasses have some minor overhead. However the ability to create a rich API on Record types can be quite valuable.
When using Flow to type Subclasses, do not use RecordFactory<TProps>
,
instead apply the props type when subclassing:
type PersonProps = {name: string, age: number};
const defaultValues: PersonProps = {name: 'Aristotle', age: 2400};
const PersonRecord = Record(defaultValues);
class Person extends PersonRecord<PersonProps> {
getName(): string {
return this.get('name')
}
setName(name: string): this {
return this.set('name', name);
}
}
Choosing Records vs plain JavaScript objects
Records offer a persistently immutable alternative to plain JavaScript
objects, however they're not required to be used within Immutable.js
collections. In fact, the deep-access and deep-updating functions
like getIn()
and setIn()
work with plain JavaScript Objects as well.
Deciding to use Records or Objects in your application should be informed by the tradeoffs and relative benefits of each:
Runtime immutability: plain JS objects may be carefully treated as immutable, however Record instances will throw if attempted to be mutated directly. Records provide this additional guarantee, however at some marginal runtime cost. While JS objects are mutable by nature, the use of type-checking tools like Flow can help gain confidence in code written to favor immutability.
Value equality: Records use value equality when compared with is()
or record.equals()
. That is, two Records with the same keys and values
are equal. Plain objects use reference equality. Two objects with the
same keys and values are not equal since they are different objects.
This is important to consider when using objects as keys in a Map
or
values in a Set
, which use equality when retrieving values.
API methods: Records have a full featured API, with methods like
.getIn()
, and .equals()
. These can make working with these values
easier, but comes at the cost of not allowing keys with those names.
Default values: Records provide default values for every key, which can be useful when constructing Records with often unchanging values. However default values can make using Flow and TypeScript more laborious.
Serialization: Records use a custom internal representation to efficiently store and update their values. Converting to and from this form isn't free. If converting Records to plain objects is common, consider sticking with plain objects to begin with.
Unlike other types in Immutable.js, the Record()
function creates a new
Record Factory, which is a function that creates Record instances.
Record<TProps>(defaultValues: TProps, name?: string): Record.Factory<TProps>
Record.isRecord(maybeRecord: unknown): boolean
Record.getDescriptiveName(record: Record<any>): string
has(key: string): boolean
get<K>(key: K, notSetValue?: unknown): TProps,[K]
get<T>(key: string, notSetValue: T): T
hasIn(keyPath: Iterable<unknown>): boolean
getIn(keyPath: Iterable<unknown>): unknown
equals(other: unknown): boolean
hashCode(): number
set<K>(key: K, value: TProps,[K]): this
update<K>(key: K, updater: (value: TProps,[K]) => TProps,[K]): this
merge(...collections: Array<Partial<TProps> | Iterable<[string, unknown]>>): this
mergeDeep(...collections: Array<Partial<TProps> | Iterable<[string, unknown]>>): this
mergeWith(merger: (oldVal: unknown, newVal: unknown, key: keyof TProps) => unknown,
...collections: Array<Partial<TProps> | Iterable<[string, unknown]>>): this
mergeDeepWith(merger: (oldVal: unknown, newVal: unknown, key: unknown) => unknown,
...collections: Array<Partial<TProps> | Iterable<[string, unknown]>>): this
Returns a new instance of this Record type with the value for the specific key set to its default value.
delete<K>(key: K): this
remove()
Returns a new instance of this Record type with all values set to their default values.
clear(): this
setIn(keyPath: Iterable<unknown>, value: unknown): this
updateIn(keyPath: Iterable<unknown>, updater: (value: unknown) => unknown): this
mergeIn(keyPath: Iterable<unknown>, ...collections: Array<unknown>): this
mergeDeepIn(keyPath: Iterable<unknown>, ...collections: Array<unknown>): this
deleteIn(keyPath: Iterable<unknown>): this
removeIn()
Deeply converts this Record to equivalent native JavaScript Object.
toJS(): DeepCopy<TProps>
Note: This method may not be overridden. Objects with custom serialization to plain JS may override toJSON() instead.
Shallowly converts this Record to equivalent native JavaScript Object.
toJSON(): TProps
Shallowly converts this Record to equivalent JavaScript Object.
toObject(): TProps
Note: Not all methods can be used on a mutable collection or within
withMutations
! Only set
may be used mutatively.
withMutations(mutator: (mutable: this) => unknown): this
asMutable(): this
wasAltered(): boolean
asImmutable(): this
[Symbol.iterator](): IterableIterator<[keyof TProps, TProps,[keyof TProps]]>