06 — Fast signals (createSignal)
use() has a small fixed cost — ~550 ns per call at runtime (hooks bookkeeping, devtools, lifecycle). For hot loops, animation frames, or hundreds of signals per component, drop to createSignal — the escape hatch added in Phase 1. Per-call cost: ~22 ns.
import { createSignal } from 'flexium/core'
import { unsafeEffect } from 'flexium/core'
// NOT a hook — call at module scope or anywhere
const [count, setCount] = createSignal(0)
console.log(count()) // 0 ← getter is a function call
setCount(1)
console.log(count()) // 1
// Subscribe with unsafeEffect
unsafeEffect(() => {
console.log('count is now', count())
})When to use
| Use case | API |
|---|---|
| React-style component hook | use(0) — value form, [value, setter] |
| Hot loop, animation, 100+ signals/component | createSignal(0) — getter form, [getter, setter] |
| Plain async/non-React code | createSignal(0) — works anywhere |
Compile-time substitution (Phase 7)
If you build with vite-plugin-flexium and optimize: 'auto', safe use() call sites are automatically rewritten to createSignal at build time:
// Source (you write):
function Counter() {
const [count, setCount] = use(0)
return <button onclick={() => setCount(c => c + 1)}>{count}</button>
}
// Compiled (Babel AST pass — invisible to you):
function Counter() {
const __sig0 = _createOwnedSignal(0)
const count = __sig0[0]()
const setCount = __sig0[1]
return <button onclick={() => setCount(c => c + 1)}>{count}</button>
}Result: per-call cost drops from 550 ns → 13 ns with zero code change. The compiler skips use(asyncFn), use(value, { key }), and conditional/loop usage (those fall back to the runtime path).
Coverage on real-world apps: ~70% of use(value) call sites transformed automatically.
Owner lifecycle
createSignal outside a component is a permanent module-level signal — no cleanup. Inside a component, prefer use(0) so cleanup runs on unmount.
Compile-time substitution uses _createOwnedSignal internally, which ties cleanup to the component via Phase 4's ComponentWeakRegistry (WeakRef + FinalizationRegistry). You don't have to think about it.
Computed
The functional form of createSignal's sibling:
import { createSignal, createComputed } from 'flexium/core'
const [count, setCount] = createSignal(0)
const doubled = createComputed(() => count() * 2)
console.log(doubled()) // 0
setCount(5)
console.log(doubled()) // 10Benchmarks
Measured on Apple M4, Node 24:
| Operation | use() | createSignal() | Speedup |
|---|---|---|---|
| Create signal | 14,000 ns | 22 ns | 636× |
| Write (no subscriber) | 167 ns | 7.3 ns | 23× |
| Update + sync end-to-end | 14,010 ns | 387 ns | 36× |
| Effect creation | 14,000 ns | 80 ns | 175× |
The escape hatch beats Solid.js's createSignal (~800 ns end-to-end).
API surface used
createSignal(initial)— getter-form signal[getter, setter]createComputed(fn)— getter-form derived signalcreateEffect(fn)— getter-form effectunsafeEffect(fn)— same ascreateEffect(auto-disposed if inside component)
Next
→ Shared state across components — use(initial, { key }).