alef-component
alef-component copied to clipboard
Stage 1 Specification
This stage includes the basic features of Alef Component.
Elements Rendering
The core concept of Alef Component is AOT compilation, that means JSX elements in Alef Component will be transpiled to native DOM operations, no Virtual DOM needed, that gives your the vanilla DOM updating preference. A JSX.Element starts with $ label (Side Effect) will add a node to the component, you can have multiple nodes in single component. Any variable declare will be treated as a reactive state in Alef Component which can cause view updates when it changed.
// App.alef
let name: string = 'World'
let n: number = 0
$: <div>
<p>Hello {name}!</p>
<p onClick={() => n++}>current count is {n}</p>
</div>
// App.output.js
export default function App {
const self = new Component()
// strip types
let name = 'World'
let n = 0
// create nodes
const nodes = [
Element(
'div',
null,
Element('p', null , `Hello ${name}!`),
Element(
'p',
{
onClick: Dirty(() => n++, [0])
},
'current count is ',
Memo(() => n, [0])
)
),
]
// register nodes
self.register(nodes)
}
Conditional Rendering
Alef Component uses the standard JSX syntax, you can write conditional statements just like React.
// App.alef
let ok: boolean = false
function toggle() {
ok = !ok
}
$: <p>
{ok ? (
<button onClick={toggle}>OFF</button>
) : (
<button onClick={toggle}>ON</button>
)}
</p>
// App.output.js
export default function App {
const self = new Component()
// strip types
let ok = false
function toggle() {
ok = !ok // dirty data: ok
}
// create nodes
const nodes = [
Element(
'p',
null,
Memo(() => ok ? Element(
'button',
{ onClick: Dirty(toggle, [0]) },
'ON'
):
Element(
'button',
{ onClick: Dirty(toggle, [0]) },
'OFF'
), [0]),
)
]
// register nodes
self.register(nodes)
return self
}
Loop Rendering
To render a list, you can put an array of JSX.Element like React, each JSX.Element should have key property. In Aleh Component, the self-update methods like push, unshift and splice of the array object will trigger the ListBlock update automatically.
// App.alef
let numbers: number[] = [1, 2, 3]
$: <>
<ul>
{numbers.map(n => (
<li key={n}>{n}</li>
)}
</ul>
<button onClick={() => { numbers.push(numbers.length + 1) }}>Add number</button>
</>
// App.output.js
export default function App {
const self = new Component()
// strip types
let numbers /* Array */ = Proxy([1, 2, 3], [0])
// create nodes
const nodes = [
Element(
'ul',
null,
Memo(() => numbers.map(n => Element('li', { key: n }, n)), [0])
),
Element(
'button', {
onClick: () => { numbers.push(numbers.length + 1) }
},
'Add number'
)
]
// register nodes
self.register(nodes)
return self
}
Memo
A Memo is a computed state which is a reactive constant, that is useful to reuse some computed expressions with states. To create a complex memo you can use IIFE expression.
// App.alef
let n: number = 0
const double: nubmer = 2 * n
const quad: nubmer = (() => 2 * double)()
$: <p onClick={() => n++}>
double is {double}, quad is {quad}, octuple is {2 * quad}
</p>
// App.output.js
export default function App {
const self = new Component()
// strip types
let n = 0
// create memos
const $double = Memo(() => 2 * n, [0])
const $quad = Memo(() => 2 * $double.value, [0])
// create nodes
const nodes = [
Element(
'p',
{ onClick: Dirty(() => n++, [0]) },
'\n double is ',
$double,
', quad is ',
$quad,
', octuple is ',
Memo(() => 2 * $quad.value, [0])
'\n'
)
]
// register nodes
self.register(nodes)
return self
}
Side Effect
The Side Effect will happen when a component mounted or the dependent states changed. You call return a dispose funtion to clear asynchronous event listeners defined in the side effect callback. JSX expression will be treated as a Side Effect that can react to UI.
// App.alef
let n: number = 0
$: console.log(`current count is ${n}`)
$: () => {
console.log('mounted')
() => console.log('unmounted')
}
$: <p onClick={() => n++}>{n}</p>
// App.output.js
export default function App {
const self = new Component()
// strip types
let n = 0
// create effects
const $$effect = Effect(() => {
console.log(`current count is ${n}`)
}, [0])
const $$effect2 = Effect(() => {
console.log(`mounted`)
() => console.log(`unmounted`)
}, [])
// create nodes
const nodes = [
Element('p', { onClick: Dirty(() => n++, [0])}, Memo(() => n, [0]))
]
// register nodes
self.register(nodes)
// register effects
self.onMount($$effect, $$effect2)
return self
}
Events
Aleh Component uses JSX event property name like onClick={CALLBACK}, you can write event callbacks in JSX expression, or create event handles out of JSX to get better readability.
// App.alef
let n: number = 0
function increase() {
n++
}
$: <div>
<p>current count is {n}</p>
<button onClick={() => n-- }>-</button>
<button onClick={increase}>+</button>
<div>
// App.output.js
export default function App {
const self = new Component()
// strip types
let n = 0
function increase() {
n++
}
// create nodes
const nodes = [
Element(
'div',
null,
Element('p', null, 'current count is ', Memo(() => n, [0])),
Element('button', { onClick: Dirty(() => n--, [0])}, '-'),
Element('button', { onClick: Dirty(increase, [0])}, '+')
),
]
// register nodes
self.register(nodes)
return self
}
Alef Component does not support two-way binding in form elements, you will need to update the state manually by the change event:
let value = ''
<input value={value} onChange={e => value = e.target.value} />
A specify JSX namespace window provided to allow you create global event handles in a component, with that you will not need to concern event disposing:
$: <window.self onResize={onResize} />
$: <window.document onKeyUp={onKeyUp} />
or you can listen global DOM/IO events in Side Effect expression $: () => {}, but should be vary careful to return a dispose function that can clear all the listeners to avoid memory leak:
$: () => {
const onResize = () => { ... }
window.AddEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}
@ije Will the compiler allow using a ternary operator instead of an if statement for a conditional? I find it much more concise
@shadowtime2000 yes, please ref #8 , i also think the top if-else is kind of weird...
if you are a rust user, it's vary familiar. but in JS i'm not sure... i'm trying to extend the conditional rendering that is weak in JSX syntax.