主题
界面
主题
《Ajax 与前后端交互》用 fetch 拿到了数据,然后要手工 document.createElement 更新 DOM——繁琐且容易漏。Vue 把这一步变成"数据变了,视图自动变" ✓
命令式(原生 JS):手动找到元素 → 修改内容 → 处理事件 → 同步状态
声明式(Vue): "视图 = f(数据)" → 你只改数据,框架管渲染 ★ref / reactive 管理响应式状态(Vue 3)★★;v-if / v-for / v-model 三大指令 ★;fetch 吗?(《Ajax 与前后端交互》)✓后端学 Vue 的深度标准:
能看懂前端组件在"调你的哪些接口"、能写简单的展示页、能一起讨论"接口该怎么设计才好用" ✓ ——不需要成为前端专家 ✓
命令式(《HTML 与 CSS 基础》 原生 JS):
document.querySelector('#count').textContent = orders.length; // 手动更新 ✓声明式(Vue):
<p v-text="count"></p> <!-- 数据变 → 自动更新 ✓ -->const count = ref(0); // 响应式数据
count.value = orders.length; // 只改数据 ✓<script setup>
import { ref, onMounted } from 'vue';
import { api } from '@/api'; // [S3-038](./S3-038) 的封装
const orders = ref([]);
const loading = ref(false);
async function load() {
loading.value = true;
try {
const page = await api('/api/orders?page=1&size=20');
orders.value = page.data; // ★ 数据变 → 视图自动变
} finally {
loading.value = false;
}
}
onMounted(load); // 组件挂载后加载 ✓
</script>
<template>
<table>
<tr v-for="o in orders" :key="o.orderNo"> <!-- :key 必须唯一 ★ -->
<td v-text="o.orderNo"></td>
<td v-text="(o.amountInCents / 100).toFixed(2)"></td>
</tr>
</table>
</template>★ 三段结构:<script setup>(逻辑)+ <template>(视图)+ <style>(样式)——一个文件一个组件 ✓
★ v-text 与插值等价:<td v-text="o.orderNo"> ≈ <td v-text="o.orderNo"></td>(框架会把表达式的结果渲染进去)✓ (本知识库文档用 v-text 展示,是因为站点的构建器会把双花括号当插值解析——《JSP 与 JSTL(技术演进理解)》 的同款限制,请留意 ⚠️)
ref 与 reactive const count = ref(0); // 基本类型用 ref;JS 里用 .value 访问 ★
count.value++;
const form = reactive({ productId: '', quantity: 1 }); // 对象用 reactive
form.quantity = 2;| API | 适用 | 访问方式 |
|---|---|---|
ref | 任何类型(推荐默认)★ | .value(模板里不需要) |
reactive | 对象/数组 | 直接 .属性(解构会失去响应性 ⚠️) |
★ reactive 的坑:解构赋值后变成普通变量 ✗ ——要么不解构,要么用 toRefs ✓
| 指令 | 作用 | 示例 |
|---|---|---|
v-if / v-show | 条件渲染(前者销毁/重建,后者只是隐藏)★ | v-if="loading" |
v-for | 列表(必须配 :key)★ | v-for="o in orders" :key="o.orderNo" |
v-model ★★ | 表单双向绑定 | <input v-model="form.productId"> |
v-text / v-html | 输出(v-html 不转义 ✗ XSS,《JSP 与 JSTL(技术演进理解)》) | — |
@click | 事件 | @click="remove(o)" |
v-model 的本质:
<input v-model="form.productId">
<!-- 等价于 -->
<input :value="form.productId" @input="form.productId = $event.target.value">★ 它是"数据 → 视图"与"视图 → 数据"的双向绑定 ✓ ——配合 《参数校验与全局异常处理》 的后端校验,形成"前端即时提示 + 后端兜底" ✓
<!-- 子组件 OrderRow.vue:只负责"展示一行" -->
<script setup>
defineProps({ order: { type: Object, required: true } });
const emit = defineEmits(['remove']);
</script>
<template>
<tr>
<td v-text="order.orderNo"></td>
<td><button @click="emit('remove', order.orderNo)">删除</button></td>
</tr>
</template><!-- 父组件:负责"拿数据"并监听子组件事件 -->
<OrderRow v-for="o in orders" :key="o.orderNo" :order="o" @remove="removeOrder" />通信规则:
| 方向 | 机制 | 记法 |
|---|---|---|
| 父 → 子 | props(单向数据流 ★) | "props 不该被子组件修改" ✓ |
| 子 → 父 | emit 事件 ✓ | 子组件"上报",父组件决定 ✓ |
★ 为什么"props 单向"重要:数据只有一个"权威来源" ——双向修改会让"谁改的"无法追溯 ✗
这与后端"单向依赖"(《Maven 进阶:依赖管理与多模块》 的模块依赖方向)是同一个原则 ✓
| Vue | React | |
|---|---|---|
| 模板 | HTML 模板(指令)✓ | JSX(JS 里写 HTML) |
| 响应式 | 自动追踪(改数据即更新)✓ | 手动 setState ⚠️ |
| 心智 | 更"开箱即用" ✓ | 更"灵活/自由" |
国内生态两者都主流——选团队熟悉的 ✓
<div id="app"></div>
<script type="module">
import { createApp, ref } from 'vue';
createApp({ setup() { return { msg: ref('Hello Vue') }; },
template: `<b v-text="msg"></b>` }).mount('#app');
</script>见 5.2 与 5.5 的组合 ✓ ——它就是"订单列表页"的前端版(对应 《Spring MVC 请求映射与参数绑定》 的后端九场景)✓
document.querySelector('table').innerHTML = rows.map(...).join(''); // ✗问题:绕过了响应式 ——Vue 不知道数据变了 ⚠️;而且 innerHTML 拼接用户数据就是 XSS ✗✗(《JSP 与 JSTL(技术演进理解)》)
修:改数据(orders.value = ...),让 Vue 渲染 ✓
运行方式:Vue 组件需要构建工具(Vite)。本知识库采用"最小内联"方式—— 完整可运行的 Vue 3 CDN 示例如下(保存为 .html 用浏览器打开 ✓):
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Vue 最小示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script></head>
<body>
<div id="app">
<h1 v-text="title"></h1>
<ul><li v-for="o in orders" :key="o.orderNo" v-text="o.orderNo + ' / ' + o.status"></li></ul>
<p v-if="orders.length === 0" v-text="'没有订单'"></p>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const title = ref('我的订单');
const orders = ref([
{ orderNo: 'ORD-1', status: '已支付' },
{ orderNo: 'ORD-2', status: '已创建' },
]);
return { title, orders };
}
}).mount('#app');
</script>
</body>
</html>★ 这段 HTML 的价值:不装任何工具就能体会"数据驱动视图" ——把 orders 改成空数组刷新,v-if 的那行就会出现 ✓
reactive 坑)✗;v-for 不加 :key:控制台警告 ⚠️(列表更新时可能错乱)✗;v-html 注入:v-html="'<script>alert(1)</script>'"——观察它执行 ✗✗(亲眼看一次 XSS,《JSP 与 JSTL(技术演进理解)》)✓。| # | 错误 | 说明 |
|---|---|---|
| 1 | v-for 不加 :key | 列表更新错乱 ✗ |
| 2 | 解构 reactive | 失去响应性 ✗ |
| 3 | v-html 渲染用户数据 | XSS ✗✗(《JSP 与 JSTL(技术演进理解)》) |
| 4 | 直接改 props ✗ | 单向数据流(5.5)——改用 emit ✓ |
| 5 | 在模板里写复杂逻辑 | 抽成 computed ✓(模板应保持"声明视图") |
v-model + 数组 push)✓;v-html 注入脚本——亲眼看 XSS(仅在本地实验 ⚠️)✓;api() 接进来,做成"真调接口的订单页" ✓。扩展任务:给列表加"筛选":状态下拉框(v-model)变化时重新请求接口——注意"筛选用查询参数还是 POST body"的选择(《Spring MVC 请求映射与参数绑定》 的综合任务)✓
const arr = ref([]) 后 arr.value.push(1) 会触发视图更新。(对 / 错)v-for 必须配 ______。v-if 与 v-show 的取舍?<input v-model="form.qty"> 等价于哪两件事?ref 的 .value 变化会被追踪):key ✓v-if 真正增删元素(初始 false 不渲染);v-show 只切 display ✓ 取舍:频繁切换用 v-show(开销小);初始大概率不显示/含重组件用 v-if ✓:value="form.qty" + @input="form.qty = $event.target.value"(5.4 的等价展开)✓emit('remove', orderNo) → 父组件调 DELETE → 成功后从 orders.value 里过滤掉 ✓(注意 《Ajax 与前后端交互》 的 204 处理 ✓)ref / reactive 与三大指令(v-if / v-for / v-model);v-html 的 XSS 风险(《JSP 与 JSTL(技术演进理解)》);《登录状态与 Token 机制》:登录之后,"凭证"在前端怎么存、怎么带、怎么续。