Now.js Framework Documentation

Now.js Framework Documentation

StateManager

TH 15 Dec 2025 08:52

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 ได้
    }
  }
}

เอกสารที่เกี่ยวข้อง