Now.js Framework Documentation

Now.js Framework Documentation

ReactiveManager

TH 15 Dec 2025 08:52

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); // 270

Watch

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 array

Effect 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);    // 405

Computed 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)];

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