JavaScript Proxy: اعمل Reactive State بـ 20 سطر بدل Vue
لو عندك component أو dashboard صغير ومحتاج state بيعيد render لوحده كل ما يتغير، مش دايمًا لازم تجيب Zustand أو MobX أو Redux. الـ Proxy الأصلي في JavaScript بيديك نفس السلوك في 20 سطر، من غير dependency ولا build step زيادة.
المشكلة باختصار
المكتبات الكبيرة زي Zustand و MobX و reactive الـ Vue مبنية كلها على فكرة واحدة: اعترض القراءة والكتابة على الـ state object، وبعدين خطر أي function مشتركة (subscribers) إن فيه تغيير. developer عربي كتير بيستخدم المكتبات دي من غير ما يفهم الطبقة اللي تحت، فلما يقابل performance bug أو reactivity مش شغّال زي ما هو متوقع بيقف. الأداة اللي المكتبات دي مبنية عليها اسمها Proxy، ولو فهمتها هتوفر على نفسك مكتبات كتير في المشاريع الصغيرة.
ليه Proxy مش object عادي
الـ Proxy بيلف أي object وبيسمحلك باعتراض 13 عملية جوهرية عليه، أهمهم: get, set, deleteProperty, has, ownKeys. بدل ما JavaScript يقرأ القيمة مباشرة من الـ object، بيمر على trap أنت كاتبه. ده بيخليك تعمل تلات حاجات بدون تعديل الـ object الأصلي:
- logging لكل قراءة أو كتابة، مفيد جدًا في debugging أي state مش عارف بيتغير منين.
- validation تلقائي: ترمي error لو حد حاول يحط نوع غلط في property.
- reactivity: تشغّل callback كل ما أي property تتغير، وده اللي هنستغله.
الحل: reactive() في 20 سطر
الكود ده بيبني state object بيعيد تشغيل function render كل ما أي حاجة تتغير جواه، بما في ذلك الـ nested objects:
function reactive(target, onChange) {
return new Proxy(target, {
get(obj, key) {
const value = obj[key];
return typeof value === 'object' && value !== null
? reactive(value, onChange)
: value;
},
set(obj, key, value) {
if (obj[key] === value) return true;
obj[key] = value;
onChange(key, value);
return true;
},
deleteProperty(obj, key) {
delete obj[key];
onChange(key, undefined);
return true;
},
});
}
const state = reactive(
{ count: 0, user: { name: 'أحمد' } },
(key) => render()
);
function render() {
document.getElementById('app').textContent =
`${state.user.name}: ${state.count}`;
}
render();
state.count = 5; // يعيد render
state.user.name = 'حايس'; // يعيد render (nested)
السطر اللي بيفرق هنا هو اللي بيرجع reactive(value, onChange) جوه الـ get trap. بدونه، ما كنش هيشغّل لأن object داخلي مش متعمل له wrap. الطريقة اللي كتير من الـ tutorials بيشرحوها بدون nested support بتفشل في أول لما تحط object جواه object.