✅
简洁易用
轻量、直观的 API,适合渐进式接入;保留熟悉的 JSX / TSX 开发方式,无需额外模板语法。
🦀
编译驱动的原生 DOM 渲染
围绕真实 DOM 做最小更新,并可结合 Rust / Wasm 运行时扩展与原生 DOM 编译能力。
🤝
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 一起切换。
用组件组织界面
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 的组件、状态和默认渲染路径都建立在细粒度响应式系统之上,依赖变化后只接管受影响的更新边界。
结合 Rust 实现的响应式系统、Rust / Wasm 运行时与原生 DOM 编译能力,JSX 会被转换为更贴近真实 DOM 的产物。
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_VAPOR_TRANSFORMED */
import { jsxDEV as _jsxDEV } from "@rue-js/jsx-dev-runtime";
import { vapor, renderBetween, _$createElement, _$createComment, _$createTextNode, _$appendChild, _$setClassName } from "@rue-js/rue";
const Hello = ()=>vapor(()=>{
const _root = _$createElement("div");
_$setClassName(_root, "card bg-primary text-primary-content shadow-sm");
const _el1 = _$createElement("div");
_$appendChild(_root, _el1);
_$setClassName(_el1, "card-body items-center text-center");
const _el2 = _$createElement("div");
_$appendChild(_el1, _el2);
_$setClassName(_el2, "text-3xl font-extrabold");
_$appendChild(_el2, _$createTextNode("Hello"));
const _el3 = _$createElement("div");
_$appendChild(_el1, _el3);
_$setClassName(_el3, "mt-2 text-sm opacity-90");
_$appendChild(_el3, _$createTextNode("Hello component"));
return _root;
});
const World = ()=>vapor(()=>{
const _root = _$createElement("div");
_$setClassName(_root, "card bg-base-100 text-base-content border border-base-200 shadow-sm");
const _el4 = _$createElement("div");
_$appendChild(_root, _el4);
_$setClassName(_el4, "card-body items-center text-center");
const _el5 = _$createElement("div");
_$appendChild(_el4, _el5);
_$setClassName(_el5, "text-3xl font-extrabold");
_$appendChild(_el5, _$createTextNode("World"));
const _el6 = _$createElement("div");
_$appendChild(_el4, _el6);
_$setClassName(_el6, "mt-2 text-sm text-base-content/70");
_$appendChild(_el6, _$createTextNode("World component"));
return _root;
});
const HelloRue = ()=>vapor(()=>{
const _root = _$createElement("div");
_$setClassName(_root, "card bg-accent text-accent-content shadow-sm");
const _el7 = _$createElement("div");
_$appendChild(_root, _el7);
_$setClassName(_el7, "card-body items-center text-center");
const _el8 = _$createElement("div");
_$appendChild(_el7, _el8);
_$setClassName(_el8, "text-3xl font-extrabold");
_$appendChild(_el8, _$createTextNode("Hi"));
const _el9 = _$createElement("div");
_$appendChild(_el7, _el9);
_$setClassName(_el9, "mt-2 text-sm opacity-90");
_$appendChild(_el9, _$createTextNode("Rue"));
return _root;
});
const IAmRue = ()=>vapor(()=>{
const _root = _$createElement("div");
_$setClassName(_root, "card bg-base-100 text-base-content border border-base-200 shadow-sm");
const _el10 = _$createElement("div");
_$appendChild(_root, _el10);
_$setClassName(_el10, "card-body items-center text-center");
const _el11 = _$createElement("div");
_$appendChild(_el10, _el11);
_$setClassName(_el11, "text-3xl font-extrabold");
_$appendChild(_el11, _$createTextNode("Yes"));
const _el12 = _$createElement("div");
_$appendChild(_el10, _el12);
_$setClassName(_el12, "mt-2 text-sm text-base-content/70");
_$appendChild(_el12, _$createTextNode("My name is Rue"));
return _root;
});
const HelloWorld = ()=>vapor(()=>{
const _root = _$createElement("div");
_$setClassName(_root, "grid gap-6");
const _list1 = _$createComment("rue:component:start");
const _list2 = _$createComment("rue:component:end");
_$appendChild(_root, _list1);
_$appendChild(_root, _list2);
const __slot3 = /*#__PURE__*/ _jsxDEV(Hello, {}, void 0, false, {
fileName: "rue-plugin-input.tsx",
lineNumber: 39,
columnNumber: 5
}, this);
renderBetween(__slot3, _root, _list1, _list2);
const _list4 = _$createComment("rue:component:start");
const _list5 = _$createComment("rue:component:end");
_$appendChild(_root, _list4);
_$appendChild(_root, _list5);
const __slot6 = /*#__PURE__*/ _jsxDEV(World, {}, void 0, false, {
fileName: "rue-plugin-input.tsx",
lineNumber: 40,
columnNumber: 5
}, this);
renderBetween(__slot6, _root, _list4, _list5);
const _list7 = _$createComment("rue:component:start");
const _list8 = _$createComment("rue:component:end");
_$appendChild(_root, _list7);
_$appendChild(_root, _list8);
const __slot9 = /*#__PURE__*/ _jsxDEV(HelloRue, {}, void 0, false, {
fileName: "rue-plugin-input.tsx",
lineNumber: 41,
columnNumber: 5
}, this);
renderBetween(__slot9, _root, _list7, _list8);
const _list10 = _$createComment("rue:component:start");
const _list11 = _$createComment("rue:component:end");
_$appendChild(_root, _list10);
_$appendChild(_root, _list11);
const __slot12 = /*#__PURE__*/ _jsxDEV(IAmRue, {}, void 0, false, {
fileName: "rue-plugin-input.tsx",
lineNumber: 42,
columnNumber: 5
}, this);
renderBetween(__slot12, _root, _list10, _list11);
return _root;
});
export default HelloWorld;Vapor 渲染路径直接面向原生 DOM,减少额外协调开销;配合细粒度响应式更新,在复杂界面中能保持更稳的性能与内存表现。
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