Reactive Framework Core in React
This challenge asks you to build the foundational core of a simple reactive framework within a React environment. Reactive programming allows for automatic propagation of data changes, simplifying UI updates and data synchronization. Building this core will give you a deeper understanding of how reactive systems work and how they can be integrated with React.
Problem Description
You are tasked with creating a basic reactive framework core using TypeScript and React. This core should manage reactive signals (data values that can change) and automatically update components when those signals change. The framework should provide a mechanism to create signals, subscribe to them, and unsubscribe from them. The core should be designed to be lightweight and easily extensible.
Key Requirements:
Signal<T>Class: This class represents a reactive signal. It should:- Hold a value of type
T. - Provide a
get()method to retrieve the current value. - Provide a
set(newValue: T)method to update the value. - Maintain a list of subscribers (functions).
- When
set()is called, it should notify all subscribers by calling them with the new value.
- Hold a value of type
createSignal<T>(initialValue: T)Function: This function should create and return a newSignal<T>instance initialized with the providedinitialValue.- Subscription Management: Subscribers should be able to unsubscribe from a signal to prevent unnecessary updates. The
Signalclass should maintain a list of subscriptions and provide anunsubscribe(callback: () => void)method. - React Integration: The framework should be designed to work seamlessly with React components. Subscribers should be React components or functions that return React elements.
Expected Behavior:
- Creating a signal with
createSignalshould return aSignalobject. - Setting a new value on a signal should trigger all subscribers.
- Subscribers should receive the new value as an argument.
- Unsubscribing a subscriber should prevent it from receiving further updates.
- The framework should not introduce unnecessary re-renders in React components.
Edge Cases to Consider:
- Multiple subscribers to the same signal.
- Subscribers unsubscribing after being called.
- Signals with complex data types (objects, arrays). (While deep reactivity isn't required for this core, consider how changes within these types might be handled – shallow comparison is acceptable).
- Error handling (e.g., what happens if a subscriber throws an error?). Basic error handling (e.g., logging) is sufficient.
Examples
Example 1:
Input:
createSignal<number>(0);
const signal = createSignal<number>(0);
signal.set(1);
signal.set(2);
Output:
Subscribers receive 1, then 2.
Explanation: The signal is initialized to 0. Setting it to 1 triggers all subscribers with 1. Setting it to 2 triggers all subscribers with 2.
Example 2:
Input:
const signal = createSignal<string>("hello");
const subscriber = (newValue: string) => { console.log(newValue); };
signal.subscribe(subscriber);
signal.set("world");
signal.unsubscribe(subscriber);
signal.set("universe");
Output:
Console logs "world"
Explanation: The signal is initialized to "hello". The subscriber is added. Setting the signal to "world" triggers the subscriber, logging "world". Unsubscribing the subscriber prevents it from being triggered by the subsequent set to "universe".
Example 3: (Edge Case - Multiple Subscribers)
Input:
const signal = createSignal<boolean>(false);
const subscriber1 = (newValue: boolean) => { console.log("Subscriber 1:", newValue); };
const subscriber2 = (newValue: boolean) => { console.log("Subscriber 2:", newValue); };
signal.subscribe(subscriber1);
signal.subscribe(subscriber2);
signal.set(true);
signal.unsubscribe(subscriber1);
signal.set(false);
Output:
Console logs:
"Subscriber 1: true"
"Subscriber 2: true"
"Subscriber 2: false"
Explanation: Both subscribers are initially added. Setting the signal to true triggers both subscribers. Unsubscribing subscriber1 prevents it from being triggered when the signal is set to false, but subscriber2 still receives the update.
Constraints
- TypeScript: The solution must be written in TypeScript.
- No External Libraries: Do not use any external reactive libraries (e.g., MobX, RxJS). This is about building the core yourself.
- Performance: While not a primary focus, avoid unnecessary re-renders in React components. Shallow comparison for object/array changes is acceptable.
- Signal Type Safety: The
Signalclass must be type-safe, ensuring that the value stored and passed to subscribers matches the declared type.
Notes
- Focus on the core functionality of creating, setting, and subscribing to signals.
- Consider how you might integrate this core with React components (e.g., using
useEffectfor subscriptions and unsubscriptions). - Think about how to handle errors gracefully within the framework.
- This is a simplified core; a production-ready framework would require more features (e.g., derived signals, computed values, advanced reactivity patterns). This challenge is about the fundamentals.