JavaScript Reactivity for Beginners
JavaScript Reactivity for Beginners
Section titled “JavaScript Reactivity for Beginners”1. Basic JavaScript Variables
Section titled “1. Basic JavaScript Variables”Let’s start with the simplest form of data storage in JavaScript:
let price = 10;let quantity = 2;let total = price * quantity;
console.log(total); // 20
// If we change priceprice = 20;console.log(total); // Still 20! Not what we wantThe problem: When we change price, total doesn’t automatically update. This is because JavaScript is not reactive by default.
2. Making It Reactive with Functions
Section titled “2. Making It Reactive with Functions”The simplest way to make something reactive is to use functions:
let price = 10;let quantity = 2;
function calculateTotal() { return price * quantity;}
console.log(calculateTotal()); // 20
price = 20;console.log(calculateTotal()); // 40 - Now it updates!This works, but we have to manually call calculateTotal() every time we want the updated value.
3. Using Object Properties (Getters and Setters)
Section titled “3. Using Object Properties (Getters and Setters)”Object.defineProperty is a built-in JavaScript method that allows you to define or modify the properties of an object. It takes three arguments: the object on which you want to define the property, the name of the property, and an object that contains the property’s descriptors.
Here’s how it works:
let data = { price: 10, quantity: 2,};
let total = 0;
// Create a reactive propertyObject.defineProperty(data, "total", { get() { return this.price * this.quantity; },});
console.log(data.total); // 20
data.price = 20;console.log(Object.keys(data)); // ['price', 'quantity'] - The `Object.keys()` method returns an array of a given object's own enumerable property names. In this case, we're logging the keys of the `data` object, which includes the `price` and `quantity` properties.console.log(Object.keys(data)); // ['price', 'quantity'] - The `total` property is not included in the array of keys. The `Object.keys()` method returns an array of a given object's own enumerable property names, but it does not include properties created by getters or setters. In this case, the `total` property is a getter that calculates the total based on the `price` and `quantity` properties, so it is not included in the array of keys.4. Using Proxy (Modern JavaScript)
Section titled “4. Using Proxy (Modern JavaScript)”The Proxy object is a more modern way to make objects reactive:
let data = { price: 10, quantity: 2,};
// Create a proxylet proxy = new Proxy(data, { get(target, property) { console.log(`Getting ${property}`); return target[property]; }, set(target, property, value) { console.log(`Setting ${property} to ${value}`); target[property] = value; return true; },});
console.log(proxy.price); // Logs: "Getting price" then 10proxy.price = 20; // Logs: "Setting price to 20"5. Creating a Simple Reactivity System
Section titled “5. Creating a Simple Reactivity System”Let’s build a basic reactivity system step by step:
// 1. Create a dependency trackerclass Dep { constructor() { this.subscribers = new Set(); }
// Add a subscriber depend() { if (activeEffect) { this.subscribers.add(activeEffect); } }
// Notify all subscribers notify() { this.subscribers.forEach((effect) => effect()); }}
// 2. Create a reactive functionlet activeEffect = null;
function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null;}
// 3. Make an object reactivefunction reactive(obj) { const deps = new Map();
return new Proxy(obj, { get(target, key) { if (activeEffect) { let dep = deps.get(key); if (!dep) { dep = new Dep(); deps.set(key, dep); } dep.depend(); } return target[key]; }, set(target, key, value) { target[key] = value; const dep = deps.get(key); if (dep) { dep.notify(); } return true; }, });}
// 4. Use our reactive systemconst state = reactive({ price: 10, quantity: 2,});
// Watch for changeswatchEffect(() => { console.log(`Total: ${state.price * state.quantity}`);});
// Change valuesstate.price = 20; // Logs: "Total: 40"state.quantity = 3; // Logs: "Total: 60"6. Real-World Example: Shopping Cart
Section titled “6. Real-World Example: Shopping Cart”Let’s see how this works in a practical example:
// Create a reactive shopping cartconst cart = reactive({ items: [], total: 0,});
// Watch for changes in the cartwatchEffect(() => { cart.total = cart.items.reduce((sum, item) => sum + item.price, 0); console.log(`Cart total: $${cart.total}`);});
// Add items to cartcart.items.push({ name: "Apple", price: 1 });cart.items.push({ name: "Banana", price: 2 });// Logs: "Cart total: $3"
// Update item pricecart.items[0].price = 2;// Logs: "Cart total: $4"7. Common Patterns in Modern Frameworks
Section titled “7. Common Patterns in Modern Frameworks”Vue.js Style
Section titled “Vue.js Style”const state = reactive({ count: 0,});
watchEffect(() => { document.getElementById("counter").textContent = state.count;});
// Update will automatically reflect in the DOMstate.count++;React Style
Section titled “React Style”let state = { count: 0,};
function setState(newState) { state = { ...state, ...newState }; render();}
function render() { document.getElementById("counter").textContent = state.count;}
// Update will trigger a re-rendersetState({ count: state.count + 1 });Key Concepts to Remember
Section titled “Key Concepts to Remember”- Reactivity means automatically updating when data changes
- Dependencies are tracked when data is accessed
- Effects are functions that run when dependencies change
- Proxies and Getters/Setters are tools to make objects reactive
- State Management is about keeping track of changes
Common Pitfalls
Section titled “Common Pitfalls”- Circular Dependencies
// Avoid this!watchEffect(() => { state.a = state.b + 1;});
watchEffect(() => { state.b = state.a + 1;});- Memory Leaks
// Always clean up effects when doneconst stop = watchEffect(() => { // effect code});
// When component unmountsstop();