Now.js Framework Documentation
ReactiveManager
ReactiveManager
ภาพรวม
ReactiveManager คือระบบ reactivity ใน Now.js Framework ที่ช่วยให้ข้อมูลเปลี่ยนแปลงแล้วอัพเดท UI อัตโนมัติ ใช้ Proxy-based reactivity คล้าย Vue 3
ใช้เมื่อ:
- ต้องการให้ UI อัพเดทอัตโนมัติเมื่อข้อมูลเปลี่ยน
- ต้องการ computed values ที่ cache และ re-compute เมื่อ dependencies เปลี่ยน
- ต้องการ watch การเปลี่ยนแปลงของข้อมูล
- ต้องการ reactive state สำหรับ components
ทำไมต้องใช้:
- ✅ Proxy-based reactivity (Vue 3 style)
- ✅ Automatic dependency tracking
- ✅ Computed values พร้อม caching
- ✅ Watch และ WatchEffect
- ✅ Batch updates สำหรับ performance
- ✅ Deep reactivity สำหรับ nested objects
- ✅ Array change detection
การใช้งานพื้นฐาน
สร้าง Reactive Object
// สร้าง reactive object
const state = ReactiveManager.reactive({
count: 0,
user: {
name: 'John',
age: 25
},
items: []
});
// เปลี่ยนค่า - UI จะอัพเดทอัตโนมัติ
state.count++;
state.user.name = 'Jane';
state.items.push({ id: 1, title: 'Item 1' });Effect
Effect คือ function ที่ทำงานเมื่อ dependencies เปลี่ยน:
// สร้าง effect
const stopEffect = ReactiveManager.effect(() => {
console.log('Count is:', state.count);
document.querySelector('#count').textContent = state.count;
});
// เมื่อ state.count เปลี่ยน effect จะทำงานอัตโนมัติ
state.count++; // Console: "Count is: 1"
// หยุด effect
stopEffect();Computed
Computed คือค่าที่คำนวณจาก reactive data พร้อม caching:
const state = ReactiveManager.reactive({
price: 100,
quantity: 2,
discount: 10
});
// สร้าง computed
const total = ReactiveManager.computed(() => {
return state.price * state.quantity * (1 - state.discount / 100);
});
console.log(total.value); // 180
// เมื่อ dependencies เปลี่ยน computed จะ re-calculate
state.quantity = 3;
console.log(total.value); // 270Watch
Watch ติดตามการเปลี่ยนแปลงและเรียก callback:
// Watch effect - ติดตาม function
const stopWatch = ReactiveManager.watch(
() => state.count,
(newValue) => {
console.log('Count changed to:', newValue);
}
);
// Watch property - ติดตาม property โดยตรง
const stopWatch2 = ReactiveManager.watchProp(
state,
'count',
(value) => {
console.log('Count is now:', value);
}
);
// หยุด watch
stopWatch();
stopWatch2();Reactive Objects
reactive()
แปลง object ธรรมดาเป็น reactive object:
const user = ReactiveManager.reactive({
name: 'John',
email: 'john@example.com',
profile: {
avatar: '/images/avatar.jpg',
bio: 'Developer'
}
});
// Nested objects ก็ reactive ด้วย
user.profile.bio = 'Senior Developer'; // Triggers updateตรวจสอบ Reactive
// ตรวจสอบว่าเป็น reactive หรือไม่
if (ReactiveManager.isReactive(user)) {
console.log('User is reactive');
}
// หรือใช้ isProxy
if (ReactiveManager.isProxy(user)) {
console.log('User is a proxy');
}Array Reactivity
Arrays รองรับ reactivity เต็มรูปแบบ:
const state = ReactiveManager.reactive({
items: []
});
// Array methods ทำงานได้ปกติ
state.items.push({ id: 1, name: 'Item 1' });
state.items.splice(0, 1);
state.items.unshift({ id: 2, name: 'Item 2' });
// Length change ก็ trigger update
state.items.length = 0; // Clear arrayEffect System
effect()
สร้าง effect ที่ทำงานเมื่อ dependencies เปลี่ยน:
const state = ReactiveManager.reactive({
firstName: 'John',
lastName: 'Doe'
});
// Effect ติดตาม dependencies อัตโนมัติ
const stop = ReactiveManager.effect(() => {
// ทุกครั้งที่ firstName หรือ lastName เปลี่ยน
// function นี้จะทำงานอัตโนมัติ
const fullName = `${state.firstName} ${state.lastName}`;
document.querySelector('#name').textContent = fullName;
});
state.firstName = 'Jane'; // UI อัพเดทอัตโนมัติ
// หยุด effect
stop();Effect Cleanup
Effect return function สำหรับ cleanup:
function setupComponent() {
const stop = ReactiveManager.effect(() => {
// Update UI
});
// Return cleanup function
return () => {
stop();
};
}Computed Values
computed()
สร้าง computed value ที่ cache และ re-compute เมื่อจำเป็น:
const cart = ReactiveManager.reactive({
items: [
{ name: 'Item 1', price: 100, qty: 2 },
{ name: 'Item 2', price: 50, qty: 3 }
],
discount: 10
});
// Computed: คำนวณเมื่อ dependencies เปลี่ยน
const subtotal = ReactiveManager.computed(() => {
return cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);
});
const total = ReactiveManager.computed(() => {
return subtotal.value * (1 - cart.discount / 100);
});
console.log(subtotal.value); // 350
console.log(total.value); // 315
// เมื่อ items เปลี่ยน computed จะ re-calculate
cart.items.push({ name: 'Item 3', price: 25, qty: 4 });
console.log(subtotal.value); // 450
console.log(total.value); // 405Computed Caching
Computed values ถูก cache จนกว่า dependencies จะเปลี่ยน:
const expensive = ReactiveManager.computed(() => {
console.log('Computing...');
return heavyCalculation(state.data);
});
// แรก: คำนวณ
console.log(expensive.value); // "Computing..." แล้วแสดงผล
// ครั้งต่อไป: ใช้ cache
console.log(expensive.value); // ไม่มี "Computing..."
console.log(expensive.value); // ไม่มี "Computing..."
// เมื่อ state.data เปลี่ยน
state.data = newData;
console.log(expensive.value); // "Computing..." อีกครั้งWatch System
watch()
ติดตามการเปลี่ยนแปลงของ reactive data:
// Watch getter function
const stop = ReactiveManager.watch(
() => state.user.name,
(newValue) => {
console.log('Name changed to:', newValue);
saveToServer({ name: newValue });
}
);
// Watch with callback
ReactiveManager.watch(
() => state.count,
(value) => {
if (value > 100) {
alert('Count exceeded 100!');
}
}
);watchEffect()
Watch ที่รัน immediately และติดตาม dependencies:
const stop = ReactiveManager.watchEffect(
// Getter
() => state.searchQuery,
// Callback
async (query) => {
if (query.length >= 3) {
const results = await search(query);
state.results = results;
}
}
);watchProp()
Watch property เฉพาะของ object:
const stop = ReactiveManager.watchProp(
state, // Target object
'count', // Property name
(value) => { // Callback
console.log('Count is:', value);
},
{ immediate: true } // Options
);watchDeep()
Watch deep changes ใน nested objects:
const stop = ReactiveManager.watchDeep(
state.user,
(user) => {
console.log('User changed:', user);
saveUser(user);
}
);
// Triggers watch
state.user.profile.bio = 'Updated bio';Component Integration
createComponentState()
สร้าง reactive state สำหรับ component:
ComponentManager.define('my-component', {
reactive: true,
state: {
count: 0
},
mounted() {
// State ถูกแปลงเป็น reactive โดยอัตโนมัติ
this.state.count++; // Triggers re-render
}
});Manual Integration
function MyComponent() {
const state = ReactiveManager.reactive({
count: 0,
items: []
});
// Setup effect to update DOM
const cleanup = ReactiveManager.effect(() => {
render(state);
});
return {
state,
cleanup
};
}การตั้งค่า
await ReactiveManager.init({
// แสดง debug logs
debug: false,
// Batch updates ใน microtask
batchUpdates: true,
// Cleanup interval (ms)
cleanupInterval: 60000,
// Computed settings
computed: {
cache: true // Enable caching
}
});API อ้างอิง
ReactiveManager.reactive(target)
แปลง object เป็น reactive
| Parameter | Type | Description |
|---|---|---|
target |
object | Object ที่จะทำให้ reactive |
Returns: Proxy - Reactive proxy
const state = ReactiveManager.reactive({ count: 0 });ReactiveManager.effect(fn)
สร้าง effect function
| Parameter | Type | Description |
|---|---|---|
fn |
function | Effect function |
Returns: function - Stop function
const stop = ReactiveManager.effect(() => {
console.log(state.count);
});ReactiveManager.computed(getter)
สร้าง computed value
| Parameter | Type | Description |
|---|---|---|
getter |
function | Getter function |
Returns: Object - Computed ref object with .value
const double = ReactiveManager.computed(() => state.count * 2);
console.log(double.value);ReactiveManager.watch(source, callback)
สร้าง watcher
| Parameter | Type | Description |
|---|---|---|
source |
function/object | Source getter หรือ target |
callback |
function | Callback function |
Returns: function - Stop function
ReactiveManager.watchEffect(getter, callback)
สร้าง watch effect
| Parameter | Type | Description |
|---|---|---|
getter |
function | Getter function |
callback |
function | Callback function |
Returns: function - Stop function
ReactiveManager.watchProp(target, prop, callback, options)
Watch specific property
| Parameter | Type | Description |
|---|---|---|
target |
object | Target object |
prop |
string | Property name |
callback |
function | Callback function |
options.immediate |
boolean | Run immediately |
Returns: function - Stop function
ReactiveManager.watchDeep(target, callback, options)
Watch deep changes
| Parameter | Type | Description |
|---|---|---|
target |
object | Target object |
callback |
function | Callback function |
options |
object | Watch options |
Returns: function - Stop function
ReactiveManager.isReactive(value)
ตรวจสอบว่าเป็น reactive หรือไม่
| Parameter | Type | Description |
|---|---|---|
value |
any | Value to check |
Returns: boolean
ReactiveManager.isProxy(obj)
ตรวจสอบว่าเป็น proxy หรือไม่
| Parameter | Type | Description |
|---|---|---|
obj |
any | Object to check |
Returns: boolean
ตัวอย่างการใช้งานจริง
Form with Validation
const form = ReactiveManager.reactive({
email: '',
password: '',
errors: {}
});
// Computed validation
const isValid = ReactiveManager.computed(() => {
return form.email.includes('@') && form.password.length >= 8;
});
// Watch email for validation
ReactiveManager.watch(
() => form.email,
(email) => {
if (!email.includes('@')) {
form.errors.email = 'Invalid email format';
} else {
delete form.errors.email;
}
}
);
// Effect to update UI
ReactiveManager.effect(() => {
document.querySelector('#submit').disabled = !isValid.value;
});Data Fetching
const state = ReactiveManager.reactive({
query: '',
results: [],
loading: false
});
// Debounced search
let timeout;
ReactiveManager.watch(
() => state.query,
async (query) => {
clearTimeout(timeout);
if (query.length < 3) {
state.results = [];
return;
}
timeout = setTimeout(async () => {
state.loading = true;
try {
const response = await fetch(`/api/search?q=${query}`);
state.results = await response.json();
} finally {
state.loading = false;
}
}, 300);
}
);Shopping Cart
const cart = ReactiveManager.reactive({
items: [],
coupon: null
});
const subtotal = ReactiveManager.computed(() => {
return cart.items.reduce((sum, item) =>
sum + item.price * item.quantity, 0
);
});
const discount = ReactiveManager.computed(() => {
if (!cart.coupon) return 0;
return subtotal.value * (cart.coupon.percent / 100);
});
const total = ReactiveManager.computed(() => {
return subtotal.value - discount.value;
});
// Update cart display
ReactiveManager.effect(() => {
document.querySelector('#subtotal').textContent = subtotal.value;
document.querySelector('#discount').textContent = discount.value;
document.querySelector('#total').textContent = total.value;
});ข้อควรระวัง
⚠️ 1. ต้อง Stop Effects เมื่อไม่ใช้
// ❌ Memory leak
function setupComponent() {
ReactiveManager.effect(() => {
updateUI();
});
}
// ✅ Cleanup properly
function setupComponent() {
const stop = ReactiveManager.effect(() => {
updateUI();
});
return stop; // Return cleanup function
}⚠️ 2. อย่า Destructure Reactive
const state = ReactiveManager.reactive({ count: 0 });
// ❌ Loses reactivity
const { count } = state;
count++; // ไม่ trigger update
// ✅ Access through state
state.count++;⚠️ 3. Arrays ต้องใช้ Methods
const state = ReactiveManager.reactive({ items: [1, 2, 3] });
// ❌ Direct index assignment อาจไม่ work
state.items[0] = 100;
// ✅ ใช้ splice หรือ reassign
state.items.splice(0, 1, 100);
// หรือ
state.items = [100, ...state.items.slice(1)];เอกสารที่เกี่ยวข้อง
- ComponentManager - Reactive components
- StateManager - Global state
- TemplateManager - Template binding