从模板渲染到响应式驱动:前端崛起的技术演进之路

引言:界面是如何"动"起来的?

不论是用户看到的哪个页面,都不应该是一成不变的静态 HTML。

待办事项的增删、商品库存的实时变化,还是聊天消息的即时推送,都让页面指向了一个必需功能------------界面必须要随着数据的变化而自动更新

而围绕着这一核心诉求,出现了两条主流路径:

  • 后端动态生成HTML(传统的 MVC 模式): 数据在服务端组装成完整页面,再一次性返还给浏览器。
  • 前端接管界面更新(现代响应式范式): 后端只提供原始数据(如JSON API),而前端通过响应式系统来驱动视图自动同步。

而在这两条路背后,反映着前后端职责划分,同时也催生了以VueReact为代表的前端技术框架革命。

时代一:纯后端渲染 ------ MVC 模式主导

假如有一个需求如:写一个简单的 HTTP 服务器,当用户访问 //users 路径时,返回一个包含用户列表的 HTML 页面,其他路径则返回 404 错误。

Node.js早期,如果我想实现这个需求,那么后端渲染将是不二之选。

代码示例:早期 Node.js 实现简单用户列表页

首先就是引入 Node.js 内置模块httpurl,而使用的方法则是Node.js最早的CommonJS 模块系统 中的 require()来"导入"

js 复制代码
const http = require("http"); // commonjs 
const url = require("url");   // url
  • http 模块:用于创建 HTTP 服务器(处理请求和响应)。
  • url 模块:用于解析浏览器发来的 URL 字符串(如 /users?id=1)。

然后再准备一些模拟数据

js 复制代码
const users = [
    { id: 1, name: '张三', email: '123@qq.com' },
    { id: 2, name: '李四', email: '123456@qq.com' },
    { id: 3, name: '王五', email: '121@qq.com' }
]

接下来就要创建生成 HTML 页面的函数了

先使用.map()方法来动态生成表格行,对每个用户生成一行 HTML 表格,用反引号来插入变量,使用 .join('')来拼接所有行,最后返还一个完整的 HTML 文档。

