subay
v0.0.12
Published
A lightweight reactive UI library with state management and template rendering
Downloads
439
Maintainers
Readme
Subay
Subay 是一个轻量级的响应式编程库,提供了状态管理和 DOM 操作的功能。
API
root
简介 创建一个根更新上下文,用于管理响应式状态的生命周期。
语法
root<T>(fn: (destroy: () => void) => T): T参数
fn: 一个函数,接收一个destroy函数作为参数,用于手动销毁根上下文。
返回值
T:fn函数的返回值。
例子
import { root, o, S } from 'subay';
root((destroy) => {
const count = o(0);
const doubled = S(() => count() * 2);
console.log(doubled()); // 0
count(1);
console.log(doubled()); // 2
// 手动销毁
destroy();
});注意
销毁使用 root 创建的上下文的唯一方式就是调用 destroy 回调。
S
简介 创建一个计算值,当依赖的可观察值变化时自动重新计算。
语法
S<T>(fn: (pv: T | undefined) => T, value?: T): IS<T>参数
fn: 计算函数,接收上一次的计算值作为参数。value: 可选的初始值。
返回值
IS<T>: 一个函数,调用时返回计算值。
例子
import { S, o } from 'subay';
const count = o(0);
const doubled = S(() => count() * 2);
console.log(doubled()); // 0
count(1);
console.log(doubled()); // 2o / observable
简介 创建一个可观察值,用于存储和更新状态。
语法
o<T>(value: T): IO<T>参数
value: 初始值。
返回值
IO<T>: 一个函数,无参数调用时返回当前值,带参数调用时更新值并返回新值。
例子
import { o } from 'subay';
const count = o(0);
console.log(count()); // 0
count(1);
console.log(count()); // 1cleanup
简介 注册一个清理函数,当当前更新上下文被销毁时执行。
语法
cleanup<T extends () => void>(f: T): T参数
f: 清理函数。
返回值
T: 传入的清理函数。
例子
import { root, S, o, cleanup } from 'subay';
root(() => {
const count = o(0);
S(() => {
console.log(count());
cleanup(() => {
console.log('Cleanup called');
});
});
count(1);
});
// 输出: 0, 1, Cleanup calledtransaction
简介 创建一个事务,批量更新可观察值,避免中间状态的计算。
语法
transaction<T>(f: () => T): T参数
f: 事务函数,在其中可以更新多个可观察值。
返回值
T:f函数的返回值。
例子
import { transaction, o, S } from 'subay';
const a = o(1);
const b = o(2);
const sum = S(() => a() + b());
S(() => console.log(sum())); // 3
transaction(() => {
a(2);
b(3);
});
// 输出: 5 (只计算一次)sample
简介 在不建立依赖关系的情况下获取可观察值的值。
语法
sample<T>(f: () => T): T参数
f: 函数,在其中可以访问可观察值但不会建立依赖关系。
返回值
T:f函数的返回值。
例子
import { sample, o, S } from 'subay';
const count = o(0);
let cachedValue;
S(() => {
// 不建立依赖关系
cachedValue = sample(() => count());
console.log(cachedValue);
});
count(1); // 不会触发重新计算
console.log(cachedValue); // 0isListening
简介 检查当前是否处于响应式监听状态。
语法
isListening(): boolean参数
- 无
返回值
boolean: 当前是否处于响应式监听状态。
例子
import { isListening, S } from 'subay';
console.log(isListening()); // false
S(() => {
console.log(isListening()); // true
});subscribe
简介 订阅一个函数,当依赖的可观察值变化时自动执行。
语法
subscribe(f: () => void): () => void参数
f: 订阅函数。
返回值
() => void: 取消订阅的函数。
例子
import { subscribe, o } from 'subay';
const count = o(0);
const unsubscribe = subscribe(() => {
console.log(count());
});
count(1); // 输出: 1
count(2); // 输出: 2
unsubscribe();
count(3); // 无输出unsubscribe
简介 取消订阅一个函数。
语法
unsubscribe(f: () => void): void参数
f: 要取消订阅的函数。
返回值
- 无
例子
import { subscribe, unsubscribe, o } from 'subay';
const count = o(0);
const fn = () => console.log(count());
subscribe(fn);
count(1); // 输出: 1
unsubscribe(fn);
count(2); // 无输出isObservable
简介 检查一个值是否是可观察值。
语法
isObservable(o: any): o is IO<any>参数
o: 要检查的值。
返回值
boolean: 是否是可观察值。
例子
import { isObservable, o, S } from 'subay';
const count = o(0);
const doubled = S(() => count() * 2);
console.log(isObservable(count)); // true
console.log(isObservable(doubled)); // falseisComputed
简介 检查一个值是否是计算值。
语法
isComputed(o: any): o is IS<any>参数
o: 要检查的值。
返回值
boolean: 是否是计算值。
例子
import { isComputed, o, S } from 'subay';
const count = o(0);
const doubled = S(() => count() * 2);
console.log(isComputed(count)); // false
console.log(isComputed(doubled)); // trueisReactive
简介 检查一个值是否是响应式值(可观察值或计算值)。
语法
isReactive(o: any): o is IO<any> | IS<any>参数
o: 要检查的值。
返回值
boolean: 是否是响应式值。
例子
import { isReactive, o, S } from 'subay';
const count = o(0);
const doubled = S(() => count() * 2);
console.log(isReactive(count)); // true
console.log(isReactive(doubled)); // true
console.log(isReactive(42)); // falsemap
简介 将一个数组映射为 DOM 节点数组,当数组变化时自动更新。
语法
map<T>(array: () => T[], f: (item: T) => Node[]): Node[]参数
array: 一个返回数组的函数。f: 映射函数,将数组项转换为 DOM 节点数组。
返回值
Node[]: DOM 节点数组。
例子
import { map, o, html } from 'subay';
const items = o(['a', 'b', 'c']);
const list = map(
() => items(),
(item) => html`<li>${item}</li>`,
);
document.body.append(...list);
// 渲染: <li>a</li><li>b</li><li>c</li>
items(['a', 'b', 'c', 'd']);
// 自动更新: <li>a</li><li>b</li><li>c</li><li>d</li>svg
简介 创建 SVG 元素的模板标签函数。
语法
svg(segments: TemplateStringsArray, ...values: any[]): Node[]参数
segments: 模板字符串的静态部分。values: 模板字符串的动态部分。
返回值
Node[]: SVG 元素节点数组。
例子
import { svg, o } from 'subay';
const radius = o(50);
const circle = svg`<circle cx="100" cy="100" r="${radius}" fill="red"/>`;
document.getElementById('svg').append(...circle);html
简介 创建 HTML 元素的模板标签函数。
语法
html(segments: TemplateStringsArray, ...values: any[]): Node[]参数
segments: 模板字符串的静态部分。values: 模板字符串的动态部分。
返回值
Node[]: HTML 元素节点数组。
例子
import { html, o } from 'subay';
const count = o(0);
const counter = html`
<div>
<p>Count: ${count}</p>
<button onclick=${() => count(count() + 1)}>Increment</button>
</div>
`;
document.body.append(...counter);
// 点击按钮会自动更新 count 的值使用 HTML
Subay 使用 DOMParser 解析 HTML 模板, html 的参数必须是合法的 HTML 字符串,(同理, svg 的参数必须是合法的 SVG 字符串), 这是有别于 JSX 的地方。
例子
import { html } from 'subay';
// 合法的 HTML 字符串
const validHtml = html`<div><p>Hello</p></div>`;
document.body.append(...validHtml);错误示例
import { html } from 'subay';
// 自闭合标签
const invalidHtml = html`<div>
<span />
<span />
</div>`; // 会导致解析成 <div><span><span></span></span></div>传入对象作为元素属性
如果需要传入一个对象作为元素属性,可以使用 ... 语法。
语法
html`<tag ...${props}></tag>`;例子
import { html, o } from 'subay';
const props = o({
className: 'container',
style: 'color: red;',
});
const element = html`<div ...${props}></div>`;
document.body.append(...element);组件的声明
组件是一个返回 Node[] 的函数,可以接收参数。和 React、Vue 等不同,组件的参数是一个列表。
语法
const Component = (text, children: () => Node[]) => {
return html`<div>${text}</div>`;
};例子
import { html } from 'subay';
const Greeting = (name: string, children: () => Node[]) => {
return html`
<div>
<h1>Hello, ${name}!</h1>
${children()}
</div>
`;
};组件的使用
在模板中使用组件时,在标签处引用组件。 因为模板是 HTML 字符串,引用的时候不支持自闭合标签。 传给组件的参数必须和组件声明的参数顺序一致。 组件获得的参数总是和传入的保持一致。
例子
import { html, o } from 'subay';
const Greeting = (greet: string, name: () => string) => {
return html`<h1>${greet}, ${name}!</h1>`;
};
const name = o('World');
const app = html` <div><${Greeting} greet=${'Hello'} name=${name}></${Greeting}></div> `;
document.body.append(...app);错误示例
import { html, o } from 'subay';
const Greeting = (greet: string, name: () => string) => {
return html`<h1>${greet}, ${name}!</h1>`;
};
const name = o('World');
// 错误, 后面的 span 会被当做 Greeting 的子节点。
const app = html`
<div>
<${Greeting}
greet=${'Hello'}
name=${name}
/>
<span></span>
</div>
`;
// 错误, 参数顺序和 Greeting 的参数不一致
const app = html` <div><${Greeting} name=${name} greet=${'Hi'} ></${Greeting}></div> `;
document.body.append(...app);动态的组件
标签位置的插值可以是 S 或 o 的返回值,可以根据条件动态选择要渲染的组件。
例子
import { html, o, S } from 'subay';
const Greeting = (name: () => string) => {
return html`<h1>Hello, ${name}!</h1>`;
};
const Farewell = (name: () => string) => {
return html`<h1>Goodbye, ${name}!</h1>`;
};
const showGreeting = o(true);
const name = o('World');
const Component = S(() => (showGreeting() ? Greeting : Farewell));
const app = html`
<div>
<${Component} name=${name} ></${Component}>
<button onclick=${() => showGreeting(!showGreeting())}>Toggle</button>
</div>
`;
document.body.append(...app);