Reactive programming is a neat approach that allows you to create applications which dynamically reflect data changes. It’s the core tech behind many modern JavaScript frameworks like React and Vue - it updates in response to user actions or other state changes. Understanding what is under the hood of reactivity can feel like too much work, it feels like one of those 'magic' abstractions that the frameworks are for. But what if you could build a small reactive system yourself to see how it really works?
This article will walk through the basics of reactive programming by building a simple reactive store from scratch in JavaScript. We’ll walk through the key concepts, including dependency tracking and automatic updates, in a minimal implementation. By the end, you should be able to understand how to create reactive data structures that automatically track dependencies and trigger updates whenever the state changes. This approach will help you understand reactivity and will give you the tools to experiment with it on your own, and possibly apply it to your projects.
Let's get started by looking at the core components of a reactive system that we are going to use:
Now that we are past reactive programming definitions, let's also mention the Javascript APIs we are going to use:
Proxy: The Proxy object allows you to create a proxy for another object, enabling you to define custom behavior for fundamental operations (like property access and assignment). In this code, it's used to make the reactive store (the state object) responsive to changes.
Reflect: The Reflect API provides methods for interceptable JavaScript operations. It is used to perform operations like Reflect.get and Reflect.set in the reactive function, which allows the proxy to handle property access and assignment while maintaining the original behavior of the object.
Map: The Map object is a collection that holds key-value pairs, where keys can be any data type. It is used in this implementation to create the dependencyMap, which tracks the dependencies associated with each signal.
Now, let's start defining what our initial state will look like:
// Let's define a Map object to track our dependencies const dependencyTrackerMap = new Map(); // The activeEffect variable will hold the currently executing // effect function. // It will be set when an effect is run and will be used // to track which effects depend on specific reactive properties. let activeEffect = null // This function will make an object reactive function reactive(target) { return new Proxy(target, { get(obj, prop) { trackDependency(prop); // Track dependency return Reflect.get(obj, prop); }, set(obj, prop, value) { const result = Reflect.set(obj, prop, value); triggerDependency(prop); // Trigger reactions return result; } }); } // the effect function will register reactive functions function effect(fn) { activeEffect = fn; fn(); // Run the function once to register dependencies activeEffect = null; } // this function will track dependencies function trackDependency(key) { if (activeEffect) { if (!dependencyTrackerMap.has(key)) { dependencyTrackerMap.set(key, new Set()); } dependencyTrackerMap.get(key).add(activeEffect); } } // this function will trigger dependencies function triggerDependency(key) { const deps = dependencyTrackerMap.get(key); if (deps) { deps.forEach(effect => effect()); } } // This will create a reactive object with an initial state // count and message here are signals const state = reactive({ count: 0, message: "Hello" });
So, here is what we have done:
Now, let's create an effect with a callback and try to trigger it:
// Let's define a Map object to track our dependencies const dependencyTrackerMap = new Map(); // The activeEffect variable will hold the currently executing // effect function. // It will be set when an effect is run and will be used // to track which effects depend on specific reactive properties. let activeEffect = null // This function will make an object reactive function reactive(target) { return new Proxy(target, { get(obj, prop) { trackDependency(prop); // Track dependency return Reflect.get(obj, prop); }, set(obj, prop, value) { const result = Reflect.set(obj, prop, value); triggerDependency(prop); // Trigger reactions return result; } }); } // the effect function will register reactive functions function effect(fn) { activeEffect = fn; fn(); // Run the function once to register dependencies activeEffect = null; } // this function will track dependencies function trackDependency(key) { if (activeEffect) { if (!dependencyTrackerMap.has(key)) { dependencyTrackerMap.set(key, new Set()); } dependencyTrackerMap.get(key).add(activeEffect); } } // this function will trigger dependencies function triggerDependency(key) { const deps = dependencyTrackerMap.get(key); if (deps) { deps.forEach(effect => effect()); } } // This will create a reactive object with an initial state // count and message here are signals const state = reactive({ count: 0, message: "Hello" });
The console logs will trigger when we try to update the state we have created:
//We are using state from the previous snippet: effect(() => { console.log(Count has changed: ${state.count}); }); effect(() => { console.log("Message has changed"); console.log(The new message is: ${state.message}); });
Here is a little visualization of what happens when a dependency is triggered:
In this article, we explored how to create a basic reactive system in JavaScript, enabling automatic updates (or side effects) in response to changes in data. This implementation serves as an introduction to reactive programming concepts, which is part of the framework 'magic'. Besides, we've learned what Proxy and Reflect APIs do, and used them, as well as the Map object.
In summary, this reactive system manages dependencies and automatically updates effects when the state changes. By registering functions that rely on specific reactive properties, the system tracks which functions depend on which properties, re-running them when needed. This approach allows to create responsive applications where state changes are automatically reflected in the UI without additional code, improving developer experience and making data handling easier and more efficient.
The above is the detailed content of Build a reactive store from scratch using Javascript. For more information, please follow other related articles on the PHP Chinese website!