介绍
RealDom 是一个轻量级高性能的响应式 DOM 框架,具有以下特点:
轻量级
压缩后仅 11KB,零依赖
高性能
基于 Proxy 的深度响应式
组件化
模板 / 样式 / 逻辑三者分离
指令系统
内置 8 个核心指令
路由管理
内置 SPA 路由 + 懒渲染
批量更新
requestAnimationFrame 合并
精准依赖
按变量名分组订阅
简单易用
无需构建工具即可使用
快速开始
引入方式
| 方式 | 代码 | 适用场景 |
|---|---|---|
| IIFE | <script src="https://cxfjh.cn/js/rd/0.1.0.js"></script> |
无构建工具 |
| Module | <script type="module" src="https://cxfjh.cn/js/rd/es.0.1.0.js"></script> |
模块化项目 |
| Module | import RealDom from "./es.0.1.0.js"; |
模块化项目 |
CDN 方式引入后,RealDom对象会自动挂载到window.RealDom,全局可用。
Hello World
HTML
<!DOCTYPE html>
<html>
<head>
<title>RealDom — Hello World</title>
<script src="https://cxfjh.cn/js/rd/0.1.0.js"></script>
</head>
<body>
<div>
<h1>{{ message }}</h1>
<p>计数器: {{ counter }}</p>
<button r-click="counter.value++">增加</button>
<button r-click="counter.value--">减少</button>
<label>
<input type="text" r-model="input">
<span>姓名: {{ input }}</span>
</label>
</div>
<script>
const { ref, provide, onMounted } = RealDom;
// 创建响应式数据
const message = ref("Hello RealDom!");
const counter = ref(10);
// 向根作用域注入数据,使 r-model 等指令可用
const input = ref("Hello");
provide({ input });
// DOM 渲染完成后执行
onMounted(() => console.log("应用已启动"));
</script>
</body>
</html>
Todo 应用
HTML
<div r-data="{title: '我的待办事项', input: '', list: [{ text: '学习 RealDom', done: false }]}">
<h2>{{ title }}</h2>
<div>
<input type="text" r-model="input" placeholder="输入待办事项...">
<button r-click="list.push({text: input, done: false})">添加</button>
</div>
<div r-for="list">
<span r-click="list[index].done = !list[index].done">
{{ value.done ? '✓' : '○' }} {{ value.text }}
</span>
<span r-click="list.splice(index, 1)">×</span>
</div>
<p>总数: {{ list.length }} | 已完成: {{ list.filter(value => value.done).length }}</p>
</div>
核心概念
响应式系统
数据变更 → Proxy 拦截 → 依赖收集 → 精准通知 → 批量异步更新 → DOM 渲染
ref — 基本类型响应式
JavaScript
const { ref } = RealDom;
// 创建响应式引用
const count = ref(0);
const name = ref("张三");
// JavaScript 代码中通过 .value 读写
console.log(count.value); // 0
count.value++; // 修改值,触发视图更新
name.value = "李四"; // 修改值,触发视图更新
reactive — 对象类型响应式
JavaScript
const { reactive } = RealDom;
// 对象响应式(深层嵌套自动代理)
const user = reactive({
name: "李四",
age: 20,
address: { city: "北京" }
});
// 直接修改属性,无需 .value
user.age = 21;
user.address.city = "上海"; // 深层属性也自动响应
// 数组响应式
const list = reactive([1, 2, 3]);
list.push(4); // 变异方法自动触发更新
list.splice(0, 1); // splice 也自动响应
list[0] = 100; // 索引赋值也自动响应
ref 与 reactive 对比
| 特性 | ref() | reactive() |
|---|---|---|
| 支持类型 | 基本类型 + 对象 | 仅对象(含数组) |
| 访问方式 | .value 读写 | 直接属性访问 |
| 适用场景 | 单个值、计数器 | 表单数据、列表 |
API 参考
ref(init) / reactive(target)
| API | 参数 | 说明 |
|---|---|---|
ref(init) |
初始值 | 创建响应式引用,通过 .value 读写 |
reactive(target) |
对象或数组 | 创建深度响应式代理 |
provide(data)
向根作用域注入响应式数据,使数据可在 r-model 中使用,其他场景不推荐使用。
JavaScript
const { provide, ref } = RealDom;
const count = ref(0);
provide({ count }); // 注入后模板中可直接使用
watch(source, callback, options?)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
source |
() => any | 是 | 返回监听值的 getter |
callback |
(newVal, oldVal) => void | 是 | 变化回调 |
options |
{ immediate?, once? } | 否 | immediate 立即执行,once 只执行一次 |
JavaScript
const { watch, ref, reactive } = RealDom;
// 监听 ref
const count = ref(0);
watch(() => count.value, (newVal, oldVal) => {
console.log(`计数从 ${oldVal} 变为 ${newVal}`);
});
// 监听 reactive 对象属性
const user = reactive({ name: "张三", age: 20 });
watch(() => user.age, (newAge, oldAge) => {
console.log(`年龄从 ${oldAge} 变为 ${newAge}`);
}, { immediate: true }); // 立即执行一次
// 监听多个值
watch(
() => [user.name, user.age],
([newName, newAge], [oldName, oldAge]) => {
console.log(`${oldName}→${newName}, ${oldAge}→${newAge}`);
}
);
// 只执行一次
const unwatch = watch(() => count.value, () => {
console.log("count 变化了(仅触发一次)");
}, { once: true });
// 手动停止监听
// unwatch();
dom(componentName, options)
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
template |
string | 是 | HTML 模板 |
style |
string | 否 | CSS 样式,支持 Scoped |
script |
function | 否 | 组件逻辑工厂函数 |
props |
object | 否 | 属性默认值 |
to |
string | 否 | 自动挂载目标 |
JavaScript
const { dom } = RealDom;
// 定义组件
const UserCard = dom("user-card", {
template: `
<div class="card">
<h2>{{ $props.title }}</h2>
<p>姓名: {{ name }}</p>
<p ref="age">年龄: {{ age }}</p>
<button r-click="grow()">长大一岁</button>
</div>
`,
style: `
.card {
border: 1px solid #ddd;
padding: 16px;
border-radius: 8px;
}
.card button {
background: #4CAF50;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
}
`,
script: ({ $props, $refs }, { ref }) => {
// setup 外定义的变量需要在 setup 中返回
const name = ref("张三");
const setup = (ctx) => {
// ctx 上定义的变量无需返回,模板中直接可用
ctx.age = ref(20);
const grow = () => {
ctx.age.value++;
console.log($props.title, $refs.age.innerHTML)
};
// 手动返回的变量优先级高于 ctx 上的变量
return { name, grow };
};
function mounted() {
console.log("组件已挂载到 DOM");
console.log(name.value);
console.log(this.age.value); // 通过 this 访问 ctx 上的变量
}
function unmounted() {
console.log("组件即将销毁");
}
return { setup, mounted, unmounted };
},
props: {
title: "默认标题",
},
});
// 手动挂载组件
const instance = UserCard({
props: { title: "用户信息" },
to: "#app",
});
onMounted(callback) / nextTick(callback) / mount() / del()
| API | 说明 |
|---|---|
onMounted(cb) |
DOM 初始化完成后执行 |
nextTick(cb) |
下一帧 DOM 更新后执行 |
mount(name, {props, to}) |
手动挂载组件 |
inst.del(removeStyle?) |
销毁组件实例 |
inst.delSty() |
仅删除组件样式 |
HTML
<script>
const { onMounted } = RealDom;
onMounted(() => {
console.log("DOM 已准备就绪");
// 可以安全地操作 DOM 元素
});
</script>
指令系统
指令概览
| 指令 | 属性 | 用途 |
|---|---|---|
r-if |
条件 | 控制元素显示/隐藏 |
r-for |
循环 | 列表/数字循环渲染 |
r-model |
双向绑定 | 表单数据双向绑定(5种控件) |
r-click |
事件 | 事件绑定(18种+键盘过滤) |
r-data |
数据 | 局部数据作用域 |
r-api |
异步 | API 数据加载 |
r-dom |
组件 | 组件挂载 + Props 传递 |
r-route |
路由 | 路由导航 + 激活样式 |
r-if — 条件渲染
HTML
<div r-if="isVisible">这会根据条件显示或隐藏</div>
<div r-if="count.value > 10">当 count 大于 10 时显示</div>
<div r-if="user.loggedIn && user.role === 'admin'">管理员面板</div>
<script>
const { ref, reactive } = RealDom;
const isVisible = ref(true);
const count = ref(5);
const user = reactive({ loggedIn: true, role: "admin" });
</script>
r-for — 循环渲染
HTML
<!-- 基础数组 -->
<ul>
<li r-for="items">{{ value }}</li>
</ul>
<!-- 对象数组 + 自定义变量名 -->
<div r-for="users" value="user" index="i">
<p>{{ i + 1 }}. {{ user.name }} — {{ user.age }}岁</p>
</div>
<!-- 配合 key 提升性能 -->
<div r-for="products" key="id">
<h3>{{ value.title }}</h3>
</div>
<!-- 循环 5 次,索引从 1 开始 -->
<div r-for="5" start="1">第 {{ index }} 次</div>
<!-- 响应式变量控制次数 -->
<div r-for="loopCount">{{ index }}</div>
<!-- 嵌套循环(九九乘法表) -->
<div r-for="outer" index="i" start="1">
<div r-for="i" index="j" start="1">
{{ j }} × {{ i }} = {{ i * j }}
</div>
</div>
<script>
const { ref, reactive } = RealDom;
const items = ref(["苹果", "香蕉", "橙子"]);
const loopCount = ref(3);
const outer = ref(3);
const users = reactive([
{ name: "张三", age: 25 },
{ name: "李四", age: 30 },
]);
const products = reactive([
{ id: 1, title: "笔记本电脑", price: 5999 },
{ id: 2, title: "手机", price: 3999 },
]);
</script>
r-model — 双向绑定
HTML
<!-- 文本输入 -->
<input type="text" r-model="inputValue" placeholder="请输入内容">
<p>当前输入: {{ inputValue }}</p>
<!-- 数字输入 -->
<input type="number" r-model="numberValue">
<p>数字: {{ numberValue }}</p>
<!-- 复选框 -->
<input type="checkbox" r-model="isChecked"> 我同意条款
<p>状态: {{ isChecked }}</p>
<!-- 单选按钮 -->
<input type="radio" r-model="gender" value="male" id="male">
<label for="male">男</label>
<input type="radio" r-model="gender" value="female" id="female">
<label for="female">女</label>
<p>选择: {{ gender }}</p>
<!-- 下拉选择 -->
<select r-model="selectedOption">
<option value="">请选择</option>
<option value="a">选项A</option>
<option value="b">选项B</option>
</select>
<p>选择: {{ selectedOption }}</p>
<script>
const { provide, ref } = RealDom;
const inputValue = ref("");
const numberValue = ref(0);
const isChecked = ref(false);
const gender = ref("");
const selectedOption = ref("");
// 在 <script> 中定义的变量需要 provide 才能在 r-model 中使用
provide({ inputValue, numberValue, isChecked, gender, selectedOption });
</script>
r-click — 事件绑定
HTML
<!-- 基础点击 -->
<button r-click="handleClick()">点击我</button>
<!-- 双击 -->
<button r-click="counter.value++" dblclick>双击增加 {{ counter.value }}</button>
<!-- 键盘事件 + 按键过滤 -->
<input r-click="submit()" keydown="Enter" placeholder="按回车提交">
<!-- 键盘事件使用别名 -->
<input r-click="close()" keydown="esc" placeholder="按 ESC 关闭">
<script>
const { ref } = RealDom;
const counter = ref(0);
const handleClick = () => console.log("按钮被点击了");
const submit = () => console.log("提交表单");
const close = () => console.log("关闭弹窗");
</script>
r-data — 数据作用域
HTML
<!-- 基础用法 -->
<div r-data="{ name: 'RealDom', version: '0.1.0', features: ['轻量', '高性能'] }">
<h1>{{ name }} v{{ version }}</h1>
<ul>
<li r-for="features">{{ value }}</li>
</ul>
</div>
<!-- 嵌套使用 + r-model + r-click -->
<div r-data="{user: {name: '张三', age: 25}, count: 0}">
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<p><input r-model="count"> 当前计数: {{ count }}</p>
<button r-click="user.age++">增长一岁</button>
<button r-click="_.count++">增加计数</button>
</div>
r-api — 异步数据加载
HTML
<!-- 自动请求(GET) -->
<div r-api="https://lv.cxfjh.cn/loves/api/public/wish" list="data">
<p>{{ value.id }}. {{ value.title }}</p>
</div>
<!-- 手动触发 + 自定义配置 -->
<div r-api="https://lv.cxfjh.cn/loves/api/public/wish" list="data" method="GET" manual refresh="#btn">
<p>{{ value.title }}</p>
</div>
<button id="btn">点击加载数据</button>
<!-- POST 请求 + 自定义请求头 -->
<div r-api="url" list="data" method="POST" headers="requestHeaders" data-body="requestBody">
<p>{{ value.name }}</p>
</div>
<!-- 手动渲染 + 自定义变量名 -->
<div r-api="url" list="data" method="GET" manual refresh="#loadBtn" arr="users">
<span>请求状态: {{ _manual ? '成功' : '加载中...' }}</span>
<div r-for="users">
<p>{{ value.id }}. {{ value.title }}</p>
</div>
</div>
<button id="loadBtn">点击加载</button>
<script>
const { ref } = RealDom;
const url = ref("https://lv.cxfjh.cn/loves/api/public/wish");
const requestHeaders = {
"Authorization": "Bearer token123",
"Content-Type": "application/json",
};
const requestBody = ref({ content: "Hello RealDom" });
</script>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
r-api="url" |
string | — | API 请求地址 |
list="key" |
string | — | 返回数据中数组的 key |
method="GET" |
string | GET | HTTP 方法 |
manual |
boolean | false | 是否手动触发 |
r-dom — 组件挂载
HTML
<!-- 基础挂载 -->
<div r-dom="user-card" id="user1"></div>
<!-- 传递 Props -->
<div r-dom="user-card" $title="'用户信息'" $email="'test@qq.com'"></div>
<!-- 动态 Props -->
<div r-dom="user-card" $title="userTitle" $count="10"></div>
<script>
const { ref, onMounted } = RealDom;
const userTitle = ref("动态标题");
// 可通过 cpInsts.get 获取 r-dom 注册组件的实例
let user1;
onMounted(() => {
user1 = RealDom.cpInsts.get(document.querySelector("#user1"))
})
</script>
r-route — 路由导航
HTML
<!-- 路由内容 -->
<div r-page="home">
<h3>【首页】 我是 view 容器的内容</h3>
</div>
<div r-page="settings" &route="view">
<h3>【设置】 我是 view 容器的内容</h3>
</div>
<!-- 路由内容 -->
<div r-page="about" &route="info">
<h3>【关于】 我是 info 容器的内容</h3>
</div>
<div r-page="mine" &route="info">
<h3>【我的】 我是 info 容器的内容</h3>
</div>
<!-- 路由容器 -->
<div>
<h1>view 容器</h1>
<div route="view"></div>
</div>
<div>
<h1>info 容器</h1>
<div route="info"></div>
</div>
<!-- 路由导航 -->
<button r-route="home" route-active="r-x">view首页</button>
<button r-route="about" route-active>info关于</button>
<button r-route="settings" route-active>view设置</button>
<button r-click="router.nav('mine')">info我的</button>
<script>
const { router } = RealDom;
// 路由激活时触发
router.add("mine", () => {
console.log("路由激活");
}, "info");
router.add("about", () => {
console.log("路由激活");
}, "info");
</script>
组件系统
生命周期
| 钩子 | 调用时机 | 能做什么 |
|---|---|---|
setup(ctx) |
DOM 挂载前 | 定义响应式变量、方法 |
mounted() |
DOM 挂载后 | 操作 DOM、$refs |
unmounted() |
组件销毁前 | 清理定时器、释放资源 |
$refs — DOM 引用
JavaScript
dom("demo", {
template: `
<div>
<h1 ref="title">标题</h1>
<p ref="content">内容</p>
<button r-click="changeStyle()">修改样式</button>
</div>
`,
script: ({ $refs }) => {
const setup = (ctx) => {
ctx.changeStyle = () => $refs.title.style.color = "blue";
};
const mounted = () => {
// $refs 在 mounted 中可用
console.log($refs.title); // <h1> 元素
console.log($refs.content); // <p> 元素
$refs.title.style.color = "red";
}
return { setup, mounted };
},
});
Scoped CSS
默认启用 CSS 作用域隔离。样式穿透语法:>>>、/deep/、::v-deep
路由系统
RealDom 内置基于 URL ?path= 查询参数的 SPA 路由系统。
声明页面 & 多容器路由
HTML
<!-- 声明页面 -->
<div r-page="home" &route="view"><h3>首页</h3></div>
<div r-page="about" &route="info"><h3>关于</h3></div>
<!-- 容器 -->
<div route="view"></div>
<div route="info"></div>
router API
| 方法 | 说明 |
|---|---|
router.add(path, handler, target?) |
手动注册路由 |
router.nav(path, replace?) |
导航到指定路径 |
router.init() |
初始化路由系统 |
进阶用法
表达式解析 & 动态样式
HTML
<p>{{ isLoggedIn.value ? '欢迎回来' : '请登录' }}</p>
<p>未完成: {{ tasks.filter(t => !t.completed).length }}</p>
<div class="{{ isActive.value ? 'active' : '' }}">动态类名</div>
<div style="color: {{ color }}; font-size: {{ size }}px;">动态样式</div>
高级 API 插件开发
面向高级用户和插件/指令开发者。这些 API 是框架内部的核心构建块。
regDir(name, handler) — 注册自定义指令
扩展 RealDom 能力的核心入口。
| 参数 | 类型 | 说明 |
|---|---|---|
name |
string | 指令名称,推荐 r-xxx 格式 |
handler |
(el, expr, scope, deps) => void | 指令处理函数 |
JavaScript
const { regDir, parser, onElRemove, initDir } = RealDom;
regDir("r-tooltip", (el, expr, scope, deps) => {
if (!initDir(el, expr, scope, "r-tooltip", "rTooltip")) return;
const text = parser.parse(expr, scope, deps);
el.setAttribute("title", String(text));
onElRemove(el, () => el.removeAttribute("title"));
});
parser — 表达式解析器
| 方法 | 说明 |
|---|---|
parser.parse(expr, scope?, deps?, unwrapRef?) |
解析 JS 表达式求值,自动收集依赖 |
parser.text(text, scope?, deps?) |
替换文本中所有 {{ }} 插值 |
batch — 批量更新管理器
batch.add(fn) 将更新函数加入 requestAnimationFrame 队列,同一帧自动合并。
数据变更1 → batch.add(fn1) ┐
数据变更2 → batch.add(fn2) ├── 同一帧收集
数据变更3 → batch.add(fn3) ┘
↓ requestAnimationFrame
batch._execute() → 遍历快照 → 一次性 DOM 更新
compile / bind / bindText — DOM 编译
| API | 参数 | 说明 |
|---|---|---|
compile(el, scope?) |
根元素, 作用域 | 递归编译 DOM 树 |
bind(el, scope) |
元素, 作用域 | 为元素创建更新函数(返回 update fn) |
bindText(node, scope) |
文本节点, 作用域 | 为文本节点建立响应式关联 |
JavaScript
const { compile, reactive } = RealDom;
const scope = reactive({ name: "RealDom" });
const el = document.getElementById("dynamic");
el.innerHTML = `<p>{{ name }}</p>`;
compile(el, scope); // 编译动态插入的 HTML
Dep — 依赖管理类
| 方法 | 说明 |
|---|---|
dep.subscribe(fn, variable?) |
订阅;指定 variable 时精准订阅 |
dep.notify(variable?) |
通知;指定时优先精准通知 |
dep.unsubscribe(fn, variable?) |
移除订阅者 |
JavaScript
const { Dep } = RealDom;
const dep = new Dep();
dep.subscribe(() => console.log("全量通知"));
dep.subscribe(() => console.log("精准通知"), "name");
dep.notify("name"); // 精准 → 全量 降级策略
dep.subs // Set<Function> 全量订阅者
dep.varSubs // Map<string, Set<Fn>> 精准订阅者
onElRemove(el, cleanup) — 元素移除监听
注册元素从 DOM 移除时的清理回调,实现自动资源管理。基于全局 MutationObserver。
JavaScript
const { onElRemove } = RealDom;
const id = setInterval(() => console.log("轮询"), 1000);
onElRemove(el, () => clearInterval(id)); // 元素移除时自动清理
initDir(el, expr, scope, dirName, flag) — 指令初始化工具
自定义指令开发的统一初始化入口,提供校验和防重复处理。
| 参数 | 类型 | 说明 |
|---|---|---|
el |
HTMLElement | 指令绑定的 DOM 元素 |
expr |
string | 指令表达式字符串 |
scope |
ReactiveInterface|null|undefined | 当前作用域 |
dirName |
string | 指令名称(用于 warn 日志) |
flag |
string | 唯一标记后缀(驼峰形式) |
返回值:true 通过校验继续执行,false 终止处理。
校验规则:① 表达式为空→warn+false ② 作用域无效→warn+false ③ 已有标记→false(防重复)④ 通过→打标记+true
自定义指令模板
JavaScript
const { regDir, parser, initDir, onElRemove } = RealDom;
regDir("r-my-directive", (el, expr, scope, deps) => {
if (!initDir(el, expr, scope, "r-my-directive", "rMyDirective")) return;
const value = parser.parse(expr, scope, deps);
// ... 执行业务逻辑 ...
onElRemove(el, () => { /* 清理资源 */ });
});
常见问题
Q1: 为什么 JS 中需要 .value,模板中不需要?
ref() 创建包装对象,模板引擎自动解包。JS 中必须显式 .value。
Q2: ref() 和 reactive() 怎么选?
| 场景 | 推荐 |
|---|---|
| 基本类型(string, number, boolean) | ref() |
| 对象/数组 | reactive() |
| 需要替换整个值 | ref() |
| 需要解构 | reactive() |
Q3: r-model 什么时候需要 provide()?
需要:<script> 标签中定义的变量。不需要:r-data 或 dom() 组件内。
Q4: 修改了数据但页面没更新?
- 数据是否用 ref() 或 reactive() 创建?
- 是否在 r-data 外用了 r-model 但没 provide()?
- 是否替换了 reactive 对象的整个引用?
Q5: r-click 中为什么基本类型需要 _. 前缀?
在 r-data 作用域内,基本类型变量通过 _.变量名 访问,这是为了区分"读取变量值"和"修改变量本身"。对象和数组类型则可以直接修改属性。
JavaScript
< r-data="{count: 0, user: {age: 20}}">
<button> r-click="_.count++">修改基本类型</button>
<button> r-click="user.age++">修改对象属性</button>
</div>