nova-view
v0.1.18
Published
轻量级响应式 UI 库,提供信号、计算属性、侦听等能力,专注于直接操作真实 DOM
Downloads
2,073
Maintainers
Readme
Nova View
把 UI 写回 DOM,把更新交给 Signal,把产能交给 AI。
没有 JSX、没有虚拟 DOM:写下去的是原生 DOM,状态一变就精准更新。
⚡️ 极简主义的反叛
不依赖模板语法和虚拟 DOM 的黑盒;CDN 场景可零构建直用。链式 API 像搭积木一样构建 UI,AI 不用学框架语法也更不容易写错。🎯 手术刀级的更新
基于 Signal 的细粒度依赖追踪。状态变化时,只更新对应的 TextNode 或属性,绝不触碰无关 DOM。🏎️ 性能控制权回归
既有自动 Diff 的便捷,也有ForController的极致。需要榨干性能时,你可以绕过 Diff 算法,直接对列表进行增删改查。
⚡ 5 秒快速体验
复制这段代码到浏览器控制台,立即感受“原生 DOM + 响应式”的魔法:
const s=document.createElement('script');s.src='https://unpkg.com/nova-view/dist/lib/nova-dom.umd.min.js';s.onload=()=>{const{Dom:$,ref:r,mount:m}=window['nova-view'],{div:d,button:b,h1:h}=$,c=r(0),app=d().style('position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:2rem;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);text-align:center;z-index:9999;')(h().style('color:#333;margin-bottom:1rem;')('Hello Nova View!'),b().style('background:#007bff;color:white;border:none;padding:0.5rem 1rem;border-radius:4px;cursor:pointer;').onclick(()=>c.value++)(()=>('点击次数: '+c.value)));m(app,document.body)};document.head.appendChild(s)🤖 为什么它是“AI Native”的 UI 库?
并不是所有框架都适合让 AI 写。Nova View 的设计天然契合 LLM 的代码生成特性:
极低的语法“幻觉”率:
- 传统框架:AI 常搞混 Vue2/3 写法、React Hook 依赖数组或 JSX 闭合标签。
- Nova View:纯函数调用链(
div().class().onclick())。结构即逻辑,AI 极难写出语法错误的函数调用。
原生的调试反馈回路:
- AI 生成的代码直接返回
HTMLElement。如果出了问题,AI 可以直接生成原生 DOM API(如querySelector)代码来自我修正,无需理解复杂的框架运行时内部状态。
- AI 生成的代码直接返回
统一的状态心智:
- 没有复杂的指令集。动态内容要么是函数
() => val,要么是ref。这种高度统一的模式让 AI 生成逻辑时异常稳定。
- 没有复杂的指令集。动态内容要么是函数
💡 提示词模板: "用 nova-view 写一个计数器,使用 ref 管理状态,div 作为容器,style 使用对象写法..." -> AI 一次通过率极高。
💡 为什么选择 Nova View?
原生 DOM API - 冗长且容易出错:
const div = document.createElement('div');
div.className = 'container';
div.style.padding = '1rem';
const span = document.createElement('span');
span.textContent = 'Hello';
div.appendChild(span);Nova View - 优雅且强大:
// 底层基于 Proxy 实现按需拦截,内置属性缓存与 Style Diff
div().class('container').style({ padding: '1rem' })(span()('Hello'));代码量减少 70%,却拥有了完整的响应式能力和 TypeScript 支持。
📦 安装
包管理器安装 (推荐)
npm install nova-view
# 或 yarn add nova-view
# 或 pnpm add nova-viewCDN 直接使用 (零构建)
无需安装,直接在 HTML 中引入(推荐使用 unpkg):
<!DOCTYPE html>
<html>
<head>
<script type="importmap">
{
"imports": {
"nova-view": "https://unpkg.com/nova-view/dist/lib/nova-dom.min.js"
}
}
</script>
</head>
<body>
<div id="app"></div>
<script type="module">
import { Dom, ref, component, mount } from 'nova-view';
const { div, button, h1 } = Dom;
const Counter = component(() => {
const count = ref(0);
return div()(
h1()(() => `Count: ${count.value}`),
button().onclick(() => count.value++)('点击')
);
});
mount(Counter(), document.getElementById('app'));
</script>
</body>
</html>🚀 核心概念与 API
1. 链式 API:不仅仅是语法糖
Nova View 的链式 API 背后的 DomElement 做了大量脏活累活:
- 智能属性缓存:同值不重复写入 DOM,减少 Layout/Paint。
- Style Diff:对象写法支持细粒度更新,动态样式自动解绑副作用。
- Proxy 懒加载:只有访问到的属性才会创建 Setter。
import { Dom, ref } from 'nova-view';
const { div, span, button } = Dom;
const count = ref(0);
// 第一个括号:配置属性(支持静态值、Ref、函数)
div()
.class('card')
.style(() => ({
color: count.value > 5 ? 'red' : 'black', // 动态样式
padding: '1rem'
}))
// 第二个括号:添加内容(支持字符串、节点、数组、函数)
(
span()('Current count: '),
span()(() => count.value), // 细粒度更新 TextNode
button().onclick(() => count.value++)('+')
);2. 响应式系统:Signal 驱动
基于 track/trigger 的响应式系统,支持 batch 批量更新。
import { ref, computed, watchEffect, batch } from 'nova-view';
const count = ref(0);
const double = computed(() => count.value * 2);
watchEffect(() => {
console.log(`Count: ${count.value}, Double: ${double.value}`);
});
// 批量更新:多次修改状态,只触发一次 DOM 刷新
batch(() => {
count.value++;
count.value++;
});3. 组件:返回真实 Node
组件本质上就是返回 DOM 节点的函数。
import { Dom, component, onMounted } from 'nova-view';
const { div } = Dom;
const MyButton = component(() => {
// 这里的代码只在组件初始化时运行一次
const btn = document.createElement('button'); // 甚至可以混合原生 DOM
onMounted(() => console.log('Mounted!'));
return div()(btn); // 返回 Node
});🎮 控制流与列表:性能直通车
Show / Switch:精确的 DOM 替换
利用 节点池 (Node Pool) 技术复用 Text/Comment 节点,减少高频切换时的 GC 压力。
Show(() => isLoading.value, {
when: () => div()('Loading...'),
fallback: () => div()('Content'),
keepAlive: true // 可选:使用 display:none 切换而非移除 DOM
});For:智能列表渲染
内置 LIS (最长递增子序列) 算法,确保最小化 DOM 移动。
For(() => items.value, {
key: item => item.id,
children: item => div()(item.name)
});🚀 ForController:绕过 Diff 的极致性能
哪怕是最高效的 Diff 算法也有开销。在处理万级数据或高频实时更新(如股票K线、即时日志)时,你可以使用 controller 获得“上帝权限”:
let listController: ForController<Item> | null = null;
For(() => state.items, {
key: it => it.id,
children: (it) => ItemComponent(it),
controller: (ctrl) => { listController = ctrl; }
});
// ❌ 传统方式:修改数据 -> 触发 Diff -> 更新 DOM
// list.value = newList;
// ✅ Nova 方式:直接操作 DOM 映射,0 Diff 开销
// 比 React 快 20%~80%(视操作密度而定)
batch(() => {
// 直接更新特定项,不触发整个列表的重渲染
listController?.updateItem(itemId, item => ({ ...item, price: newPrice }));
// 复用现有 DOM 节点进行全量替换
listController?.replaceAll(newItems, { reuseDOM: true });
});这是 Nova View 区别于大多数 MVVM 框架的硬核能力:我们允许你为了性能“作弊”。
⚡ 性能基准 (Benchmark)
我们在 js-framework-benchmark 的标准场景下进行了测试。得益于无 Virtual DOM 开销和精细的更新策略,Nova View 在多项指标上表现优异。
- DOM 创建与替换:直接操作原生节点,无额外对象创建开销。
- 内存占用:得益于 TextNode/CommentNode 对象池 技术,内存波动极低。
- 列表更新:
- 常规模式 (For):自动 Diff,性能与主流框架持平。
- 极速模式 (Controller):在特定场景下(如全部替换、定点更新),通过绕过 Diff 逻辑,比 React/Vue 快 20%~84%(视具体操作密度而定,详见 bench 报告)。
注:性能数据基于 M1/M2 芯片测试环境,实际收益取决于业务场景。推荐在大量数据更新场景使用 Controller 模式。
📚 更多功能
- VirtualList: 开箱即用的虚拟滚动组件,支持动态高度。
- ErrorBoundary: 优雅处理组件树错误。
- Context API:
provide/inject依赖注入。 - 异步组件: 支持
async/await组件定义。
🎯 使用场景
- ✅ AI 辅助开发 - 语法简单,AI 生成代码准确率高。
- ✅ 中小型 Web 应用 - 快速构建,打包体积极小。
- ✅ 极致性能场景 - 大屏数据展示、高频实时更新列表。
- ✅ 微前端 / 嵌入式 - 无需全家桶,轻松嵌入现有项目。
📚 深入指南(细节版)
你可以把上面当作“快速入口”。下面这一段是面向想把 Nova View 用到生产环境的人:把常见能力、边界和技巧一次讲透。
🎨 链式 API:双括号 ()() 的完整心智模型
Nova View 把 UI 写法拆成两个阶段:
- 第一段
():创建元素并“配置”(属性、事件、样式、class…) - 第二段
():添加内容(子节点/字符串/函数/Ref…)
import { Dom } from 'nova-view';
const { div, button, span } = Dom;
div() // 创建元素
.class('container') // 配置:class / id / style / on*
.id('app')
.style({ padding: '1rem' })
( // 添加内容
span()('Hello'),
button().onclick(() => console.log('clicked'))('Click'),
);样式绑定:静态 / 混合 / 全响应式
import { Dom, ref } from 'nova-view';
const { div } = Dom;
// 1) 静态对象
div().style({ padding: '1rem', borderRadius: '8px' });
// 2) 混合:静态 + 响应式函数
const size = ref(16);
div().style({
padding: '1rem',
fontSize: () => `${size.value}px`,
color: () => (size.value > 20 ? 'red' : '#111'),
});
// 3) 全响应式:函数返回对象
div().style(() => ({
transform: `translateY(${size.value}px)`,
opacity: size.value > 20 ? 0.8 : 1,
}));响应式属性绑定:函数或 Ref
import { Dom, ref } from 'nova-view';
const { button } = Dom;
const count = ref(0);
button()
.class(() => `btn ${count.value > 0 ? 'active' : ''}`)
.disabled(() => count.value >= 10)
.onclick(() => count.value++)
(() => `Count: ${count.value}`);嵌套结构:用代码保持层级清晰
import { Dom } from 'nova-view';
const { div, header, h1, nav, a, main, section } = Dom;
div().class('app')(
header().class('header')(
h1()('My App'),
nav().class('nav')(
a().href('/home')('Home'),
a().href('/about')('About'),
),
),
main().class('main')(
section().class('content')('Content here'),
),
);🎯 直接返回真实 DOM(这不是“抽象对象”)
Nova View 的元素就是浏览器原生节点,你可以直接调用任何 DOM API。
import { Dom, component, onMounted } from 'nova-view';
const { button } = Dom;
const EnhancedButton = component(() => {
const el = button()
.class('my-button')
.onclick(() => console.log('clicked'))
('Click me');
onMounted(() => {
const rect = el.getBoundingClientRect();
console.log('按钮位置:', rect.x, rect.y);
el.style.transform = 'scale(1.05)';
});
return el;
});⚡ 响应式系统:ref / reactive / computed / watch / batch
import { ref, reactive, computed, watch, watchEffect, batch } from 'nova-view';
const count = ref(0);
const user = reactive({ name: 'Alice', age: 25 });
const double = computed(() => count.value * 2);
watch(() => count.value, (n, o) => console.log('count:', o, '->', n));
watchEffect(() => console.log('double:', double.value));
batch(() => {
user.name = 'Bob';
count.value += 2;
});🧩 组件系统(含插槽/事件/异步)
基础组件
import { Dom, ref, component, mount } from 'nova-view';
const { div, h1, button } = Dom;
const Counter = component(() => {
const count = ref(0);
return div().class('counter')(
h1()(() => `Count: ${count.value}`),
button().onclick(() => count.value++)('+'),
button().onclick(() => count.value--)('-'),
);
});
mount(Counter(), document.getElementById('app')!);插槽(slots):默认插槽 / 具名插槽
import { Dom, component, type Slots } from 'nova-view';
const { div, button } = Dom;
const Card = component<{ title: string }>((props, { slots }) => {
return div().class('card')(
div().class('card-header')(props.title),
div().class('card-body')(slots.default?.()),
div().class('card-footer')(slots.footer?.()),
);
});
const App = component(() =>
Card({ title: '我的卡片' }, {
default: () => div()('这是卡片内容'),
footer: () => button()('确定'),
})
);作用域插槽:给插槽传数据
import { Dom, component } from 'nova-view';
const { div, span } = Dom;
type Item = { id: number; name: string; status: 'active' | 'inactive' };
const DataList = component<{ items: Item[] }>((props, { slots }) => {
return div().class('data-list')(
...props.items.map(item =>
slots.default?.(item) || div()(item.name)
)
);
});
const App = component(() => {
const items: Item[] = [
{ id: 1, name: '项目1', status: 'active' },
{ id: 2, name: '项目2', status: 'inactive' },
];
return DataList({ items }, {
default: (item: Item) =>
div().class(() => `item ${item.status}`)(
span()(item.name),
span().class('status')(item.status === 'active' ? '✓' : '✗'),
),
});
});自定义事件:emit + onXxx
import { Dom, component } from 'nova-view';
const { button } = Dom;
const CustomButton = component<{ variant?: 'primary' | 'secondary' }>((props, { emit }) => {
return button()
.class(() => `btn btn-${props.variant || 'primary'}`)
.onclick(() => emit('click', { timestamp: Date.now() }))
('点击我');
});
const App = component(() =>
CustomButton({ variant: 'primary' }, {
onClick: (data) => console.log('按钮被点击', data),
})
);异步组件:直接 async 返回 Node
import { Dom, component } from 'nova-view';
const { div, h2, p } = Dom;
const AsyncData = component(async () => {
const resp = await fetch('/api/data');
const data = await resp.json();
return div().class('data-view')(
h2()('异步数据'),
p()(data.message),
);
});注意:
await与生命周期/上下文的关系(务必看)
在当前实现下,async component会在第一次await之后丢失“当前组件实例上下文”。
这意味着:
- ✅
await之前调用onMounted/onUpdated/onUnmounted/provide/inject等“依赖当前实例”的 API 是可靠的- ⚠️
await之后再调用这些 API,通常不会被注册到该组件实例上(表现为钩子不触发 / provide 不生效)推荐写法:把钩子注册和 provide/inject 放在
await之前;await之后只做数据获取,然后更新ref/reactive来驱动 UI。
🧷 Hook 组件:把组件也做成链式 API
import { Dom, component, hook, mount } from 'nova-view';
const { div } = Dom;
const MyCard = component<{ title: string; active?: boolean }>((props) => {
return div().class(() => `card ${props.active ? 'active' : ''}`)(
div().class('title')(props.title),
);
});
const Card = hook(MyCard);
mount(Card().title('Hello').active(true)(), document.body);🧠 条件渲染 / 多分支 / 列表渲染(更完整示例)
Show:支持 keepAlive
import { Dom, ref, Show } from 'nova-view';
const { div } = Dom;
const isLoading = ref(true);
Show(() => isLoading.value, {
when: () => div()('加载中...'),
fallback: () => div()('内容'),
keepAlive: true,
});Switch:多分支更直观
import { Dom, ref, Switch } from 'nova-view';
const { div } = Dom;
const status = ref<'loading' | 'error' | 'success'>('loading');
Switch(() => status.value, [
[() => status.value === 'loading', () => div()('加载中')],
[() => status.value === 'error', () => div()('出错了!')],
[true, () => div()('成功')],
]);For:keyed 列表 + controller
import { Dom, ref, For, type ForController } from 'nova-view';
const { div } = Dom;
type Row = { id: number; name: string };
const items = ref<Row[]>([
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
]);
let controller: ForController<Row> | null = null;
For(() => items.value, {
key: (it) => it.id,
children: (it) => div().class('row')(it.name),
controller: (ctrl) => { controller = ctrl; },
});
// controller?.updateItem(1, (it) => ({ ...it, name: 'A+' }));
// controller?.removeItem(2);
// controller?.replaceAll(newItems, { reuseDOM: true });🧱 VirtualList:虚拟滚动(配置说明)
适合超大列表,只渲染可视区内容,保持滚动流畅。
核心属性:
- items:
() => T[]列表数据(必填) - height:容器高度(必填)
- itemHeight:固定高度或
(item, index) => number - overscan:额外渲染的行数,减少闪烁
- key:为每行生成 key
- children:渲染函数(必填)
- onReachEnd / threshold:触底加载
🛡️ ErrorBoundary:错误边界(配置说明)
捕获子组件树中的错误,避免整个应用崩溃。
核心属性:
- children:
() => Node(必填) - fallback:
(error, reset) => Node(可选) - onError:错误上报回调(可选)
💡 最佳实践(更贴近真实项目)
- 批量更新:高频更新时优先用
batch()合并刷新 - 列表性能:常规用
For;明确知道更新路径/追求极限时用ForController - KeepAlive:
Show(..., { keepAlive: true })适合昂贵组件的频繁切换 - 组织方式:把业务逻辑抽到组合函数(
useXxx),UI 保持纯粹
🚚 迁移指南(从 React / Vue / 原生)
迁移的核心不是“语法替换”,而是心智转换:
- 从 React:
useState->ref/reactive;useEffect->watchEffect;ref + useEffect才能拿 DOM -> 直接返回 DOM - 从 Vue:
ref/reactive/computed/watch基本同构;模板 -> 链式 DSL - 从原生:命令式创建 -> 声明式组合;事件监听 ->
.onclick/.oninput...
📖 文档与资源
- 📖 在线文档 - 完整的 API 文档和示例
- 📚 完整 API 参考 - 详细的 API 参数说明
- 💻 示例项目 - 查看实际应用示例
🤝 贡献
欢迎提交 Issue 和 Pull Request!我们非常欢迎社区的贡献。
📄 许可证
Made with ❤️ by the Nova View team
