Core guide

This covers the core of ichigo.js: creating an app and its reactivity. For directive syntax see the Directives reference; for components see Components.

Create an app

Create an app with VDOM.createApp(options) and mount it with mount(selector).

import { VDOM } from '@mintjamsinc/ichigojs';

VDOM.createApp({
  data() {
    return { message: 'Hello ichigo.js!', count: 0 };
  },
  methods: {
    increment() { this.count++; }
  }
}).mount('#app');

Options

Option Role
data() function returning the initial data; this is $ctx ({ $markRaw })
computed computed properties: a getter, or { get, set }
methods methods
watch watchers keyed by property path
emits event names the app is expected to emit (optional; validation)
logLevel 'debug' / 'info' / 'warn' / 'error'

Instance helpers include $markRaw / $nextTick / $emit / $refs / $ctx.

Reactivity

data

Values returned from data() are automatically reactive. Property assignments, nested object changes, and array mutations (push / splice, …) are all detected and a DOM update is scheduled automatically.

data() {
  return { count: 0, user: { name: 'Alice' }, items: [1, 2, 3] };
},
methods: {
  update() {
    this.count++;            // repaints
    this.user.name = 'Bob';  // nested change repaints
    this.items.push(4);      // array mutation repaints
  }
}

computed

Computed properties track their dependencies automatically and cache the result. Evaluation is pull-based (lazy): when a dependency changes the computed is marked stale and recomputed on the next read. Reading a computed synchronously right after mutating a dependency therefore returns an up-to-date value.

this.cartItems.push(item);
console.log(this.subtotal); // already reflects the new item

DOM updates are batched on a microtask, so multiple synchronous mutations still result in a single render. Each computed is recomputed at most once per update cycle, and a computed whose value is unchanged does not trigger DOM updates or watchers. Computed→computed chains resolve automatically and independently of declaration order.

A computed can also be defined as { get, set } to make it writable — usable as a v-model target or assignable directly.

computed: {
  fullName: {
    get() { return `${this.firstName} ${this.lastName}`; },
    set(v) { const [f, l] = v.split(' '); this.firstName = f; this.lastName = l; }
  }
}
<!-- Assigning through v-model invokes the setter -->
<input v-model="fullName">

watch

The watch option runs a callback whenever a watched property changes. Keys are property paths ("count", "user.name"), and the callback receives the new and old values.

watch: {
  // Shorthand: a callback
  count(newValue, oldValue) {
    console.log(`count: ${oldValue} → ${newValue}`);
  },

  // Nested path
  'user.name'(newValue, oldValue) { /* ... */ },

  // Full form: with deep / immediate
  user: {
    handler(newValue, oldValue) { /* ... */ },
    deep: true,      // observe nested changes
    immediate: true  // run once on registration
  }
}
Option Default Description
handler the callback invoked on change
deep false observe nested object/array changes
immediate false run once immediately with the current value

$markRaw

$markRaw() prevents an object from being wrapped in a reactive proxy — useful for third-party instances (Chart.js, etc.) or large data that doesn't need reactivity.

data() {
  return { chart: null };
},
methods: {
  initChart($ctx) {
    const instance = new Chart(canvas, { /* ... */ });
    this.chart = this.$markRaw(instance); // kept non-reactive
  }
}

A $markRaw()-ed object won't trigger updates when modified; change another reactive property to trigger one. In lifecycle hooks, $ctx.userData (a Proxy-free Map) serves the same purpose — see the Directives reference.

Methods

Inside methods, this gives access to data, computed, other methods, and helpers.

methods: {
  increment() { this.count++; },
  reset() { this.count = 0; }
}

Event handlers receive the event first and $ctx second — see Directives reference › v-on.

$nextTick

Updates are batched on a microtask. To run code after the DOM has updated, use $nextTick.

methods: {
  addAndScroll() {
    this.messages.push(msg);
    this.$nextTick(() => {
      // the DOM is up to date here
      this.$refs.list.scrollTop = this.$refs.list.scrollHeight;
    });
  }
}

Template refs ($refs)

The ref attribute gives you a direct reference to a DOM element (or a component's host element). References are collected into $refs, available on this in methods and as $refs in template expressions.

<input ref="search">
<button @click="focusSearch">Focus</button>
methods: {
  focusSearch() { this.$refs.search.focus(); }
}
  • When available — a ref is registered on mount and removed on unmount, so it is populated by the time @mounted runs. For elements just rendered by v-if / v-for, wait for $nextTick before reading.
  • Components — a ref on a component resolves to its host custom element.
  • Inside v-for — refs collected on (or inside) a v-for are stored as an array under that name.
  • Dynamic / function refs (:ref):ref computes the ref from an expression. A string is used as the name; a function is called with the element on mount and null on unmount (a function ref).
<li v-for="item in items" :key="item.id" ref="rows">{{ item.name }}</li>
this.$refs.rows; // [<li>, <li>, ...] one entry per rendered row

Note: $refs is not reactive. Registering or clearing a ref never triggers a re-render, and it should not drive reactive output — read it from event handlers and lifecycle hooks (this matches Vue).

Tips

  • Updates are batched on a microtask; multiple synchronous changes collapse into one render.
  • Run post-render work in $nextTick.
  • Keep third-party instances non-reactive with $markRaw or $ctx.userData.
  • v-component and VComponentRegistry are deprecated; use defineComponent.

Next steps