Now.js Framework Documentation
StateManager
StateManager
ภาพรวม
StateManager คือระบบจัดการ global state ใน Now.js Framework ที่ใช้รูปแบบคล้าย Vuex/Redux รองรับ modules, mutations, actions, getters, history tracking และ persistence
ใช้เมื่อ:
- ต้องการ global state ที่หลาย components ใช้ร่วมกัน
- ต้องการติดตามการเปลี่ยนแปลง state (debugging)
- ต้องการ time-travel debugging
- ต้องการ persist state ลง localStorage
ทำไมต้องใช้:
- ✅ Module-based state organization
- ✅ Mutations สำหรับ synchronous changes
- ✅ Actions สำหรับ async operations
- ✅ Getters สำหรับ computed state
- ✅ Watch และ Subscribe สำหรับ reactivity
- ✅ History tracking และ time-travel
- ✅ Automatic persistence
- ✅ Middleware support
การใช้งานพื้นฐาน
การติดตั้ง Module
// ลงทะเบียน module
StateManager.registerModule('counter', {
// Initial state
state: {
count: 0,
step: 1
},
// Synchronous state changes
mutations: {
increment(state, payload) {
state.count += payload || state.step;
},
decrement(state, payload) {
state.count -= payload || state.step;
},
setStep(state, step) {
state.step = step;
}
},
// Async operations
actions: {
async incrementAsync(context, delay = 1000) {
await new Promise(resolve => setTimeout(resolve, delay));
context.commit('increment');
},
async fetchAndSet(context, url) {
const response = await fetch(url);
const data = await response.json();
context.commit('increment', data.value);
}
},
// Computed values
getters: {
doubleCount(state) {
return state.count * 2;
},
isPositive(state) {
return state.count > 0;
}
}
});การใช้งาน State
// อ่าน state
const count = StateManager.get('counter.count');
console.log(count); // 0
// Commit mutation (synchronous)
StateManager.commit('counter/increment');
StateManager.commit('counter/increment', 5);
// Dispatch action (async)
await StateManager.dispatch('counter/incrementAsync', 500);
// Watch changes
const unwatch = StateManager.watch('counter.count', (newValue) => {
console.log('Count changed:', newValue);
});
// Later: stop watching
unwatch();Modules
โครงสร้าง Module
StateManager.registerModule('moduleName', {
// State: ข้อมูลเริ่มต้น
state: {
items: [],
loading: false,
error: null
},
// Mutations: เปลี่ยน state แบบ sync
mutations: {
setItems(state, items) {
state.items = items;
},
setLoading(state, loading) {
state.loading = loading;
},
setError(state, error) {
state.error = error;
}
},
// Actions: logic แบบ async
actions: {
async fetchItems(context) {
context.commit('setLoading', true);
try {
const response = await fetch('/api/items');
const items = await response.json();
context.commit('setItems', items);
} catch (error) {
context.commit('setError', error.message);
} finally {
context.commit('setLoading', false);
}
}
},
// Getters: computed values
getters: {
itemCount(state) {
return state.items.length;
},
activeItems(state) {
return state.items.filter(item => item.active);
}
},
// Watch: ติดตามการเปลี่ยนแปลง
watch: {
'items': (newItems) => {
console.log('Items updated:', newItems.length);
}
},
// Init: เรียกเมื่อ module ถูกลงทะเบียน
init(context) {
console.log('Module initialized');
context.dispatch('fetchItems');
}
});State Function
หาก state เป็น function จะสร้าง instance ใหม่ทุกครั้ง:
StateManager.registerModule('form', {
// ใช้ function เพื่อป้องกัน shared state
state: () => ({
fields: {},
errors: [],
dirty: false
})
});Force Replace Module
// แทนที่ module ที่มีอยู่
StateManager.registerModule('counter', {
state: { count: 100 }
}, { force: true });Mutations
Mutations คือ synchronous functions ที่เปลี่ยนแปลง state:
// ประกาศ mutation
mutations: {
// Simple mutation
increment(state) {
state.count++;
},
// With payload
setUser(state, user) {
state.user = user;
},
// With multiple values
addItem(state, { id, name, price }) {
state.items.push({ id, name, price });
}
}
// เรียกใช้ mutation
StateManager.commit('moduleName/mutationName');
StateManager.commit('moduleName/mutationName', payload);❌ อย่าทำ Async ใน Mutations
mutations: {
// ❌ ไม่ดี - อย่าใช้ async ใน mutations
async fetchData(state) {
const data = await fetch('/api/data');
state.data = data;
}
}
// ✅ ใช้ actions แทน
actions: {
async fetchData(context) {
const response = await fetch('/api/data');
const data = await response.json();
context.commit('setData', data);
}
}Actions
Actions คือ async functions ที่สามารถทำ side effects:
actions: {
// Simple action
async loadUser(context, userId) {
context.commit('setLoading', true);
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to load user');
const user = await response.json();
context.commit('setUser', user);
return user;
} catch (error) {
context.commit('setError', error.message);
throw error;
} finally {
context.commit('setLoading', false);
}
},
// Action that dispatches other actions
async initializeApp(context) {
await context.dispatch('loadUser', 'current');
await context.dispatch('loadSettings');
}
}
// เรียกใช้ action
const user = await StateManager.dispatch('users/loadUser', 123);Context Object
Action context มีสิ่งเหล่านี้:
actions: {
example(context, payload) {
// State ของ module นี้
console.log(context.state);
// Getters ของ module นี้
console.log(context.getters.itemCount);
// Commit mutation
context.commit('mutationName', payload);
// Dispatch action
await context.dispatch('otherAction', payload);
// Watch changes
context.watch('path', handler);
}
}Getters
Getters คือ computed values ที่คำนวณจาก state:
getters: {
// Simple getter
itemCount(state) {
return state.items.length;
},
// Getter ที่ใช้ getter อื่น
doubleCount(state, getters) {
return getters.itemCount * 2;
},
// Filter items
activeItems(state) {
return state.items.filter(item => item.active);
},
// Search function
findById(state) {
return (id) => state.items.find(item => item.id === id);
}
}
// ใช้งาน getters
const context = StateManager.getModuleContext('moduleName');
console.log(context.getters.itemCount);
console.log(context.getters.findById(123));Watch และ Subscribe
Watch
ติดตามการเปลี่ยนแปลง state path:
// Watch single path
const unwatch = StateManager.watch('counter.count', (newValue) => {
console.log('Count is now:', newValue);
});
// หยุด watch
unwatch();Subscribe
Subscribe พร้อม options เพิ่มเติม:
// Subscribe with options
const unsubscribe = StateManager.subscribe('user.profile', (value) => {
console.log('Profile updated:', value);
}, {
immediate: true, // เรียกทันทีด้วย current value
deep: false // ตรวจจับ deep changes
});
// หยุด subscribe
unsubscribe();Watch ใน Module Definition
StateManager.registerModule('cart', {
state: {
items: [],
total: 0
},
watch: {
'items': function(newItems, oldItems) {
// Recalculate total
this.commit('updateTotal');
}
}
});Time Travel
StateManager บันทึกประวัติการเปลี่ยนแปลง state:
// ดูประวัติ
console.log(StateManager.history);
// [
// { type: 'counter/increment', payload: null, state: {...}, timestamp: 1699...},
// { type: 'counter/increment', payload: 5, state: {...}, timestamp: 1699...}
// ]
// ย้อนกลับไป state ก่อนหน้า
StateManager.timeTravel(0); // ไปที่ entry แรก
// ดู index ปัจจุบัน
console.log(StateManager.historyIndex); // 0
// ไปข้างหน้า
StateManager.timeTravel(StateManager.historyIndex + 1);การตั้งค่า
await StateManager.init({
// Debug mode - แสดง logs
debug: false,
// Persistence settings
persistence: {
enabled: true,
key: 'app_state', // localStorage key
blacklist: ['temp', 'ui'], // modules ที่ไม่ persist
encrypt: false // encrypt data
},
// History settings
history: {
enabled: true,
maxSize: 50, // จำนวน entries สูงสุด
include: ['user', 'data'] // modules ที่ track
},
// Batch updates
batch: {
enabled: true,
delay: 16 // ms (1 frame)
},
// Strict mode - log mutations
strict: true
});API อ้างอิง
StateManager.init(options)
เริ่มต้น StateManager
| Parameter | Type | Description |
|---|---|---|
options |
object | Configuration options |
Returns: Promise<StateManager>
StateManager.registerModule(name, module, options)
ลงทะเบียน module
| Parameter | Type | Description |
|---|---|---|
name |
string | ชื่อ module |
module |
object | Module definition |
options.force |
boolean | แทนที่ module เดิม |
StateManager.hasModule(name)
ตรวจสอบว่ามี module หรือไม่
| Parameter | Type | Description |
|---|---|---|
name |
string | ชื่อ module |
Returns: boolean
StateManager.get(path)
รับค่า state
| Parameter | Type | Description |
|---|---|---|
path |
string | Path เช่น 'module.property' |
Returns: any
const count = StateManager.get('counter.count');
const user = StateManager.get('auth.user');StateManager.set(path, value)
ตั้งค่า state โดยตรง (ใช้ในกรณีพิเศษ)
| Parameter | Type | Description |
|---|---|---|
path |
string | Path |
value |
any | ค่าใหม่ |
StateManager.commit(type, payload)
เรียก mutation
| Parameter | Type | Description |
|---|---|---|
type |
string | 'moduleName/mutationName' |
payload |
any | ข้อมูลที่ส่งไป mutation |
StateManager.commit('counter/increment');
StateManager.commit('counter/setCount', 100);StateManager.dispatch(type, payload)
เรียก action
| Parameter | Type | Description |
|---|---|---|
type |
string | 'moduleName/actionName' |
payload |
any | ข้อมูลที่ส่งไป action |
Returns: Promise<any>
await StateManager.dispatch('auth/login', { email, password });
const users = await StateManager.dispatch('users/fetchAll');StateManager.watch(path, callback)
ติดตามการเปลี่ยนแปลง
| Parameter | Type | Description |
|---|---|---|
path |
string | State path |
callback |
function | Handler function |
Returns: function - Unwatch function
StateManager.subscribe(path, callback, options)
Subscribe กับ options
| Parameter | Type | Description |
|---|---|---|
path |
string | State path |
callback |
function | Handler function |
options.immediate |
boolean | เรียกทันที |
options.deep |
boolean | Deep comparison |
Returns: function - Unsubscribe function
StateManager.timeTravel(index)
ย้อนกลับไป state ใน history
| Parameter | Type | Description |
|---|---|---|
index |
number | History index |
StateManager.reset()
รีเซ็ต state ทั้งหมดเป็นค่าเริ่มต้น
StateManager.persistState()
บันทึก state ลง localStorage
StateManager.use(middleware)
เพิ่ม middleware
| Parameter | Type | Description |
|---|---|---|
middleware |
object | Middleware object |
ตัวอย่างการใช้งานจริง
Authentication Module
StateManager.registerModule('auth', {
state: () => ({
user: null,
token: null,
loading: false,
error: null
}),
mutations: {
setUser(state, user) {
state.user = user;
},
setToken(state, token) {
state.token = token;
},
setLoading(state, loading) {
state.loading = loading;
},
setError(state, error) {
state.error = error;
},
logout(state) {
state.user = null;
state.token = null;
}
},
actions: {
async login(context, { email, password }) {
context.commit('setLoading', true);
context.commit('setError', null);
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Login failed');
}
const { user, token } = await response.json();
context.commit('setUser', user);
context.commit('setToken', token);
return user;
} catch (error) {
context.commit('setError', error.message);
throw error;
} finally {
context.commit('setLoading', false);
}
},
async logout(context) {
await fetch('/api/auth/logout', { method: 'POST' });
context.commit('logout');
}
},
getters: {
isLoggedIn(state) {
return !!state.user && !!state.token;
},
userName(state) {
return state.user?.name || 'Guest';
}
}
});
// Usage
await StateManager.dispatch('auth/login', {
email: 'user@example.com',
password: 'password123'
});
const isLoggedIn = StateManager.getModuleContext('auth').getters.isLoggedIn;Shopping Cart Module
StateManager.registerModule('cart', {
state: () => ({
items: [],
coupon: null
}),
mutations: {
addItem(state, product) {
const existing = state.items.find(item => item.id === product.id);
if (existing) {
existing.quantity++;
} else {
state.items.push({ ...product, quantity: 1 });
}
},
removeItem(state, productId) {
state.items = state.items.filter(item => item.id !== productId);
},
updateQuantity(state, { productId, quantity }) {
const item = state.items.find(item => item.id === productId);
if (item) {
item.quantity = Math.max(0, quantity);
if (item.quantity === 0) {
state.items = state.items.filter(item => item.id !== productId);
}
}
},
setCoupon(state, coupon) {
state.coupon = coupon;
},
clearCart(state) {
state.items = [];
state.coupon = null;
}
},
getters: {
itemCount(state) {
return state.items.reduce((sum, item) => sum + item.quantity, 0);
},
subtotal(state) {
return state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
discount(state, getters) {
if (!state.coupon) return 0;
return getters.subtotal * (state.coupon.discount / 100);
},
total(state, getters) {
return getters.subtotal - getters.discount;
}
}
});ข้อควรระวัง
⚠️ 1. อย่าเปลี่ยน State โดยตรง
// ❌ ไม่ดี - เปลี่ยน state โดยตรง
StateManager.state.counter.count++;
// ✅ ดี - ใช้ mutation
StateManager.commit('counter/increment');⚠️ 2. Watch ต้อง Cleanup
// ❌ Memory leak
function setupComponent() {
StateManager.watch('counter.count', updateUI);
}
// ✅ Cleanup เมื่อ component ถูกลบ
function setupComponent() {
const unwatch = StateManager.watch('counter.count', updateUI);
return () => unwatch(); // Cleanup function
}⚠️ 3. Action ต้อง Handle Errors
// ❌ ไม่ handle error
actions: {
async fetchData(context) {
const data = await fetch('/api/data').then(r => r.json());
context.commit('setData', data);
}
}
// ✅ Handle error
actions: {
async fetchData(context) {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Fetch failed');
const data = await response.json();
context.commit('setData', data);
} catch (error) {
context.commit('setError', error.message);
throw error; // Re-throw เพื่อให้ caller handle ได้
}
}
}เอกสารที่เกี่ยวข้อง
- ComponentManager - Component lifecycle
- ReactiveManager - Reactive state
- StorageManager - Storage utilities