从 <table> 到 ref():Web 开发的奇幻漂流——一场关于“谁该负责界面”的千年之辩
“从前,有一个程序员,他每天的工作就是拼字符串。”
——《前端考古学·卷一》
如果你穿越回 2005 年,走进一家互联网公司,你可能会看到这样的场景:后端工程师一边敲着 Java 或 PHP,一边在 .jsp 或 .php 文件里夹杂着 <% if (user != null) { %> 和 ``。他们不是在写业务逻辑,而是在用代码缝合 HTML 的补丁。
而今天,一个 Vue 工程师只需写下:
const users = ref([]);
然后在模板里:
<tr>
<td>{{ user.name }}</td>
</tr>
——用户列表就自动出现了。数据一变,界面跟着跳舞。仿佛魔法。
那么,这中间到底发生了什么?是谁把我们从“字符串拼接地狱”中拯救出来的?
让我们开启这场 Web 开发的奇幻漂流。
第一章:黑暗时代——后端即上帝(MVC 的黄金牢笼)
1.1 “全栈”其实是“全能苦力”
在 Web 1.0 时代,HTTP 是个极其朴素的协议:请求 → 响应 → 刷新。没有 AJAX,没有 WebSocket,甚至连 JSON 都还没普及(XML 才是贵族)。
于是,所有逻辑都压在服务器上。典型的 MVC 流程如下:
- 用户访问
/users - Controller 查询数据库(Model)
- 拿到数据后,用模板引擎(如 EJS、Thymeleaf)把数据“塞”进 HTML
- 整个页面作为字符串返回给浏览器
就像这样(Node.js 简化版):
res.end(`
<h1>欢迎, ${user.name}!</h1>
<p>你的邮箱是: ${user.email}</p>
`);
听起来简单?但当页面复杂起来——比如要加个分页、筛选、动态高亮——你就得在字符串里嵌套 if、for、escape、encode……稍有不慎,XSS 攻击就找上门来。
开发者内心 OS:
“我到底是写业务的,还是写 HTML 的?为什么我要关心<要转成<?”
1.2 模板引擎:缝合怪的诞生
为了解决“硬拼字符串”的痛苦,人们发明了模板引擎:EJS、Pug、Handlebars、Jinja2……
它们允许你写:
<% users.forEach(user => { %>
<tr>
<td><%= user.id %></td>
<td><%= user.name %></td>
</tr>
<% }) %>
看起来清爽多了?但问题没消失,只是被包装了:
- 前端想改样式?得等后端部署;
- 想做个无刷新搜索?对不起,整页刷新走起;
- 前端工程师?不存在的,都是“会切图的后端”。
MVC 成了“Model-View-Controller,但 View 归后端管”的缩写。
第二章:觉醒年代——AJAX 与前后端分离的革命
2.1 2005 年,Gmail 改变了世界
当 Gmail 首次实现“不刷新页面就能收邮件”时,全世界程序员都惊呆了。背后的技术叫 AJAX(Asynchronous JavaScript and XML) 。
虽然名字带 XML,但很快大家发现:JSON 更香。
于是,新的协作模式诞生了:
- 后端只提供 API:
GET /api/users → [{id:1, name:"舒俊"}] - 前端用 JavaScript 拉取数据,自己画界面
fetch('/api/users')
.then(r => r.json())
.then(users => {
// 手动操作 DOM
const ul = document.getElementById('user-list');
users.forEach(u => {
const li = document.createElement('li');
li.textContent = u.name;
ul.appendChild(li);
});
});
2.2 自由的代价:DOM 地狱
自由是有了,但新痛苦来了——DOM 编程太反人类!
想象你要实现一个“用户列表支持增删改查 + 实时搜索 + 排序”:
- 每次数据变化,你都要清空 tbody,重新 appendChild;
- 如果某个用户被删除,你得找到对应的
<tr>并 remove; - 搜索时,不能直接 filter 数据,还得同步更新 DOM;
- 一不小心,内存泄漏、重复渲染、闪烁问题全来了。
前端工程师的日常:
“我写了 200 行代码,其中 180 行在找节点、删节点、建节点……我的业务逻辑呢?”
这时候,大家开始怀念后端模板的“自动渲染”——但又不想回到整页刷新的石器时代。
我们需要一种既能保留 SPA 体验,又能像模板一样“声明式渲染”的东西。
第三章:魔法降临——响应式编程的崛起
3.1 什么是“响应式”?一个生活比喻
想象你有一块白板,上面写着:
“今日菜单:{{ dish }}”
旁边有个厨师,手里拿着一张纸条:“dish = 红烧肉”。
当你把纸条改成“dish = 宫保鸡丁”,白板上的字自动变成“今日菜单:宫保鸡丁” ——不需要你擦掉重写,也不需要你喊“更新菜单!”。
这就是响应式:数据是源头,视图是影子。源头一动,影子自动跟随。
3.2 Vue/React 如何实现魔法?
以 Vue 3 为例:
const users = ref([
{ id: 1, name: '舒俊' }
]);
ref() 不只是一个数组,它是一个被“施了魔法”的对象。Vue 在内部做了两件事:
- 依赖收集:当模板读取
users时,Vue 记下:“这个组件依赖 users”; - 触发更新:当
users.value.push(...)时,Vue 知道:“哦,依赖变了,重新渲染组件”。
整个过程对开发者透明。你只管改数据,框架负责更新界面。
3.3 对比:三种开发心智模型
| 模式 | 开发者思考方式 | 代码重心 |
|---|---|---|
| 后端模板 | “怎么把数据塞进 HTML 字符串?” | 字符串拼接、转义、循环嵌套 |
| 原生 DOM | “怎么找到那个元素并改它的 innerHTML?” | querySelector、createElement、appendChild |
| 响应式框架 | “我的数据应该是什么样子?” | 声明数据结构,描述 UI 与数据的关系 |
从“命令式”走向“声明式” ,是现代前端的核心哲学。
第四章:为什么响应式是未来的答案?
4.1 解耦:让专业的人做专业的事
- 后端:专注 API 设计、数据库优化、权限控制、高并发;
- 前端:专注用户体验、动效、状态管理、无障碍访问;
- 接口契约(如 OpenAPI)成为唯一沟通桥梁。
团队可以并行开发:前端用 Mock 数据先行,后端按规范出接口,最后无缝对接。
4.2 性能与体验的双赢
- 虚拟 DOM(React)或编译时优化(Vue)确保最小化真实 DOM 操作;
- 组件化让 UI 可复用、可测试;
- 状态管理(Pinia、Zustand)让复杂交互不再混乱。
4.3 未来已来:不止于浏览器
响应式思想已蔓延至全栈:
- 服务端渲染(SSR) :Nuxt、Next.js 在服务器生成 HTML,首屏快,SEO 友好;
- 跨端开发:Taro、UniApp 用同一套响应式逻辑编译到小程序、App;
- 低代码平台:拖拽组件 + 绑定数据源,本质仍是“数据驱动视图”。
尾声:我们终于可以只关心“业务”了
回到开头那段 Node.js 代码:
res.end(generateUserHTML(users));
它代表了一个时代——界面是后端的附属品。
而今天的 Vue 组件:
<tr>
<td>{{ user.name }}</td>
</tr>
它宣告了一个新时代——界面是数据的自然延伸。
我们不再问:“怎么把数据画出来?”
而是问:“数据应该是什么?”
这才是真正的开发者解放。
所以,下次当你写下
ref()时,请记得:
你不是在调用一个函数,
你是在施展一场持续了二十年的魔法。
🪄✨
附:致谢那些改变历史的英雄们
- 2005 年,Jesse James Garrett 提出 “AJAX” 一词;
- 2010 年,Angular 开启 MVVM 时代;
- 2014 年,React 引入虚拟 DOM;
- 2013 年,尤雨溪发布 Vue.js;
- 以及无数在深夜调试 DOM 的你我。
正是这些点滴,汇成了今天的河流。