Build a reactive store from scratch using Javascript
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:
- Effects: Functions that automatically run in response to changes in reactive data. They are registered using the effect function, which tracks dependencies on any accessed signals.
- Signals: Reactive data properties that notify dependent effects whenever their values change.
- Dependencies: The relationships between signals and the effects that rely on them. Dependencies are tracked so that changes in signals trigger updates to the relevant effects.
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:
- We created a reactive function that takes in an object and returns a Proxy which will track what changes will be done to the object in the future
- We created an effect function which registers reactive functions that depend on the state. When an effect is defined, it runs immediately to register any dependencies and sets activeEffect accordingly.
- We created a dependency tracker which consists of three parts: the trackDependency function checks if there is an active effect when a reactive property is accessed. If yes, it adds that effect to a set of dependencies for the corresponding property in dependencyTrackerMap. In turn, the triggerDependency function retrieves the set of dependent effects associated with a property from dependencyTrackerMap and executes each of them when the property value changes.
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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

In-depth discussion of the root causes of the difference in console.log output. This article will analyze the differences in the output results of console.log function in a piece of code and explain the reasons behind it. �...

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.
