一个轻量级高性能的
响应式 DOM 框架

Gzip 压缩后仅 11 KB,零依赖,无需构建工具,<script> 标签引入即可使用

11 KB
Gzip 压缩后
0
外部依赖
8+
内置指令
SPA
内置路由

介绍

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: 修改了数据但页面没更新?

  1. 数据是否用 ref() 或 reactive() 创建?
  2. 是否在 r-data 外用了 r-model 但没 provide()?
  3. 是否替换了 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>