Reactive Runtime
Rasen is designed to work with any reactive system. This guide explains how reactive runtimes integrate with Rasen.
Overview
Instead of building its own reactive system, Rasen provides a ReactiveRuntime interface that adapters implement:
interface ReactiveRuntime {
watch<T>(
source: () => T,
callback: (value: T, oldValue: T) => void,
options?: { immediate?: boolean; deep?: boolean }
): () => void
effectScope(): {
run<T>(fn: () => T): T | undefined
stop(): void
}
ref<T>(value: T): Ref<T>
computed<T>(getter: () => T): ReadonlyRef<T>
unref<T>(value: T | Ref<T>): T
isRef(value: unknown): boolean
}Setting Up a Runtime
Before using Rasen, you must set up a reactive runtime:
import { setReactiveRuntime } from '@rasenjs/core'
import { createVueRuntime } from '@rasenjs/reactive-vue'
setReactiveRuntime(createVueRuntime())This should be done once at app initialization, before mounting any components.
Available Adapters
Vue Reactivity
The recommended adapter for most use cases:
import { setReactiveRuntime } from '@rasenjs/core'
import { createVueRuntime } from '@rasenjs/reactive-vue'
import { ref, computed, watch } from 'vue'
setReactiveRuntime(createVueRuntime())
// Use Vue's reactive primitives directly
const count = ref(0)
const doubled = computed(() => count.value * 2)TC39 Signals
For projects preferring the emerging Signals standard:
import { setReactiveRuntime } from '@rasenjs/core'
import { createSignalsRuntime, ref } from '@rasenjs/reactive-signals'
setReactiveRuntime(createSignalsRuntime())
// Use the adapter's ref
const count = ref(0)Using Reactive State
With Vue Runtime
import { ref, computed, reactive } from 'vue'
// Primitive values
const count = ref(0)
const name = ref('World')
// Computed values
const greeting = computed(() => `Hello, ${name.value}!`)
const doubled = computed(() => count.value * 2)
// Reactive objects
const state = reactive({
items: [],
loading: false
})
// In components
const Counter = () => div({
children: [
span({ textContent: () => `Count: ${count.value}` }),
span({ textContent: doubled }), // Can pass computed directly
span({ textContent: greeting })
]
})With Signals Runtime
import { ref, computed } from '@rasenjs/reactive-signals'
const count = ref(0)
const doubled = computed(() => count.value * 2)
// Usage is the same
const Counter = () => div({
children: [
span({ textContent: () => `Count: ${count.value}` }),
span({ textContent: doubled })
]
})PropValue Type
Rasen components accept props that can be:
- Static values — Plain values that don't change
- Ref values — Reactive references (mutable)
- Computed values — Derived reactive values (readonly)
- Getter functions — Functions that return values
import type { PropValue } from '@rasenjs/core'
// All of these work:
div({ textContent: 'Hello' }) // Static
div({ textContent: ref('Hello') }) // Ref
div({ textContent: computed(() => 'Hello') }) // Computed
div({ textContent: () => 'Hello' }) // GetterThe component will automatically track dependencies and update when reactive values change.
Creating a Custom Runtime
You can create adapters for any reactive library:
import type { ReactiveRuntime } from '@rasenjs/core'
import { createSignal, createEffect, createMemo } from 'solid-js'
function createSolidRuntime(): ReactiveRuntime {
return {
watch(source, callback, options) {
let oldValue: any
let isFirst = true
createEffect(() => {
const newValue = source()
if (!isFirst || options?.immediate) {
callback(newValue, oldValue)
}
oldValue = newValue
isFirst = false
})
return () => {
// Solid handles cleanup automatically
}
},
effectScope() {
// Solid doesn't have explicit scopes
return {
run: (fn) => fn(),
stop: () => {}
}
},
ref(value) {
const [get, set] = createSignal(value)
return {
get value() { return get() },
set value(v) { set(v) }
}
},
computed(getter) {
const memo = createMemo(getter)
return {
get value() { return memo() }
}
},
unref(value) {
if (this.isRef(value)) {
return value.value
}
return value
},
isRef(value) {
return value && typeof value === 'object' && 'value' in value
}
}
}Best Practices
1. Initialize Early
Set the reactive runtime before any components are created:
// main.ts
import { setReactiveRuntime } from '@rasenjs/core'
import { createVueRuntime } from '@rasenjs/reactive-vue'
setReactiveRuntime(createVueRuntime())
// Now safe to import and use components
import { App } from './App'
mount(App(), document.getElementById('app'))2. Use the Library Directly
Don't import reactive primitives from Rasen — use the library directly:
// ✅ Good
import { ref, computed } from 'vue'
// ❌ Avoid
import { ref } from '@rasenjs/core' // Only for internal use3. Prefer Getters for Simple Expressions
For simple computed values, getter functions are often cleaner:
// Both work, but getters are more concise for simple cases
div({ textContent: () => `Count: ${count.value}` })
div({ textContent: computed(() => `Count: ${count.value}`) })Next Steps
- Render Targets — Learn about different hosts
- Components — Building reusable components