TSone

Reactive System

TSone's reactivity system lets you create reactive state and automatically update the UI when it changes.

Core Concepts

Reactive Objects

reactive creates reactive objects that automatically track dependencies when properties are read and changed.

Effect Functions

effect creates effect functions that run again when dependencies change.

Dependency Tracking

The framework records reactive properties accessed while an effect runs.

Core APIs

reactive

import { reactive } from '@geektech/tsone';

const state = reactive({
  count: 0,
  user: { name: 'John', age: 30 },
});

console.log(state.count);
state.count = 1;

effect

import { effect, reactive } from '@geektech/tsone';

const state = reactive({ count: 0 });

effect(() => {
  console.log(`Count: ${state.count}`);
});

state.count = 1;
state.count = 2;

computed

import { computed, reactive } from '@geektech/tsone';

const state = reactive({ count: 0 });
const doubleCount = computed(() => state.count * 2);

console.log(doubleCount.value);
state.count = 1;
console.log(doubleCount.value);

readonly

import { reactive, readonly } from '@geektech/tsone';

const original = reactive({ count: 0 });
const readOnlyState = readonly(original);

console.log(readOnlyState.count);
original.count = 1;

ref

import { effect, ref } from '@geektech/tsone';

const count = ref(0);

effect(() => {
  console.log(`Count: ${count.value}`);
});

count.value += 1;

How Reactivity Works

TSone uses JavaScript Proxy to intercept get/set operations, collect dependencies, and dispatch updates.

Dependency Tracking Process

  • Run effect and mark it as the active effect
  • Read reactive or ref values inside effect
  • Record the dependency relationship
  • Schedule the corresponding effect again when values change

Advanced Usage

Nested Reactive Objects

const state = reactive({
  user: { name: 'John', address: { city: 'New York' } },
});

effect(() => {
  console.log(`${state.user.name} lives in ${state.user.address.city}`);
});

state.user.address.city = 'Los Angeles';

Reactive Arrays

const state = reactive({ items: ['Item 1', 'Item 2'] });

effect(() => {
  console.log(state.items.join(', '));
});

state.items.push('Item 3');
state.items[0] = 'Updated Item 1';

Conditional Dependencies

const state = reactive({ showCount: true, count: 0, message: 'Hello' });

effect(() => {
  if (state.showCount) {
    console.log(`Count: ${state.count}`);
  } else {
    console.log(state.message);
  }
});

Stop an Effect

import { effect, reactive, stop } from '@geektech/tsone';

const state = reactive({ count: 0 });
const runner = effect(() => {
  console.log(`Count: ${state.count}`);
});

stop(runner);

watch Listeners

watch(source, callback, options) listens to a reactive source (a getter or a ref) and runs a callback when it changes, returning a stop function. The callback receives the new value, the old value, and an onCleanup parameter for registering cleanup. By default callbacks are batched with the scheduler, so multiple changes in one batch run the callback once.

import { reactive, ref, watch } from '@geektech/tsone';

const count = ref(0);

const stop = watch(count, (value, oldValue) => {
  console.log(`count: ${oldValue} -> ${value}`);
});

const state = reactive({ profile: { name: 'John' } });

watch(() => state.profile.name, (name) => {
  console.log('name changed to', name);
}, { deep: true });

stop();
  • immediate: run the callback once right away (oldValue is undefined)
  • deep: track nested property changes of the getter result
  • sync: invoke the callback synchronously on change (default is scheduler batching)

Best Practices

State Design

  • Put only data that drives the UI into reactive objects
  • Organize state by business module and avoid meaningless deep nesting
  • Prefer computed for complex derived values

Performance Optimization

  • Use computed caching for expensive calculations
  • Avoid expensive calculations or chained side-effect writes in effect
  • Use readonly for data that should not be written

Common Pitfalls

  • Do not replace an entire reactive object directly
  • Do not unconditionally modify dependency state inside effect
  • Nested objects become reactive automatically; do not call reactive again manually

Integration with Components

class CounterComponent extends Component<object, { count: number }> {
  protected initState() {
    return { count: 0 };
  }

  protected initStyles(): void {}

  protected render(): VNode {
    return {
      tag: 'button',
      children: ['Count: '],
      listeners: { click: () => this.state.count++ },
    };
  }
}

Summary

With reactive, effect, computed, readonly, ref, and stop, TSone provides a unified reactive foundation for component state and standalone data models.