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
@mountedruns. For elements just rendered byv-if/v-for, wait for$nextTickbefore reading. - Components — a
refon a component resolves to its host custom element. - Inside
v-for— refs collected on (or inside) av-forare stored as an array under that name. - Dynamic / function refs (
:ref) —:refcomputes the ref from an expression. A string is used as the name; a function is called with the element on mount andnullon 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:
$refsis 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
$markRawor$ctx.userData. v-componentandVComponentRegistryare deprecated; usedefineComponent.
Next steps
- Directive list and modifiers → Directives reference
- Reusable components → Components
- Live demos → Examples