✅
简洁易用
轻量、直观的 API,适合渐进式接入;保留熟悉的 JSX / TSX 开发方式,无需额外模板语法。
🦀
编译驱动的原生 DOM 渲染
静态 JSX 直接生成 DOM;Signal 交互加载 compiled core,复杂组件与结构按需回退 Vapor。
🤝
React JSX + Vue 式响应式
适合希望保留 React 风格 JSX,同时获得 ref、reactive、computed 等 Vue 式响应式 API 的项目。
import { type FC, ref } from '@rue-js/rue'
const App: FC = () => {
const count = ref(0)
return (
<div className="card h-[310px] border border-base-300/70 bg-base-100/80 shadow-sm backdrop-blur-sm">
<div className="card-body flex h-full p-6">
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-sm uppercase tracking-[0.22em] text-base-content/45">Live Demo</div>
<div className="mt-2 text-sm text-base-content/70">
一个 ref signal 同时驱动计数和 v-if 条件分支。
</div>
</div>
<div className="badge badge-outline badge-lg">{count.value}</div>
</div>
<div className="mt-6 grid gap-4">
<button
className="rounded-xl border border-base-300 bg-base-100 px-4 py-3 text-base font-medium text-base-content shadow-sm transition hover:border-primary hover:text-primary"
onClick={() => count.value++}
>
计数:{count.value}
</button>
<div className="min-h-[96px] rounded-2xl border border-base-300/70 bg-base-200/40 p-4">
<div v-if={count.value % 2 === 0} className="flex h-full items-center justify-between gap-3">
<div>
<div className="font-semibold text-base-content">偶数态 UI</div>
<div className="mt-1 text-sm text-base-content/70">当前展示欢迎提示卡片。</div>
</div>
<span className="badge badge-info badge-lg">v-if</span>
</div>
<div v-else className="flex h-full items-center justify-between gap-3">
<div>
<div className="font-semibold text-base-content">奇数态 UI</div>
<div className="mt-1 text-sm text-base-content/70">signal 更新后切到另一套界面。</div>
</div>
<span className="badge badge-success badge-lg">v-else</span>
</div>
</div>
</div>
<div className="mt-auto text-xs tracking-[0.16em] text-base-content/45">
点击按钮时,下面的两个 UI 会跟着 signal 一起切换。
</div>
</div>
</div>
)
}
export default AppLive Demo
一个 ref signal 同时驱动计数和 v-if 条件分支。
0
偶数态 UI
当前展示欢迎提示卡片。
奇数态 UI
signal 更新后切到另一套界面。
点击按钮时,下面的两个 UI 会跟着 signal 一起切换。
用组件组织界面
Rue 以 JSX / TSX 组件表达界面中的可复用片段。组件同时包含结构与逻辑,按需组合即可形成页面或模块。
import { type FC } from '@rue-js/rue'
const Hello: FC = () => (
<div className="card bg-primary text-primary-content shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Hello</div>
<div className="mt-2 text-sm opacity-90">Hello component</div>
</div>
</div>
)
const World: FC = () => (
<div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">World</div>
<div className="mt-2 text-sm text-base-content/70">World component</div>
</div>
</div>
)
const HelloRue: FC = () => (
<div className="card bg-accent text-accent-content shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Hi</div>
<div className="mt-2 text-sm opacity-90">Rue</div>
</div>
</div>
)
const IAmRue: FC = () => (
<div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Yes</div>
<div className="mt-2 text-sm text-base-content/70">My name is Rue</div>
</div>
</div>
)
const HelloWorld: FC = () => (
<div className="grid gap-6">
<Hello />
<World />
<HelloRue />
<IAmRue />
</div>
)
export default HelloWorldHello
Hello component
World
World component
Hi
Rue
Yes
My name is Rue
Rue 的组件强调可复用与可组合,保持清晰的数据与事件流,也方便把交互片段渐进接入现有页面。
细粒度响应式:更贴近真实 DOM 更新模型
Rue 的组件、状态和渲染路径都建立在细粒度响应式系统之上,依赖变化后只接管受影响的更新边界。
编译器会把 JSX 路由为静态 DOM、Signal compiled core 或 Vapor fallback,只加载保持当前语义所需的最小能力。
const Hello: FC = () => (
<div className="card bg-primary text-primary-content shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Hello</div>
<div className="mt-2 text-sm opacity-90">Hello component</div>
</div>
</div>
)
const World: FC = () => (
<div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">World</div>
<div className="mt-2 text-sm text-base-content/70">World component</div>
</div>
</div>
)
const HelloRue: FC = () => (
<div className="card bg-accent text-accent-content shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Hi</div>
<div className="mt-2 text-sm opacity-90">Rue</div>
</div>
</div>
)
const IAmRue: FC = () => (
<div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
<div className="card-body items-center text-center">
<div className="text-3xl font-extrabold">Yes</div>
<div className="mt-2 text-sm text-base-content/70">My name is Rue</div>
</div>
</div>
)
const HelloWorld: FC = () => (
<div className="grid gap-6">
<Hello />
<World />
<HelloRue />
<IAmRue />
</div>
)
export default HelloWorld/* RUE_TRANSFORMED */
import { _$template, _$compiledCreateDocumentFragment, _$compiledAppendChild, _$compiledBatch, _$compiledCreateElement, _$compiledCreateTextNode, _$compiledRoot, _$mountCompiledComponent, _$withCompiledPropsUpdater } from "@rue-js/rue/internal/compiler";
const _$getTemplate5 = _$template('<div class="grid gap-6"><!--rue:opaque-hole:0--><!--rue:opaque-hole:1--><!--rue:opaque-hole:2--><!--rue:opaque-hole:3--></div>');
const Hello = ()=>{
return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
const _root = _$compiledCreateElement("div", __rue_parent_context);
_root.className = "card bg-primary text-primary-content shadow-sm";
const _el1 = _$compiledCreateElement("div", _root);
_$compiledAppendChild(_root, _el1);
_el1.className = "card-body items-center text-center";
const _el2 = _$compiledCreateElement("div", _el1);
_$compiledAppendChild(_el1, _el2);
_el2.className = "text-3xl font-extrabold";
_$compiledAppendChild(_el2, _$compiledCreateTextNode("Hello"));
const _el3 = _$compiledCreateElement("div", _el1);
_$compiledAppendChild(_el1, _el3);
_el3.className = "mt-2 text-sm opacity-90";
_$compiledAppendChild(_el3, _$compiledCreateTextNode("Hello component"));
return {
__rue_compiled_host: _root,
__rue_compiled_roots: [
_root
]
};
}, {
__rue_compiled_explicit_roots: true
})), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const World = ()=>{
return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
const _root = _$compiledCreateElement("div", __rue_parent_context);
_root.className = "card bg-base-100 text-base-content border border-base-200 shadow-sm";
const _el4 = _$compiledCreateElement("div", _root);
_$compiledAppendChild(_root, _el4);
_el4.className = "card-body items-center text-center";
const _el5 = _$compiledCreateElement("div", _el4);
_$compiledAppendChild(_el4, _el5);
_el5.className = "text-3xl font-extrabold";
_$compiledAppendChild(_el5, _$compiledCreateTextNode("World"));
const _el6 = _$compiledCreateElement("div", _el4);
_$compiledAppendChild(_el4, _el6);
_el6.className = "mt-2 text-sm text-base-content/70";
_$compiledAppendChild(_el6, _$compiledCreateTextNode("World component"));
return {
__rue_compiled_host: _root,
__rue_compiled_roots: [
_root
]
};
}, {
__rue_compiled_explicit_roots: true
})), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const HelloRue = ()=>{
return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
const _root = _$compiledCreateElement("div", __rue_parent_context);
_root.className = "card bg-accent text-accent-content shadow-sm";
const _el7 = _$compiledCreateElement("div", _root);
_$compiledAppendChild(_root, _el7);
_el7.className = "card-body items-center text-center";
const _el8 = _$compiledCreateElement("div", _el7);
_$compiledAppendChild(_el7, _el8);
_el8.className = "text-3xl font-extrabold";
_$compiledAppendChild(_el8, _$compiledCreateTextNode("Hi"));
const _el9 = _$compiledCreateElement("div", _el7);
_$compiledAppendChild(_el7, _el9);
_el9.className = "mt-2 text-sm opacity-90";
_$compiledAppendChild(_el9, _$compiledCreateTextNode("Rue"));
return {
__rue_compiled_host: _root,
__rue_compiled_roots: [
_root
]
};
}, {
__rue_compiled_explicit_roots: true
})), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const IAmRue = ()=>{
return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
const _root = _$compiledCreateElement("div", __rue_parent_context);
_root.className = "card bg-base-100 text-base-content border border-base-200 shadow-sm";
const _el10 = _$compiledCreateElement("div", _root);
_$compiledAppendChild(_root, _el10);
_el10.className = "card-body items-center text-center";
const _el11 = _$compiledCreateElement("div", _el10);
_$compiledAppendChild(_el10, _el11);
_el11.className = "text-3xl font-extrabold";
_$compiledAppendChild(_el11, _$compiledCreateTextNode("Yes"));
const _el12 = _$compiledCreateElement("div", _el10);
_$compiledAppendChild(_el10, _el12);
_el12.className = "mt-2 text-sm text-base-content/70";
_$compiledAppendChild(_el12, _$compiledCreateTextNode("My name is Rue"));
return {
__rue_compiled_host: _root,
__rue_compiled_roots: [
_root
]
};
}, {
__rue_compiled_explicit_roots: true
})), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const HelloWorld = ()=>{
return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
const _fragment = _$getTemplate5().content.cloneNode(true);
const _root = _fragment.firstChild;
const _el13 = _root.childNodes[0];
const _el14 = _el13.parentNode;
const _el15 = _root.childNodes[1];
const _el16 = _el15.parentNode;
const _el17 = _root.childNodes[2];
const _el18 = _el17.parentNode;
const _el19 = _root.childNodes[3];
const _el20 = _el19.parentNode;
const _el21 = _$compiledCreateDocumentFragment(_el14);
_$mountCompiledComponent(_el21, Hello, ()=>({}));
_el14.insertBefore(_el21, _el13);
const _el22 = _$compiledCreateDocumentFragment(_el16);
_$mountCompiledComponent(_el22, World, ()=>({}));
_el16.insertBefore(_el22, _el15);
const _el23 = _$compiledCreateDocumentFragment(_el18);
_$mountCompiledComponent(_el23, HelloRue, ()=>({}));
_el18.insertBefore(_el23, _el17);
const _el24 = _$compiledCreateDocumentFragment(_el20);
_$mountCompiledComponent(_el24, IAmRue, ()=>({}));
_el20.insertBefore(_el24, _el19);
return {
__rue_compiled_host: _root,
__rue_compiled_roots: [
_root
]
};
}, {
__rue_compiled_explicit_roots: true
})), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
export default HelloWorld;纯静态 JSX 可以没有 Rue 值运行时;Signal 页面使用最小 compiled core。组件、Hydration、Teleport、Transition 等复杂能力仍会按需进入 Vapor,因此这不是“所有应用绝对零运行时”的承诺。
React 风格 JSX,Vue 式响应式 API
Rue 适合希望保留 React 风格 JSX,同时获得 Vue 式响应式 API 的项目。你既可以用 useState,也可以用 ref / reactive / computed 来组织交互逻辑。
import { type FC, useState, ref, reactive, computed } from '@rue-js/rue';
type Video = { title: string; desc: string };
const videos: Video[] = [
{ title: '原始 DOM 编程', desc: '直接操作节点与事件' },
{ title: 'jQuery 的崛起', desc: 'Write Less, Do More' },
{ title: 'Backbone.js 与 MVC', desc: '早期前端架构探索' },
{ title: 'Web Components', desc: '原生组件标准' },
{ title: '现代构建工具与生态', desc: '模块化与开发体验' },
];
const SearchInput: FC<{ value: string; onChange: (t: string) => void }> = p => (
<input
className="w-full rounded-md border border-base-300 bg-base-100 px-3 py-2 text-base-content shadow-sm focus:border-violet-500 focus:ring focus:ring-violet-200"
value={p.value}
onInput={(e: any) => p.onChange((e.target as HTMLInputElement).value)}
placeholder="搜索视频"
/>
)
// VideoList 组件
const VideoList: FC<{ videos: Video[]; emptyHeading?: string }> = p => (
<div className="mt-3 space-y-2">
<div className="text-sm text-base-content/70">{p.videos.length} 个视频</div>
{p.videos.length === 0 ? (
<div className="rounded-md border border-base-300/70 bg-base-100/70 backdrop-blur-sm p-3 text-sm text-base-content/70">
{p.emptyHeading || '暂无匹配'}
</div>
) : (
<ul className="space-y-2">
{p.videos.map((v, i) => (
<li
key={i}
className="rounded-md border border-base-300/70 bg-base-100/70 backdrop-blur-sm p-3"
>
<div className="font-medium text-base-content">{v.title}</div>
<div className="text-sm text-base-content/70">{v.desc}</div>
</li>
))}
</ul>
)}
</div>
)
const SearchableVideoList: FC<{ videos: Video[] }> = (p) => {
const [searchText, setSearchText] = useState('');
const foundVideos = computed(() =>
p.videos.filter(v =>
v.title.toLowerCase().includes(searchText.value.toLowerCase()),
)
)
return (
<>
<SearchInput value={searchText.value} onChange={setSearchText} />
<VideoList videos={foundVideos} emptyHeading={`没有匹配 “${searchText.value}”`} />
</>
);
};
const ReactiveDemo: FC = () => {
const count = ref(0);
const state = reactive({ enabled: false });
return (
<div className="rounded-xl border border-gray-200/70 bg-white/60 backdrop-blur-sm p-4">
<div className="flex items-center gap-3">
<button className="btn btn-primary" onClick={() => (count.value++)}>+1</button>
<button className="btn btn-outline" onClick={() => (count.value = 0)}>重置</button>
<label className="flex items-center gap-2 ml-auto">
<input
type="checkbox"
className="checkbox"
checked={state.enabled}
onChange={(e: any) => (state.enabled = (e.target as HTMLInputElement).checked)}
/>
<span className="text-sm">启用</span>
</label>
</div>
<div className="mt-2 text-sm text-gray-700">
计数:{count.value},启用:{state.enabled ? '是' : '否'}
</div>
</div>
);
};
const Reactive: FC = () => (
<div className="grid gap-6">
<div>
<h3 className="text-lg font-semibold mb-2 text-base-content">前端的发展,从原始DOM,到 JQUERY等</h3>
<SearchableVideoList videos={videos} />
</div>
<div>
<h3 className="text-lg font-semibold mb-2 text-base-content">ref / reactive 示例</h3>
<ReactiveDemo />
</div>
</div>
)
export default Reactive;前端的发展,从原始DOM,到 JQUERY等
5 个视频
- 原始 DOM 编程直接操作节点与事件
- jQuery 的崛起Write Less, Do More
- Backbone.js 与 MVC早期前端架构探索
- Web Components原生组件标准
- 现代构建工具与生态模块化与开发体验
ref / reactive 示例
计数:0,启用:否
Rue 支持渐进集成:你可以在现有页面中按需挂载组件、路由或交互片段,而不必一次性重写整站。
Platinum Sponsor
白金赞助商
Gold Sponsor
黄金赞助商
Links