מונחים רבים משמשים אותנו כל הזמן בעולם הפרונט, אחד מהם הוא "ריאקטיביות". המונח מתייחס ליכולת של ממשק לשנות את מצבו בתגובה לשינויים המתבצעים בנתונים הקשורים אליו. כאשר הנתונים ישתנו, הממשק יעדכן את עצמו באופן אוטומטי וישפיע על תצוגת המשתמש.
ריאקטיביות מהווה אתגר משמעותי בפיתוח, אך התוצאות יכולות להיות פנטסטיות. במאמר הבא נממש ריאקטיביות עם Proxy, שהוא כלי עוצמתי בכלל ובפרט למטרה שלנו.
מה זה Proxy API ואיך הוא עובד?
Proxy מאפשר לנו ״לתפוס״ קריאה או שינוי של מידע באובייקט, ובכך גם לכפות לוגיקה מותאמת אישית בכל אחת מהפעולות הללו.
Proxy מקבל שני משתנים: אובייקט המטרה שלנו (האובייקט שעליו נאזין לקריאה או שינוי) ואובייקט הגדרות (handler) שמכיל פונקציות get & set
המגדירות איך הפרוקסי יתנהג בזמן קריאה או שינוי אובייקט המטרה.
לדוגמה, בהגדרה של פונקציה get
בתוך ה handler, אנו יכולים לכתוב קוד שירוץ בכל פעם שננסה לקרוא ערך מתוך פרופ של אובייקט המטרה. באמצעות הפונקציה set
אפשר להגדיר קוד שירוץ ברגע שנרצה לשנות את ערך הפרופ באובייקט המטרה.
יתרונות של Proxy לבניית ריאקטיביות
בבניית ריאקטיביות עם Proxy ישנם מספר יתרונות:
- שמירה על נקיון הקוד: השימוש ב Proxy מאפשר לנו לבנות קוד יציב וקריא, כאשר תגובת הממשק הריאקטיבי נמצאת במקום אחד בלבד.
- אפשרויות מותאמות אישית: היכולת להגדיר התנהגות רצויה במגוון תרחישים שונים מאפשרת דינמיות וגמישות.
- הוספת פונקציונליות מתקדמת: בעזרת פרוקסי ניתן להוסיף תכונות מתקדמות, טכניקות בדיקה ושיפורים נוספים לממשק.
איך זה נראה בקוד?
בכמה שורות קוד, אדגים כיצד לממש ריאקטיביות עם Proxy ע״י מעקב אחר סטייט פשוט בכדי לשנות את התצוגה באופן אוטומטי.
// Creating a simple object that holds a state. Will be used as the target object.
const data = {
counter: 0
};
// Configure the handler object and how we are going to follow changes on state and update the DOM.
const handler = {
get(target, property) {
// Get the value of a specific property
return target[property];
},
set(target, property, value) {
// Set the value of a specific property
target[property] = value;
// Update the DOM
updateView();
}
};
// Creating the Proxy
const reactiveData = new Proxy(data, handler);
// Function for updating the DOM
function updateView() {
const counterElement = document.getElementById('counter');
if (counterElement) {
counterElement.textContent = reactiveData.counter;
}
}
// Listen to click event to set the new counter value
const incrementButton = document.getElementById('increment');
if (incrementButton) {
incrementButton.addEventListener('click', () => {
reactiveData.counter++;
});
}
// Initial DOM update
updateView();
בדוגמה זו, יצרתי אובייקט Proxy
עבור הסטייט השמור בתוך המשתנה data
. כאשר הערך של פרופ מסויים משתנה, הפונקציה set
ב-handler מופעלת והיא מעדכנת את התצוגה באופן אוטומטי.
ברגע שערך הפרופ counter
משתנה בעת לחיצה על כפתור Increment, התצוגה מתעדכנת ומציגה את הערך החדש.
וככה זה נראה בפועל ->
ריאקטיביות בסקייל
עד כה הצלחנו להציג דוגמא עובדת של ריאקטיביות דרך עדכוני DOM על כל שינוי שמתבצע בסטייט, אבל איך מוסיפים סקייל ועוד קומפוננטות?
כדי לעשות זאת מימשתי EventBus דרכו נרשמות קומפוננטות לשינויים של הסטייט ובעצם על כל שינוי כל קומפוננטה תשקף את השינוי על האלמנט שלה.
ברמת העיקרון באותה פונקציית update כל קומפוננטה יכול לממש כל לוגיקה שנרצה, במקרה שלנו הלוגיקה זהה ופשוטה וסך הכל מעדכנת את ה DOM.
class EventBus {
// List of all subscribed components
subscribers = new Set();
// Add subscriber
subscribe(subscriberCallback) {
this.subscribers.add(subscriberCallback);
subscriberCallback(reactiveData.counter);
}
// Remove subscriber
unsubscribe(subscriberCallback) {
this.subscribers.delete(subscriberCallback);
}
// Letting the subscribers know about new change
publish() {
this.subscribers.forEach(subscriberCallback => subscriberCallback(reactiveData.counter));
}
}
const eventBus = new EventBus();
// Initial DOM update
eventBus.publish();
function renderCounterComponent() {
// Rendering a counter component that shows the counter number and also a button to remove itself.
const counter = document.createElement('div');
counter.classList.add('counter');
const counterText = document.createElement('span');
counter.appendChild(counterText);
function updateCounter(count) {
// Updating the counter text.
counterText.innerText = count;
}
const removeCounterBtn = document.createElement('button');
removeCounterBtn.innerText = '❌';
removeCounterBtn.addEventListener('click', () => {
eventBus.unsubscribe(updateCounter);
counter.remove();
})
counter.appendChild(removeCounterBtn);
eventBus.subscribe(updateCounter);
document.body.querySelector('.counters-container').appendChild(counter);
}
const addCounter = document.getElementById('add-counter');
addCounter.addEventListener('click', renderCounterComponent);
והתוצאה לפניכם
סיכום
כיום, לא מעט ספריות ופריימוורקים משתמשים ב Proxy בכדי לעקוב אחר שינויים בסטייט ולעדכן את התצוגה בהתאם, למשל – Vue, SolidJS, Immer ועוד…
אז כפי שהבנתם, Proxy, שהוצג לראשונה ב-ES6 (ECMAScript 2015), הוא כלי עוצמתי בבניית ריאקטיביות. השימוש בו מאפשר לנו להפעיל תגובה מותאמת אישית בעת שינויים בנתונים. כל היתרונות הללו עוזרים לנו לכתוב קוד יעיל, קריא וקל לתחזוקה.