Bind to JS Object
JavaScript objects are a combination of several use-cases:
As a "record" or "struct" in other languages (like ReScript and C).
As a hash map.
As a class.
As a module to import/export.
ReScript cleanly separates the binding methods for JS object based on these 4 use-cases. This page documents the first three. Binding to JS module objects is described in the Import from/Export to JS section.
Bind to Record-like JS Objects
Bind Using ReScript Record
If your JavaScript object has fixed fields, then it's conceptually like a ReScript record. Since a ReScript record compiles to a clean JavaScript object, you can definitely type a JS object as a ReScript record!
External is documented here. @module is documented here.
If you want or need to use different field names on the ReScript and the JavaScript side, you can use the @as decorator:
This is useful to map to JavaScript attribute names that cannot be expressed in ReScript (such as keywords).
It is also possible to map a ReScript record to a JavaScript array by passing indices to the @as decorator:
Bind Using ReScript Object
Alternatively, you can use ReScript object to model a JS object too:
Bind Using Special Getter and Setter Attributes
Alternatively, you can use get and set to bind to individual fields of a JS object:
You can also use get_index and set_index to access a dynamic property or an index:
Bind to Hash Map-like JS Object
If your JavaScript object:
might or might not add/remove keys
contains only values that are of the same type
Then it's not really an object, it's a hash map. Use Dict, which contains operations like get, set, etc. and cleanly compiles to a JavaScript object still.
Bind to a JS Object That's a Class
Use new to emulate e.g. new Date():
You can chain new and module if the JS module you're importing is itself a class:
Represent Class Inheritance
ReScript has no class declarations, but bindings can model existing JavaScript inheritance using private type abbreviations. Suppose animals.js exports the hierarchy Animal → Mammal → Dog:
JS// animals.js
export class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
export class Mammal extends Animal {}
export class Dog extends Mammal {
bark() {
return "Woof!";
}
}
Declare the base type and its methods in Animal.res:
REStype t
@send external getName: t => string = "getName"
In Mammal.res, relate the subclass to its parent:
REStype t = private Animal.t
Repeat for Dog.res, adding its constructor and methods:
REStype t = private Mammal.t
@new @module("./animals.js") external make: string => t = "Dog"
@send external bark: t => string = "bark"
Use :> to coerce a subclass instance to a parent type:
RESlet dog = Dog.make("Rex")
let mammal = (dog :> Mammal.t)
let animal = (dog :> Animal.t)
let name = animal->Animal.getName
let sound = dog->Dog.bark
Coercion works through the hierarchy: Dog.t can be coerced directly to Animal.t. It changes only the type; all three variables refer to the same JavaScript object.
Unlike a regular type alias, private keeps the types distinct. Coercion requires :>; a : annotation alone is not enough. The reverse conversion from Animal.t to Dog.t is rejected, so keep the original Dog.t value if you need dog-specific methods.
Private abbreviations describe an existing inheritance relationship; they do not create inheritance or check instances at runtime.
Interfaces
If you add a .resi interface, expose the same relationship. For example, Mammal.resi should contain:
REStype t = private Animal.t
Exposing only type t would hide the relationship and prevent callers from coercing Mammal.t to Animal.t.