js 复制代码
function generateUsersHtml(users) {
    const userRows = users.map(user => `
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.email}</td>
        </tr>
    `).join('');
    
    return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>User List</title>
        <style>
            table { width: 100%; border-collapse: collapse; margin-top: 20px; }
            th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
            th { background-color: #f4f4f4; }
        </style>
    </head>
    <body>
        <h1>Users</h1>
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
            </thead>
            <tbody>
                ${userRows}
            </tbody>
        </table>
    </body>
    </html>
    `;
}

最后也是最重要的就是创建 HTTP 服务器了。

js 复制代码
const server = http.createServer((req, res) => {
    const parsedUrl = url.parse(req.url, true);
    
    if (parsedUrl.pathname === '/' || parsedUrl.pathname === '/users') {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/html;charset=utf-8');
        const html = generateUsersHtml(users);
        res.end(html);
    } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', "text/plain");
        res.end('Not Found');
    }
});

关键概念解释:

  • req(Request):用户的请求对象,包含 URL、方法、头信息等。
  • res(Response):你要返回给用户的内容,通过它设置状态码、头、正文。

解析 URL

js 复制代码
// url.parse(pathname, query)
const parsedUrl = url.parse(req.url, true);
  • req.url即用户请求的 路径+参数部分

  • url.parse()将 URL 字符串"拆解"成结构化的对象,从而方便读取,其中:

    • pathname: 路径部分 (如 /users
    • query: 查询参数 (如 ?id=1就变成了{ id: '1' }),这里的true是用于判断你是否需要自动解析URL参数部分并转换为对象(通常为 true)

路由判断(简单路由)

js 复制代码
if (parsedUrl.pathname === '/' || parsedUrl.pathname === '/users')

如果路径是根路径 //users,就显示用户列表,否则返回 404。

成功响应(200)

js 复制代码
res.statusCode = 200; // 设置状态码为 200
res.setHeader('Content-Type', 'text/html;charset=utf-8');
const html = generateUsersHtml(users);
res.end(html);

通过.setHeader告诉浏览器:"我返回的是 HTML,用 UTF-8 编码"。

然后利用函数 generateUsersHtml(users),传入用户数据,最后调用res.end()生成 HTML 并发送。

错误响应(404 Not Found)

js 复制代码
res.statusCode = 404;
res.setHeader('Content-Type', "text/plain");
res.end('Not Found');

状态码 404 表示"页面不存在",如果产生错误则返还Not Found

注意:url.parse() 是旧 API,现代开发基本弃用


Node.js HTTP服务器启动的最后一步

js 复制代码
server.listen(1234, () => {
    console.log('Server is running on port 1234')
})

让服务器监听 1234 端口 (任意修改)。此时就可以在浏览器访问:http://localhost:1234http://localhost:1234/users,而访问其他路径(如 /about)会显示 "Not Found"。

效果图:

核心缺点 + 时代局限性:

  1. 前后端高度耦合,协作效率低下

HTML 结构、样式、JavaScript 逻辑全部硬编码在一个函数里,如果要修改表格样式等操作,就得修改这个函数,并且无法复用。

而这也几乎将前后端工程师捆绑起来了:

  • 前端工程师无法独立开发或调试 UI,必须依赖后端接口和模板
  • 后端工程师被迫处理本应属于前端范畴的展示逻辑

阻碍了团队协作,让前后端工程师的开发体验都极差。

  1. 用户体验受限,交互能力弱

页面完全由服务端生成,每次跳转或操作都需整页刷新,无法实现局部更新、动态加载、表单实时校验等现代 Web 交互,即使只是点击一个按钮,也要重新请求整个 HTML 文档。

时代二:转折点 AJAX 与前后端分离的诞生

在 2005 年之前,Web 应用基本是:用户点击 → 浏览器发请求 → 后端生成完整 HTML → 返回 → 整页刷新 ,导致用户每次交互都像"重新打开一个页面",体验感大打折扣。

转折事件:Google Maps(2005)首次大规模使用 XMLHttpRequest(XHR)

  • 地图拖拽时不刷新页面
  • 动态加载新区域数据
  • 用户体验飞跃 → 行业震动

这就是 AJAX(Asynchronous JavaScript and XML) 范式的诞生------ "让网页像桌面应用一样流畅"

范式对比再深化

维度 后端渲染(传统) 前后端分离(AJAX 时代)
职责划分 后端一家独大 前端负责 UI/交互,后端负责数据/API
开发模式 全栈一人干 前后端并行开发
部署方式 服务端部署 HTML 前端静态资源(CDN),后端 API(独立服务)
用户体验 卡顿、白屏、跳转 流畅、局部更新、SPA雏形
技术栈 PHP/Java/Node + 模板引擎 HTML/CSS/JS + REST API

代码示例:

已经配置好的环境

在后端 backend 文件夹中包含一个存储用户数据的db.json文件:

json 复制代码
{
    "users": [
        {
            "id": 1,
            "name": "张三",
            "email": "123@qq.com"
        },
        {
            "id": 2,
            "name": "李四",
            "email": "1232@qq.com"
        },
        {
            "id": 3,
            "name": "王五",
            "email": "121@qq.com"
        }
    ]
}

注:json-server 会把 JSON 的顶层 key(如 "users")自动映射为 RESTful 路由

package.json 中的脚本

json 复制代码
{
  "scripts": {
    "dev": "json-server --watch db.json"
  }
}

就使得运行 npm run dev 时,json-server 会监听 backend/db.json 文件变化(--watch),并且启动一个 HTTP 服务器,默认端口 3000。(别忘了启动后端服务哦~~)

前端代码(重头戏):

基础页面

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User List</title>
    <style>
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
        th { background-color: #f4f4f4; }
    </style>
</head>
<body>
    <h1>Users</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
        </tbody>
    </table>
</body>
</html>

<script>中的内部逻辑

html 复制代码
<script>
    // DOM 编程
    fetch('http://localhost:3000/users') // 发出请求
        .then(res => res.json()) // 将 JSON 字符串转为 JS 对象数组
        .then(data => {
            const tbody = document.querySelector('tbody');
            tbody.innerHTML = data.map(user => `
                <tr>
                    <td>${user.id}</td>
                    <td>${user.name}</td>
                    <td>${user.email}</td>
                </tr>
            `).join('');
        })
</script>

.then(data => ...)中的 data 就是上一步 res.json() 解析的结果,并且通过 .map()生成字符串数组,再用.join('') 拼接字符串,而通过 tbody.innerHTML 让浏览器重新解析并渲染表格。

这代表了"纯手工"前后端分离的起点

  • 前端不再依赖后端吐 HTML
  • 数据通过 JSON API 获取
  • 视图由 JavaScript 动态生成

但这种方法并非完美,仍然存在痛点:手动操作 DOM 繁琐且易错

举个"胶水代码灾难"的例子:

js 复制代码
// 用户点击"删除"
button.onclick = () => {
  fetch(`/api/users/${id}`, { method: 'DELETE' })
    .then(() => {
      // 从列表中移除元素
      li.remove();
      // 更新
      countSpan.textContent = --totalCount;
      // 如果列表空了,显示"暂无数据"
      if (totalCount === 0) emptyMsg.style.display = 'block';
      // 可能还要发埋点、更新缓存、通知其他组件...
    });
};

视图更新逻辑散落在各处,难以维护,极易出错,删除数据要:

  • 找到 <tr> 并删除
  • 更新
  • 找到空状态提示并显示
  • 可能还要:更新侧边栏统计、刷新分页、清除搜索高亮......

每次 UI 变化都要手动找一堆 DOM 节点去修改,并且难以复用。

这时期的前端程序员内心都憋着一句话:我不想再写 document.getElementById 了!

AJAX 让网页活了过来,但也让前端开发者陷入了新的地狱(DOM)------直到框架降临

时代三:革命!响应式数据驱动界面的崛起

核心思想:

"你只管改数据,界面自动更新。"

关键技术:ref 与响应式系统

  • ref() 将普通值包装成响应式对象
  • 模板中通过 {{ }}v-for 声明式绑定数据
  • 数据变化会自动触发视图更新(无需手动 DOM 操作)

响应式(以 Vue 为例)

html 复制代码
<template>
  <table>
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
      <!-- 遍历数据渲染到界面 -->
      <tr v-for="user in users" :key="user.id">
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.email }}</td>
      </tr>
    </tbody>
  </table>
</template>
  • v-for :声明遍历 users 数组
  • {{ }} :显示 user 的某个属性
  • :key:帮助 Vue 高效追踪列表变化(性能优化)

但是关键在于:我没有写任何 DOM 操作代码!

只是在"描述 UI 应该是什么样子",而不是"怎么去修改 DOM"。

html 复制代码
<script setup>
import { 
  ref,
  onMounted // 挂载之后的生命周期
} from 'vue'; // 响应式api(将数据包装成响应式对象)

// 用 ref() 将普通数组包装成一个 响应式引用对象
const users = ref([]);

// onMounted:确保 DOM 已创建后再发起请求(避免操作不存在的元素)
onMounted(() => {
  console.log('页面挂载完成');
  fetch('http://localhost:3000/users')
    .then(res => res.json())
    .then(data => {
      users.value = data; // 只修改数据
    })
})

// 定时器添加数据
setTimeout(() => {
  users.value.push({
    id: '4',
    name: '钱六',
    email: '12313@qq.com'
  })
}, 3000)
</script>

没有 innerHTML没有 createElement没有 getElementById

并且所有 UI 更新都是数据变化的自然结果,无需人工干预!

整个历史进程:

阶段 开发模式 核心关注点 开发体验
后端渲染 MVC 数据 → 模板 → HTML 前端边缘化
前后端分离 AJAX + DOM 手动同步数据与视图 繁琐、易错
响应式框架 数据驱动 聚焦业务逻辑 高效、声明式、愉悦

这段短短的 Vue 代码,浓缩了前端开发十年的演进:

  • 从"操作 DOM"到"描述 UI"
  • 从"分散状态"到"单一数据源"
  • 从"易错胶水"到"自动同步"

它让前端开发者终于认识到一个新的自己:前端不再只是"切图仔",而是复杂应用的架构者与体验设计师。 这,就是 响应式数据驱动界面 的革命性所在。

相关推荐
一 乐5 小时前
家政管理|基于SprinBoot+vue的家政服务管理平台(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot
源码获取_wx:Fegn08956 小时前
基于springboot + vue停车场管理系统
java·vue.js·spring boot·后端·spring·课程设计
cc蒲公英6 小时前
vue 对象、数组增删改,对比vue2和vue3 —— 最新总结2025
前端·javascript·vue.js
_一两风6 小时前
揭秘 ChatGPT 同款“打字机”特效:前端流式输出 (Streaming) 原理全解
前端·vue.js·openai
三翼鸟数字化技术团队7 小时前
vue3组件二次封装-另外一种思路
vue.js
老华带你飞7 小时前
宠物商城销售|基于Java+ vue宠物商城销售管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·宠物
计算机学姐7 小时前
基于Python的在线考试系统【2026最新】
开发语言·vue.js·后端·python·mysql·django·flask
live丶9 小时前
从零实现一个低代码 H5 页面编辑器(Vue3 + 拖拽)
前端·vue.js
码界奇点9 小时前
基于Django REST framework与Vue的前后端分离后台管理系统设计与实现
vue.js·后端·python·django·毕业设计·源代码管理