RBox
RBox is a reactive abstraction in F-Box. It encapsulates a single value, supports reactive updates, and provides a functional interface for transforming, combining, and chaining operations.
The RBox abstraction extends the functionality of Box by introducing reactivity. It allows you to subscribe to updates, propagate changes to derived RBox instances, and manage dependencies.
Key Features
- Reactivity: Automatically updates derived
RBoxinstances when the original value changes. - Encapsulation: Wraps a value to provide a controlled interface for interactions.
Creating an RBox
To create an RBox, use the static method RBox.pack. This method initializes an RBox instance with a given value. Additionally, you can subscribe to value changes to observe updates in real-time.
Example
import { RBox } from "f-box-core"
// Create an RBox with an initial valueconst rbox = RBox.pack(42)console.log(rbox.getValue()) // Outputs: 42
// Subscribe to changesconst subscriptionKey = rbox.subscribe((value) => { console.log(`Updated value: ${value}`)})
// Update the valuerbox.setValue((x) => x + 10) // Logs: Updated value: 52
// Unsubscribe from updatesrbox.unsubscribe(subscriptionKey)rbox.setValue((x) => x * 2) // No logs, as subscription is removed
// Get the current valueconsole.log(rbox.getValue()) // Outputs: 104This demonstrates how to:
- Create an
RBox. - Subscribe to value changes.
- Update the value reactively.
- Unsubscribe from updates when no longer needed.
Supported Operators
The RBox abstraction provides the following operators for working with its encapsulated value. Each operator is an alias for a corresponding method.
<$>: Alias formap. Applies a transformation function to the value.<*>: Alias forapply. Combines a function in anRBoxwith a value in anotherRBox.>>=: Alias forflatMap. Chains computations that return newRBoxinstances.
For detailed usage, see API Methods.
API Methods
RBox.pack
RBox.pack(value: T): RBox<T>
Creates a new RBox instance containing the provided value.
const rbox = RBox.pack(42)console.log(rbox.getValue()) // Outputs: 42RBox.isRBox
RBox.isRBox(value: any): boolean
Checks if a given value is an RBox instance.
import { RBox } from "f-box-core"
const rbox = RBox.pack(10)console.log(RBox.isRBox(rbox)) // Outputs: trueconsole.log(RBox.isRBox({})) // Outputs: falsegetValue
getValue(): T
Retrieves the value encapsulated in the RBox.
const rbox = RBox.pack(10)console.log(rbox.getValue()) // Outputs: 10setValue
setValue(fn: (value: T) => T): void
Updates the value inside the RBox and notifies all subscribers.
const rbox = RBox.pack(10)rbox.setValue((x) => x * 2)console.log(rbox.getValue()) // Outputs: 20map
map<U>(fn: (value: T) => U): RBox<U>
Applies a transformation function to the encapsulated value, returning a new derived RBox.
Alias: <$>
const rbox = RBox.pack(10)const derivedRBox = rbox["<$>"]((x) => x * 2)console.log(derivedRBox.getValue()) // Outputs: 20// Method callconst derivedRBox = rbox.map((x) => x * 2)console.log(derivedRBox.getValue()) // Outputs: 20apply
apply<A, B>(this: RBox<(a: A) => B>, boxValue: RBox<A>): RBox<B>
Applies a function wrapped in one RBox to a value wrapped in another RBox.
Alias: <*>
const rboxFn = RBox.pack((x: number) => x + 5)const rboxValue = RBox.pack(10)const resultRBox = rboxFn["<*>"](rboxValue)console.log(resultRBox.getValue()) // Outputs: 15// Method callconst resultRBox = rboxFn.apply(rboxValue)console.log(resultRBox.getValue()) // Outputs: 15flatMap
flatMap<U>(fn: (value: T) => RBox<U>): RBox<U>
Applies a function returning an RBox to the value inside this RBox and flattens the result.
Alias: >>=
const rbox = RBox.pack(10)const derivedRBox = rbox[">>="]((x) => RBox.pack(x * 2))console.log(derivedRBox.getValue()) // Outputs: 20// Method callconst derivedRBox = rbox.flatMap((x) => RBox.pack(x * 2))console.log(derivedRBox.getValue()) // Outputs: 20subscribe
subscribe(observer: Observer<T>): string
Subscribes to updates of the RBox value.
const rbox = RBox.pack(10)const key = rbox.subscribe((value) => { console.log(`Updated value: ${value}`)})rbox.setValue((x) => x + 5) // Logs: Updated value: 15unsubscribe
unsubscribe(key: string): void
Unsubscribes from updates using the subscription key.
const rbox = RBox.pack(10)const key = rbox.subscribe((value) => { console.log(`Updated value: ${value}`)})rbox.unsubscribe(key)rbox.setValue((x) => x + 5) // No logsunsubscribeAll
unsubscribeAll(): void
Unsubscribes all observers from the RBox.
const rbox = RBox.pack(10)rbox.subscribe((value) => console.log(`Observer 1: ${value}`))rbox.subscribe((value) => console.log(`Observer 2: ${value}`))rbox.unsubscribeAll()rbox.setValue((x) => x + 5) // No logsdetach
detach(): void
Detaches the RBox from all dependencies and stops updates to derived RBox instances.
const rbox = RBox.pack(10)const derivedRBox = rbox.map((x) => x * 2)rbox.detach()rbox.setValue((x) => x + 5)console.log(derivedRBox.getValue()) // Outputs: 20 (unchanged)Use Cases
Reactive Transformations
Automatically update derived RBox values when the original changes:
const rbox = RBox.pack(100)const derivedRBox = rbox["<$>"]((x) => x / 2)rbox.setValue((x) => x + 50)console.log(derivedRBox.getValue()) // Outputs: 75Learn more about how map enables reactive transformations.
Combining RBoxes
Combine multiple RBox instances using the <*> operator:
const result = RBox.pack((a: number) => (b: number) => a + b) ["<*>"](RBox.pack(10)) ["<*>"](RBox.pack(20))
console.log(result.getValue()) // Outputs: 30Explore the apply method for combining RBox instances.
Chaining Reactive Updates
Chain multiple reactive updates into a pipeline:
const chainedRBox = RBox.pack(10) [">>="]((x) => RBox.pack(x * 2)) [">>="]((x) => RBox.pack(x + 5))
console.log(chainedRBox.getValue()) // Outputs: 25Discover how flatMap simplifies chaining reactive updates.
Why Use RBox?
The RBox abstraction provides a robust way to manage reactive state with minimal boilerplate. Its reactivity and functional interface make it an ideal tool for scenarios requiring dynamic updates and efficient state propagation.
Next Steps
Explore other abstractions built on Box and RBox: