后端转全栈:前端思维转变(Vue 视角)

一个后端老手第一次正儿八经写前端,最崩溃的往往不是语法,而是**「思维拧巴」**:

  • 我明明 count = 100 了,为什么页面上的数字不动?
  • 一个数据,为什么要在组件之间传来传去,不能直接全局共享吗?
  • 我发了个请求,return 出来的结果为什么是 undefined
  • 后端一个 Controller 搞定的事,前端为什么要拆成七八个组件

你可能会觉得前端「又乱又绕」。但真相是------前端的底层逻辑是一套和你后端截然不同的「世界观」,你用后端的脑子硬套,当然处处别扭。

这篇不教你 Vue 语法,专治「思维拧巴」。我们用后端熟悉的概念当锚点,把前端最核心的几个思想转变讲透:

  1. 变量到底是怎么流转的?
  2. 怎么给后端传数据?
  3. 后端传回来的数据怎么处理?
  4. 数据是怎么隔离的?

老规矩,先上图,再讲理。


一、最根本的转变:从「请求驱动」到「状态驱动」

这是所有拧巴的根源。想通这一层,后面全都顺了。

1.1 后端的世界:请求驱动

你熟悉的后端,本质是**「来一个请求,我算一个结果给你」**。整个流程是一条「流水线」:
#mermaid-svg-A7d6ku49sBB1RXuy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A7d6ku49sBB1RXuy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A7d6ku49sBB1RXuy .error-icon{fill:#552222;}#mermaid-svg-A7d6ku49sBB1RXuy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A7d6ku49sBB1RXuy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A7d6ku49sBB1RXuy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A7d6ku49sBB1RXuy .marker.cross{stroke:#333333;}#mermaid-svg-A7d6ku49sBB1RXuy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A7d6ku49sBB1RXuy p{margin:0;}#mermaid-svg-A7d6ku49sBB1RXuy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-A7d6ku49sBB1RXuy .cluster-label text{fill:#333;}#mermaid-svg-A7d6ku49sBB1RXuy .cluster-label span{color:#333;}#mermaid-svg-A7d6ku49sBB1RXuy .cluster-label span p{background-color:transparent;}#mermaid-svg-A7d6ku49sBB1RXuy .label text,#mermaid-svg-A7d6ku49sBB1RXuy span{fill:#333;color:#333;}#mermaid-svg-A7d6ku49sBB1RXuy .node rect,#mermaid-svg-A7d6ku49sBB1RXuy .node circle,#mermaid-svg-A7d6ku49sBB1RXuy .node ellipse,#mermaid-svg-A7d6ku49sBB1RXuy .node polygon,#mermaid-svg-A7d6ku49sBB1RXuy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-A7d6ku49sBB1RXuy .rough-node .label text,#mermaid-svg-A7d6ku49sBB1RXuy .node .label text,#mermaid-svg-A7d6ku49sBB1RXuy .image-shape .label,#mermaid-svg-A7d6ku49sBB1RXuy .icon-shape .label{text-anchor:middle;}#mermaid-svg-A7d6ku49sBB1RXuy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-A7d6ku49sBB1RXuy .rough-node .label,#mermaid-svg-A7d6ku49sBB1RXuy .node .label,#mermaid-svg-A7d6ku49sBB1RXuy .image-shape .label,#mermaid-svg-A7d6ku49sBB1RXuy .icon-shape .label{text-align:center;}#mermaid-svg-A7d6ku49sBB1RXuy .node.clickable{cursor:pointer;}#mermaid-svg-A7d6ku49sBB1RXuy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-A7d6ku49sBB1RXuy .arrowheadPath{fill:#333333;}#mermaid-svg-A7d6ku49sBB1RXuy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-A7d6ku49sBB1RXuy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-A7d6ku49sBB1RXuy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A7d6ku49sBB1RXuy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-A7d6ku49sBB1RXuy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A7d6ku49sBB1RXuy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-A7d6ku49sBB1RXuy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-A7d6ku49sBB1RXuy .cluster text{fill:#333;}#mermaid-svg-A7d6ku49sBB1RXuy .cluster span{color:#333;}#mermaid-svg-A7d6ku49sBB1RXuy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-A7d6ku49sBB1RXuy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-A7d6ku49sBB1RXuy rect.text{fill:none;stroke-width:0;}#mermaid-svg-A7d6ku49sBB1RXuy .icon-shape,#mermaid-svg-A7d6ku49sBB1RXuy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A7d6ku49sBB1RXuy .icon-shape p,#mermaid-svg-A7d6ku49sBB1RXuy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-A7d6ku49sBB1RXuy .icon-shape .label rect,#mermaid-svg-A7d6ku49sBB1RXuy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A7d6ku49sBB1RXuy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-A7d6ku49sBB1RXuy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-A7d6ku49sBB1RXuy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 请求
Controller
Service
DAO / 数据库
返回数据
响应 HTML/JSON
请求结束

特点:

  • 一次请求,一条流水线,走完就结束;
  • 变量大多是局部变量,跟着这次请求生、跟着这次请求死;
  • 结果是「算出来」的,算完直接返回给浏览器渲染。

1.2 前端的世界:状态驱动

前端(尤其 Vue、React)是完全反过来的思路。它信奉一句话:

视图(页面)= 状态(state)的函数。

意思是:页面长什么样,完全由「当前的状态」决定。你只要改状态,视图会自动跟着变。你不需要手动去「改页面上的某个元素」,你只需要改「数据」,剩下的交给框架。
#mermaid-svg-9F7EGC4WPqpIkhe8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9F7EGC4WPqpIkhe8 .error-icon{fill:#552222;}#mermaid-svg-9F7EGC4WPqpIkhe8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9F7EGC4WPqpIkhe8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .marker.cross{stroke:#333333;}#mermaid-svg-9F7EGC4WPqpIkhe8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9F7EGC4WPqpIkhe8 p{margin:0;}#mermaid-svg-9F7EGC4WPqpIkhe8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster-label text{fill:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster-label span{color:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster-label span p{background-color:transparent;}#mermaid-svg-9F7EGC4WPqpIkhe8 .label text,#mermaid-svg-9F7EGC4WPqpIkhe8 span{fill:#333;color:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .node rect,#mermaid-svg-9F7EGC4WPqpIkhe8 .node circle,#mermaid-svg-9F7EGC4WPqpIkhe8 .node ellipse,#mermaid-svg-9F7EGC4WPqpIkhe8 .node polygon,#mermaid-svg-9F7EGC4WPqpIkhe8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .rough-node .label text,#mermaid-svg-9F7EGC4WPqpIkhe8 .node .label text,#mermaid-svg-9F7EGC4WPqpIkhe8 .image-shape .label,#mermaid-svg-9F7EGC4WPqpIkhe8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9F7EGC4WPqpIkhe8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .rough-node .label,#mermaid-svg-9F7EGC4WPqpIkhe8 .node .label,#mermaid-svg-9F7EGC4WPqpIkhe8 .image-shape .label,#mermaid-svg-9F7EGC4WPqpIkhe8 .icon-shape .label{text-align:center;}#mermaid-svg-9F7EGC4WPqpIkhe8 .node.clickable{cursor:pointer;}#mermaid-svg-9F7EGC4WPqpIkhe8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .arrowheadPath{fill:#333333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9F7EGC4WPqpIkhe8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9F7EGC4WPqpIkhe8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9F7EGC4WPqpIkhe8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster text{fill:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 .cluster span{color:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9F7EGC4WPqpIkhe8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9F7EGC4WPqpIkhe8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9F7EGC4WPqpIkhe8 .icon-shape,#mermaid-svg-9F7EGC4WPqpIkhe8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9F7EGC4WPqpIkhe8 .icon-shape p,#mermaid-svg-9F7EGC4WPqpIkhe8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9F7EGC4WPqpIkhe8 .icon-shape .label rect,#mermaid-svg-9F7EGC4WPqpIkhe8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9F7EGC4WPqpIkhe8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9F7EGC4WPqpIkhe8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9F7EGC4WPqpIkhe8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 自动渲染
修改
触发
状态 state

(数据)
视图 view

(页面)
用户操作 / 事件

1.3 一张图看懂两种世界观

维度 后端(请求驱动) 前端(状态驱动)
核心问题 「这个请求,我该返回什么?」 「当前状态是什么,页面就显示什么」
数据来源 数据库 / 缓存,用完即走 内存里的状态,常驻页面生命周期
更新方式 重新执行一次流程 改状态,视图自动更新
关注点 过程(Controller→Service→DAO) 状态(数据变,视图跟着变)

一句话 :后端是「来一个请求,我算一个结果 」;前端是「我维护一堆状态,视图始终是状态的投影」。你以前写的每个请求都是一次「运算」,而前端是一个「会自己跟着数据变的画面」。


二、变量是如何流转的(重点)

搞懂「状态驱动」后,第一个具体问题是:前端的变量,到底是怎么活、怎么变的?

2.1 后端变量 vs 前端变量:寿命完全不同

java 复制代码
// 后端:局部变量,请求结束就没了
@GetMapping("/user")
public User getUser() {
    User u = userService.findById(1);  // u 是这次请求的局部变量
    return u;                          // 返回后,u 就消失了
}
vue 复制代码
<script setup>
// 前端:状态变量,活在「组件」的整个生命周期里
import { ref } from 'vue'

const count = ref(0)   // 这个 count 会一直存在,直到组件被销毁
</script>

⚠️ 关键差异:后端的变量是「一次性 」的,算完就走;前端的变量是「常驻」的,页面开着它就在,页面关了它才没。

2.2 响应式:改变量 = 改视图

这是后端程序员最容易懵的一点。在 Vue 里,一个普通变量和一个「响应式变量」,行为天差地别:

vue 复制代码
<script setup>
import { ref } from 'vue'

let normal = 0          // 普通变量
const count = ref(0)    // 响应式变量(用 ref 包裹)

function increment() {
    normal = normal + 1    // ❌ 改了,但页面不会变!
    count.value = count.value + 1   // ✅ 改了,页面自动更新
}
</script>

#mermaid-svg-0g3h8eoFSRMvDyWC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0g3h8eoFSRMvDyWC .error-icon{fill:#552222;}#mermaid-svg-0g3h8eoFSRMvDyWC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0g3h8eoFSRMvDyWC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0g3h8eoFSRMvDyWC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0g3h8eoFSRMvDyWC .marker.cross{stroke:#333333;}#mermaid-svg-0g3h8eoFSRMvDyWC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0g3h8eoFSRMvDyWC p{margin:0;}#mermaid-svg-0g3h8eoFSRMvDyWC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster-label text{fill:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster-label span{color:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster-label span p{background-color:transparent;}#mermaid-svg-0g3h8eoFSRMvDyWC .label text,#mermaid-svg-0g3h8eoFSRMvDyWC span{fill:#333;color:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC .node rect,#mermaid-svg-0g3h8eoFSRMvDyWC .node circle,#mermaid-svg-0g3h8eoFSRMvDyWC .node ellipse,#mermaid-svg-0g3h8eoFSRMvDyWC .node polygon,#mermaid-svg-0g3h8eoFSRMvDyWC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0g3h8eoFSRMvDyWC .rough-node .label text,#mermaid-svg-0g3h8eoFSRMvDyWC .node .label text,#mermaid-svg-0g3h8eoFSRMvDyWC .image-shape .label,#mermaid-svg-0g3h8eoFSRMvDyWC .icon-shape .label{text-anchor:middle;}#mermaid-svg-0g3h8eoFSRMvDyWC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0g3h8eoFSRMvDyWC .rough-node .label,#mermaid-svg-0g3h8eoFSRMvDyWC .node .label,#mermaid-svg-0g3h8eoFSRMvDyWC .image-shape .label,#mermaid-svg-0g3h8eoFSRMvDyWC .icon-shape .label{text-align:center;}#mermaid-svg-0g3h8eoFSRMvDyWC .node.clickable{cursor:pointer;}#mermaid-svg-0g3h8eoFSRMvDyWC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0g3h8eoFSRMvDyWC .arrowheadPath{fill:#333333;}#mermaid-svg-0g3h8eoFSRMvDyWC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0g3h8eoFSRMvDyWC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0g3h8eoFSRMvDyWC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0g3h8eoFSRMvDyWC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0g3h8eoFSRMvDyWC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0g3h8eoFSRMvDyWC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster text{fill:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC .cluster span{color:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0g3h8eoFSRMvDyWC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0g3h8eoFSRMvDyWC rect.text{fill:none;stroke-width:0;}#mermaid-svg-0g3h8eoFSRMvDyWC .icon-shape,#mermaid-svg-0g3h8eoFSRMvDyWC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0g3h8eoFSRMvDyWC .icon-shape p,#mermaid-svg-0g3h8eoFSRMvDyWC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0g3h8eoFSRMvDyWC .icon-shape .label rect,#mermaid-svg-0g3h8eoFSRMvDyWC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0g3h8eoFSRMvDyWC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0g3h8eoFSRMvDyWC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0g3h8eoFSRMvDyWC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 响应式变量 ref
count.value = 1
自动通知视图
页面数字 +1
普通变量
normal = 1
页面无反应

ref(或 reactive)就是 Vue 的「魔法」------它把变量变成「可追踪」的,一旦修改,就会自动通知所有用到它的地方更新。

2.3 单向数据流:数据只有一条「从状态到视图」的路

前端的数据流是单向的,这是理解一切的关键:
#mermaid-svg-1Nasbt6L6AqcEMPI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Nasbt6L6AqcEMPI .error-icon{fill:#552222;}#mermaid-svg-1Nasbt6L6AqcEMPI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Nasbt6L6AqcEMPI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Nasbt6L6AqcEMPI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Nasbt6L6AqcEMPI .marker.cross{stroke:#333333;}#mermaid-svg-1Nasbt6L6AqcEMPI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Nasbt6L6AqcEMPI p{margin:0;}#mermaid-svg-1Nasbt6L6AqcEMPI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster-label text{fill:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster-label span{color:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster-label span p{background-color:transparent;}#mermaid-svg-1Nasbt6L6AqcEMPI .label text,#mermaid-svg-1Nasbt6L6AqcEMPI span{fill:#333;color:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI .node rect,#mermaid-svg-1Nasbt6L6AqcEMPI .node circle,#mermaid-svg-1Nasbt6L6AqcEMPI .node ellipse,#mermaid-svg-1Nasbt6L6AqcEMPI .node polygon,#mermaid-svg-1Nasbt6L6AqcEMPI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1Nasbt6L6AqcEMPI .rough-node .label text,#mermaid-svg-1Nasbt6L6AqcEMPI .node .label text,#mermaid-svg-1Nasbt6L6AqcEMPI .image-shape .label,#mermaid-svg-1Nasbt6L6AqcEMPI .icon-shape .label{text-anchor:middle;}#mermaid-svg-1Nasbt6L6AqcEMPI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1Nasbt6L6AqcEMPI .rough-node .label,#mermaid-svg-1Nasbt6L6AqcEMPI .node .label,#mermaid-svg-1Nasbt6L6AqcEMPI .image-shape .label,#mermaid-svg-1Nasbt6L6AqcEMPI .icon-shape .label{text-align:center;}#mermaid-svg-1Nasbt6L6AqcEMPI .node.clickable{cursor:pointer;}#mermaid-svg-1Nasbt6L6AqcEMPI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1Nasbt6L6AqcEMPI .arrowheadPath{fill:#333333;}#mermaid-svg-1Nasbt6L6AqcEMPI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1Nasbt6L6AqcEMPI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1Nasbt6L6AqcEMPI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1Nasbt6L6AqcEMPI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1Nasbt6L6AqcEMPI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1Nasbt6L6AqcEMPI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster text{fill:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI .cluster span{color:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1Nasbt6L6AqcEMPI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1Nasbt6L6AqcEMPI rect.text{fill:none;stroke-width:0;}#mermaid-svg-1Nasbt6L6AqcEMPI .icon-shape,#mermaid-svg-1Nasbt6L6AqcEMPI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1Nasbt6L6AqcEMPI .icon-shape p,#mermaid-svg-1Nasbt6L6AqcEMPI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1Nasbt6L6AqcEMPI .icon-shape .label rect,#mermaid-svg-1Nasbt6L6AqcEMPI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1Nasbt6L6AqcEMPI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1Nasbt6L6AqcEMPI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1Nasbt6L6AqcEMPI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 流向
② 用户触发事件
③ 修改状态
状态 state
模板/视图
事件处理函数

数据永远「从状态 → 视图 」;要改变数据,只能「通过事件 → 改状态 → 视图再自动更新 」。你不会、也不应该去直接操作视图里的 DOM

你可能会问:那 Vue 的 v-model(双向绑定)不是「双向」吗?其实 v-model 只是语法糖------它本质还是「输入触发事件 → 改状态 → 视图更新」,只是 Vue 帮你把「监听输入 + 改状态」这两步打包了,让你写起来像「双向」而已。
一句话 :前端的变量是「响应式状态 」,常驻页面、改了自动更新视图。数据流是单向的------状态流向视图,事件改状态。记住这条「单行道」,你就不会写出「手动改 DOM」的别扭代码。


三、如何给后端传数据(重点)

这一步其实你「反向」已经很熟------你在后端写过无数个接口收数据。现在站在发送端看看。

3.1 你在后端怎么收,前端就怎么发

先回忆你后端的接收姿势:

java 复制代码
// 后端:接收数据的三种位置
@GetMapping("/user")                    // GET:参数放 URL query
public User get(@RequestParam Long id) { ... }

@PostMapping("/user")                   // POST:数据放 body
public User create(@RequestBody User u) { ... }

@GetMapping("/user/{id}")               // 路径参数
public User getById(@PathVariable Long id) { ... }

前端发送端,一一对应:

javascript 复制代码
// 前端:axios 发请求(和上面后端一一对应)
import axios from 'axios'

// GET:参数拼在 URL 的 query 里
axios.get('/user', { params: { id: 1 } })

// POST:数据放 body 里
axios.post('/user', { name: '小明', age: 18 })

// 路径参数:直接拼进 URL
axios.get('/user/1')

3.2 最关键的认知:请求是「异步」的

这是后端程序员转前端最大的一个坎 。你后端写代码,调用是同步的:

java 复制代码
// 后端:同步,直接拿到结果
User u = userService.findById(1);
System.out.println(u.getName());   // 立刻能拿到

但前端的网络请求是异步 的------「发出去」之后,不会立刻有结果 ,要等后端处理完、网络传回来,才能拿到。所以你不能这样写

javascript 复制代码
// ❌ 错误想法:以为能同步拿到结果
const user = axios.get('/user/1')   // user 不是数据,而是一个「Promise」!
console.log(user.name)              // undefined!

正确姿势是「等它回来 」,用 async/await

javascript 复制代码
// ✅ 正确:async/await 等待结果
async function getUser() {
    const resp = await axios.get('/user/1')   // 等请求完成
    const user = resp.data                    // 拿到数据
    console.log(user.name)                    // 现在有了
}

后端 前端 后端 前端 #mermaid-svg-HLvEJiLpIxBF5LrM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HLvEJiLpIxBF5LrM .error-icon{fill:#552222;}#mermaid-svg-HLvEJiLpIxBF5LrM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HLvEJiLpIxBF5LrM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HLvEJiLpIxBF5LrM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HLvEJiLpIxBF5LrM .marker.cross{stroke:#333333;}#mermaid-svg-HLvEJiLpIxBF5LrM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HLvEJiLpIxBF5LrM p{margin:0;}#mermaid-svg-HLvEJiLpIxBF5LrM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HLvEJiLpIxBF5LrM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-HLvEJiLpIxBF5LrM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-HLvEJiLpIxBF5LrM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-HLvEJiLpIxBF5LrM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-HLvEJiLpIxBF5LrM .sequenceNumber{fill:white;}#mermaid-svg-HLvEJiLpIxBF5LrM #sequencenumber{fill:#333;}#mermaid-svg-HLvEJiLpIxBF5LrM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-HLvEJiLpIxBF5LrM .messageText{fill:#333;stroke:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HLvEJiLpIxBF5LrM .labelText,#mermaid-svg-HLvEJiLpIxBF5LrM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .loopText,#mermaid-svg-HLvEJiLpIxBF5LrM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-HLvEJiLpIxBF5LrM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-HLvEJiLpIxBF5LrM .noteText,#mermaid-svg-HLvEJiLpIxBF5LrM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-HLvEJiLpIxBF5LrM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HLvEJiLpIxBF5LrM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HLvEJiLpIxBF5LrM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HLvEJiLpIxBF5LrM .actorPopupMenu{position:absolute;}#mermaid-svg-HLvEJiLpIxBF5LrM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-HLvEJiLpIxBF5LrM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HLvEJiLpIxBF5LrM .actor-man circle,#mermaid-svg-HLvEJiLpIxBF5LrM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-HLvEJiLpIxBF5LrM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 这段时间前端继续干别的(渲染空页面、显示 loading) 发请求(继续往下执行,不等待)响应返回(此时才触发回调)拿到数据,更新状态

⚠️ 核心心法:请求发出去,结果是「未来某个时刻」才到的 。你不能用后端「同步调用」的直觉去拿结果,必须用 await(或回调、.then())来「等」。这是前端异步和「变量流转」纠缠最深的地方。
一句话 :给后端传数据,就是「你在后端怎么收,前端就怎么发 」(GET 拼 query、POST 带 body、路径参数拼 URL)。但记住:请求是异步的 ,要用 await 等结果,不能同步 return 拿到值。


四、后端传过来的数据怎么处理

数据「发出去」难,其实「接回来」也有讲究。

4.1 从「返回模板」到「填状态」

后端渲染页面的思路,是「直接算好 HTML 返回」:

java 复制代码
// 后端(如 Thymeleaf/JSP):服务端渲染
model.addAttribute("users", userList);
return "userList";   // 返回一个完整的 HTML 页面

前端的思路则是「先拿数据,再填进状态,视图自己变」:

javascript 复制代码
// 前端:拿数据 → 塞进状态 → 视图自动更新
import { ref } from 'vue'
import axios from 'axios'

const users = ref([])          // ① 先定义一个空的响应式状态

async function loadUsers() {
    const resp = await axios.get('/users')  // ② 拿数据
    users.value = resp.data                // ③ 填进状态
    // ④ 视图自动重新渲染,展示 users
}

#mermaid-svg-VeP14ptDzhi2CMQX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VeP14ptDzhi2CMQX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VeP14ptDzhi2CMQX .error-icon{fill:#552222;}#mermaid-svg-VeP14ptDzhi2CMQX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VeP14ptDzhi2CMQX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VeP14ptDzhi2CMQX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VeP14ptDzhi2CMQX .marker.cross{stroke:#333333;}#mermaid-svg-VeP14ptDzhi2CMQX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VeP14ptDzhi2CMQX p{margin:0;}#mermaid-svg-VeP14ptDzhi2CMQX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VeP14ptDzhi2CMQX .cluster-label text{fill:#333;}#mermaid-svg-VeP14ptDzhi2CMQX .cluster-label span{color:#333;}#mermaid-svg-VeP14ptDzhi2CMQX .cluster-label span p{background-color:transparent;}#mermaid-svg-VeP14ptDzhi2CMQX .label text,#mermaid-svg-VeP14ptDzhi2CMQX span{fill:#333;color:#333;}#mermaid-svg-VeP14ptDzhi2CMQX .node rect,#mermaid-svg-VeP14ptDzhi2CMQX .node circle,#mermaid-svg-VeP14ptDzhi2CMQX .node ellipse,#mermaid-svg-VeP14ptDzhi2CMQX .node polygon,#mermaid-svg-VeP14ptDzhi2CMQX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VeP14ptDzhi2CMQX .rough-node .label text,#mermaid-svg-VeP14ptDzhi2CMQX .node .label text,#mermaid-svg-VeP14ptDzhi2CMQX .image-shape .label,#mermaid-svg-VeP14ptDzhi2CMQX .icon-shape .label{text-anchor:middle;}#mermaid-svg-VeP14ptDzhi2CMQX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VeP14ptDzhi2CMQX .rough-node .label,#mermaid-svg-VeP14ptDzhi2CMQX .node .label,#mermaid-svg-VeP14ptDzhi2CMQX .image-shape .label,#mermaid-svg-VeP14ptDzhi2CMQX .icon-shape .label{text-align:center;}#mermaid-svg-VeP14ptDzhi2CMQX .node.clickable{cursor:pointer;}#mermaid-svg-VeP14ptDzhi2CMQX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VeP14ptDzhi2CMQX .arrowheadPath{fill:#333333;}#mermaid-svg-VeP14ptDzhi2CMQX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VeP14ptDzhi2CMQX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VeP14ptDzhi2CMQX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VeP14ptDzhi2CMQX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VeP14ptDzhi2CMQX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VeP14ptDzhi2CMQX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VeP14ptDzhi2CMQX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VeP14ptDzhi2CMQX .cluster text{fill:#333;}#mermaid-svg-VeP14ptDzhi2CMQX .cluster span{color:#333;}#mermaid-svg-VeP14ptDzhi2CMQX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VeP14ptDzhi2CMQX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VeP14ptDzhi2CMQX rect.text{fill:none;stroke-width:0;}#mermaid-svg-VeP14ptDzhi2CMQX .icon-shape,#mermaid-svg-VeP14ptDzhi2CMQX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VeP14ptDzhi2CMQX .icon-shape p,#mermaid-svg-VeP14ptDzhi2CMQX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VeP14ptDzhi2CMQX .icon-shape .label rect,#mermaid-svg-VeP14ptDzhi2CMQX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VeP14ptDzhi2CMQX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VeP14ptDzhi2CMQX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VeP14ptDzhi2CMQX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} await 拿到
自动触发
后端返回 JSON
塞进响应式状态

users.value = resp.data
视图重新渲染

4.2 异步的坑:数据「还没到」怎么办

因为请求是异步的,页面第一次渲染时,数据往往还没回来。所以你必须处理这几种状态:

状态 含义 处理
loading 数据还在路上 显示「加载中...」
空状态 数据回来了,但是空的 显示「暂无数据」
错误 请求失败了 显示「加载失败」+ 重试
vue 复制代码
<script setup>
import { ref } from 'vue'

const users = ref([])
const loading = ref(false)
const error = ref('')

async function loadUsers() {
    loading.value = true
    try {
        const resp = await axios.get('/users')
        users.value = resp.data
    } catch (e) {
        error.value = '加载失败'
    } finally {
        loading.value = false
    }
}
</script>

这和后端最大的区别:后端是「要么成功返回、要么报错 」,干脆利落;前端是「数据有一个『还没到』的空窗期」,你必须主动管理这个空窗期,否则页面就会「闪一下空白」或「卡在加载」。
一句话 :处理后端数据 = 「拿 JSON → 塞进响应式状态 → 视图自动更新 」三步。但别忘了异步的「空窗期」------loading、空状态、错误,这三样是前端处理数据的「标配三件套」。


五、数据隔离(重点)

后端程序员问得最多的问题之一:「为什么前端的数据要传来传去,不能直接搞个全局变量共享吗?」答案藏在「隔离」里。

5.1 后端 vs 前端:隔离的「基本单元」不同

  • 后端的数据隔离,靠的是作用域、事务、线程局部变量,框架帮你隔离得妥妥的;
  • 前端的数据隔离,基本单元是**「组件」------每个组件的状态默认是私有的**,别的组件碰不到。

#mermaid-svg-f2a4ZvKjOn7ykihv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f2a4ZvKjOn7ykihv .error-icon{fill:#552222;}#mermaid-svg-f2a4ZvKjOn7ykihv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f2a4ZvKjOn7ykihv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f2a4ZvKjOn7ykihv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f2a4ZvKjOn7ykihv .marker.cross{stroke:#333333;}#mermaid-svg-f2a4ZvKjOn7ykihv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f2a4ZvKjOn7ykihv p{margin:0;}#mermaid-svg-f2a4ZvKjOn7ykihv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster-label text{fill:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster-label span{color:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster-label span p{background-color:transparent;}#mermaid-svg-f2a4ZvKjOn7ykihv .label text,#mermaid-svg-f2a4ZvKjOn7ykihv span{fill:#333;color:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv .node rect,#mermaid-svg-f2a4ZvKjOn7ykihv .node circle,#mermaid-svg-f2a4ZvKjOn7ykihv .node ellipse,#mermaid-svg-f2a4ZvKjOn7ykihv .node polygon,#mermaid-svg-f2a4ZvKjOn7ykihv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f2a4ZvKjOn7ykihv .rough-node .label text,#mermaid-svg-f2a4ZvKjOn7ykihv .node .label text,#mermaid-svg-f2a4ZvKjOn7ykihv .image-shape .label,#mermaid-svg-f2a4ZvKjOn7ykihv .icon-shape .label{text-anchor:middle;}#mermaid-svg-f2a4ZvKjOn7ykihv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f2a4ZvKjOn7ykihv .rough-node .label,#mermaid-svg-f2a4ZvKjOn7ykihv .node .label,#mermaid-svg-f2a4ZvKjOn7ykihv .image-shape .label,#mermaid-svg-f2a4ZvKjOn7ykihv .icon-shape .label{text-align:center;}#mermaid-svg-f2a4ZvKjOn7ykihv .node.clickable{cursor:pointer;}#mermaid-svg-f2a4ZvKjOn7ykihv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f2a4ZvKjOn7ykihv .arrowheadPath{fill:#333333;}#mermaid-svg-f2a4ZvKjOn7ykihv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f2a4ZvKjOn7ykihv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f2a4ZvKjOn7ykihv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f2a4ZvKjOn7ykihv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f2a4ZvKjOn7ykihv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f2a4ZvKjOn7ykihv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster text{fill:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv .cluster span{color:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f2a4ZvKjOn7ykihv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f2a4ZvKjOn7ykihv rect.text{fill:none;stroke-width:0;}#mermaid-svg-f2a4ZvKjOn7ykihv .icon-shape,#mermaid-svg-f2a4ZvKjOn7ykihv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f2a4ZvKjOn7ykihv .icon-shape p,#mermaid-svg-f2a4ZvKjOn7ykihv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f2a4ZvKjOn7ykihv .icon-shape .label rect,#mermaid-svg-f2a4ZvKjOn7ykihv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f2a4ZvKjOn7ykihv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f2a4ZvKjOn7ykihv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f2a4ZvKjOn7ykihv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 组件 B
组件 A
互不影响
私有状态 count = 1
私有状态 count = 100

这就像两个互不相干的小程序,各自维护自己的数据。这是好事------它避免了「一个全局变量被几十处乱改」的灾难。

5.2 父子组件怎么通信?

组件隔离了,那组件之间要协作怎么办?Vue 给了两条「合法通道 」,而且方向是单向的:
#mermaid-svg-oUeunX2Vy4ABw4i5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oUeunX2Vy4ABw4i5 .error-icon{fill:#552222;}#mermaid-svg-oUeunX2Vy4ABw4i5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oUeunX2Vy4ABw4i5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .marker.cross{stroke:#333333;}#mermaid-svg-oUeunX2Vy4ABw4i5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oUeunX2Vy4ABw4i5 p{margin:0;}#mermaid-svg-oUeunX2Vy4ABw4i5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster-label text{fill:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster-label span{color:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster-label span p{background-color:transparent;}#mermaid-svg-oUeunX2Vy4ABw4i5 .label text,#mermaid-svg-oUeunX2Vy4ABw4i5 span{fill:#333;color:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .node rect,#mermaid-svg-oUeunX2Vy4ABw4i5 .node circle,#mermaid-svg-oUeunX2Vy4ABw4i5 .node ellipse,#mermaid-svg-oUeunX2Vy4ABw4i5 .node polygon,#mermaid-svg-oUeunX2Vy4ABw4i5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .rough-node .label text,#mermaid-svg-oUeunX2Vy4ABw4i5 .node .label text,#mermaid-svg-oUeunX2Vy4ABw4i5 .image-shape .label,#mermaid-svg-oUeunX2Vy4ABw4i5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-oUeunX2Vy4ABw4i5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .rough-node .label,#mermaid-svg-oUeunX2Vy4ABw4i5 .node .label,#mermaid-svg-oUeunX2Vy4ABw4i5 .image-shape .label,#mermaid-svg-oUeunX2Vy4ABw4i5 .icon-shape .label{text-align:center;}#mermaid-svg-oUeunX2Vy4ABw4i5 .node.clickable{cursor:pointer;}#mermaid-svg-oUeunX2Vy4ABw4i5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .arrowheadPath{fill:#333333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oUeunX2Vy4ABw4i5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oUeunX2Vy4ABw4i5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oUeunX2Vy4ABw4i5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster text{fill:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 .cluster span{color:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oUeunX2Vy4ABw4i5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oUeunX2Vy4ABw4i5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-oUeunX2Vy4ABw4i5 .icon-shape,#mermaid-svg-oUeunX2Vy4ABw4i5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oUeunX2Vy4ABw4i5 .icon-shape p,#mermaid-svg-oUeunX2Vy4ABw4i5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oUeunX2Vy4ABw4i5 .icon-shape .label rect,#mermaid-svg-oUeunX2Vy4ABw4i5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oUeunX2Vy4ABw4i5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oUeunX2Vy4ABw4i5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oUeunX2Vy4ABw4i5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① props 传数据(向下)
② emit 发事件(向上)
父组件
子组件

  • 父 → 子 :用 props(属性),父组件把数据「递给」子组件,子组件只能读、不能改;
  • 子 → 父 :用 emit(事件),子组件「喊一声」,父组件自己决定怎么改自己的状态。
vue 复制代码
<!-- 父组件:把 user 传给子组件,并监听子组件发来的 update 事件 -->
<Child :user="user" @update="handleUpdate" />
vue 复制代码
<!-- 子组件:接收 props,通过 emit 通知父组件 -->
<script setup>
defineProps(['user'])          // 接收父组件传来的数据(只读)
const emit = defineEmits(['update'])   // 声明可以发的事件

function notify() {
    emit('update', '新数据')   // 通知父组件,而不是直接改父组件的数据
}
</script>

🎯 记住一个原则:数据只能「单向」流 。父组件传数据给子组件(props),子组件想改数据,只能「发事件」(emit)请父组件改。子组件永远不直接改父组件的数据------这保证了数据流向清晰、可追踪。

5.3 状态提升:多个组件要共享怎么办?

那「兄弟组件」要共享同一份数据呢?它们不能直接通信,而是要把数据「提升」到共同的父组件:
#mermaid-svg-ZysxJXbohMVeq3Mv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZysxJXbohMVeq3Mv .error-icon{fill:#552222;}#mermaid-svg-ZysxJXbohMVeq3Mv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZysxJXbohMVeq3Mv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZysxJXbohMVeq3Mv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZysxJXbohMVeq3Mv .marker.cross{stroke:#333333;}#mermaid-svg-ZysxJXbohMVeq3Mv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZysxJXbohMVeq3Mv p{margin:0;}#mermaid-svg-ZysxJXbohMVeq3Mv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster-label text{fill:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster-label span{color:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster-label span p{background-color:transparent;}#mermaid-svg-ZysxJXbohMVeq3Mv .label text,#mermaid-svg-ZysxJXbohMVeq3Mv span{fill:#333;color:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv .node rect,#mermaid-svg-ZysxJXbohMVeq3Mv .node circle,#mermaid-svg-ZysxJXbohMVeq3Mv .node ellipse,#mermaid-svg-ZysxJXbohMVeq3Mv .node polygon,#mermaid-svg-ZysxJXbohMVeq3Mv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZysxJXbohMVeq3Mv .rough-node .label text,#mermaid-svg-ZysxJXbohMVeq3Mv .node .label text,#mermaid-svg-ZysxJXbohMVeq3Mv .image-shape .label,#mermaid-svg-ZysxJXbohMVeq3Mv .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZysxJXbohMVeq3Mv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZysxJXbohMVeq3Mv .rough-node .label,#mermaid-svg-ZysxJXbohMVeq3Mv .node .label,#mermaid-svg-ZysxJXbohMVeq3Mv .image-shape .label,#mermaid-svg-ZysxJXbohMVeq3Mv .icon-shape .label{text-align:center;}#mermaid-svg-ZysxJXbohMVeq3Mv .node.clickable{cursor:pointer;}#mermaid-svg-ZysxJXbohMVeq3Mv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZysxJXbohMVeq3Mv .arrowheadPath{fill:#333333;}#mermaid-svg-ZysxJXbohMVeq3Mv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZysxJXbohMVeq3Mv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZysxJXbohMVeq3Mv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZysxJXbohMVeq3Mv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZysxJXbohMVeq3Mv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZysxJXbohMVeq3Mv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster text{fill:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv .cluster span{color:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZysxJXbohMVeq3Mv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZysxJXbohMVeq3Mv rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZysxJXbohMVeq3Mv .icon-shape,#mermaid-svg-ZysxJXbohMVeq3Mv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZysxJXbohMVeq3Mv .icon-shape p,#mermaid-svg-ZysxJXbohMVeq3Mv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZysxJXbohMVeq3Mv .icon-shape .label rect,#mermaid-svg-ZysxJXbohMVeq3Mv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZysxJXbohMVeq3Mv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZysxJXbohMVeq3Mv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZysxJXbohMVeq3Mv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} props
props
emit
emit
父组件(放共享状态)
子组件 A
子组件 B

如果项目大了、共享状态太复杂,就放进全局状态管理 (Vue 里是 Pinia)------它本质上就是一个「放在最顶层、谁都能读写的共享状态池」,但依然遵循「单向数据流 + 显式修改」的纪律。

为什么不能直接「全局变量」?因为全局可变变量是 bug 的温床 ------你永远不知道是谁、在什么时候、把它改成了什么。前端的「状态管理」不是不让你共享,而是让你「有纪律地共享」:数据流向清晰、修改有迹可循。
一句话 :前端的数据隔离基本单元是组件 ,组件状态默认私有。通信走两条「单行道」------props 往下传、emit 往上喊。共享数据要「状态提升」或交给 Pinia。核心纪律:单向数据流,谁的数据谁负责改。


六、生命周期与副作用:另一个思维转变

还有一个后端几乎没有、前端却很重要的概念:生命周期

6.1 后端没有「生命周期」的烦恼

后端对象(Controller、Service)的创建和销毁,都由框架(Spring 等)管理,你几乎不用关心「它什么时候生、什么时候死」。

6.2 前端组件有明确的「生老病死」

前端组件有清晰的生命周期:挂载(创建)→ 更新 → 卸载(销毁)。你要在正确的时机做正确的事:
#mermaid-svg-evs22n32awTwRRCM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-evs22n32awTwRRCM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-evs22n32awTwRRCM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-evs22n32awTwRRCM .error-icon{fill:#552222;}#mermaid-svg-evs22n32awTwRRCM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-evs22n32awTwRRCM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-evs22n32awTwRRCM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-evs22n32awTwRRCM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-evs22n32awTwRRCM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-evs22n32awTwRRCM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-evs22n32awTwRRCM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-evs22n32awTwRRCM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-evs22n32awTwRRCM .marker.cross{stroke:#333333;}#mermaid-svg-evs22n32awTwRRCM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-evs22n32awTwRRCM p{margin:0;}#mermaid-svg-evs22n32awTwRRCM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-evs22n32awTwRRCM .cluster-label text{fill:#333;}#mermaid-svg-evs22n32awTwRRCM .cluster-label span{color:#333;}#mermaid-svg-evs22n32awTwRRCM .cluster-label span p{background-color:transparent;}#mermaid-svg-evs22n32awTwRRCM .label text,#mermaid-svg-evs22n32awTwRRCM span{fill:#333;color:#333;}#mermaid-svg-evs22n32awTwRRCM .node rect,#mermaid-svg-evs22n32awTwRRCM .node circle,#mermaid-svg-evs22n32awTwRRCM .node ellipse,#mermaid-svg-evs22n32awTwRRCM .node polygon,#mermaid-svg-evs22n32awTwRRCM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-evs22n32awTwRRCM .rough-node .label text,#mermaid-svg-evs22n32awTwRRCM .node .label text,#mermaid-svg-evs22n32awTwRRCM .image-shape .label,#mermaid-svg-evs22n32awTwRRCM .icon-shape .label{text-anchor:middle;}#mermaid-svg-evs22n32awTwRRCM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-evs22n32awTwRRCM .rough-node .label,#mermaid-svg-evs22n32awTwRRCM .node .label,#mermaid-svg-evs22n32awTwRRCM .image-shape .label,#mermaid-svg-evs22n32awTwRRCM .icon-shape .label{text-align:center;}#mermaid-svg-evs22n32awTwRRCM .node.clickable{cursor:pointer;}#mermaid-svg-evs22n32awTwRRCM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-evs22n32awTwRRCM .arrowheadPath{fill:#333333;}#mermaid-svg-evs22n32awTwRRCM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-evs22n32awTwRRCM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-evs22n32awTwRRCM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-evs22n32awTwRRCM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-evs22n32awTwRRCM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-evs22n32awTwRRCM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-evs22n32awTwRRCM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-evs22n32awTwRRCM .cluster text{fill:#333;}#mermaid-svg-evs22n32awTwRRCM .cluster span{color:#333;}#mermaid-svg-evs22n32awTwRRCM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-evs22n32awTwRRCM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-evs22n32awTwRRCM rect.text{fill:none;stroke-width:0;}#mermaid-svg-evs22n32awTwRRCM .icon-shape,#mermaid-svg-evs22n32awTwRRCM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-evs22n32awTwRRCM .icon-shape p,#mermaid-svg-evs22n32awTwRRCM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-evs22n32awTwRRCM .icon-shape .label rect,#mermaid-svg-evs22n32awTwRRCM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-evs22n32awTwRRCM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-evs22n32awTwRRCM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-evs22n32awTwRRCM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 组件创建

onMounted 挂载
组件更新

数据变化时
组件卸载

onUnmounted 销毁

6.3 副作用:要在「对的时候」做和清理

所谓「副作用」,就是「会改变外部世界的操作」------发请求、开定时器、加事件监听。这些操作有两条铁律:

  1. 该做的时候做 :比如「发请求拿数据」,要在组件挂载(onMounted)时做;
  2. 该清理的时候清理 :比如「定时器、监听器」,要在组件卸载(onUnmounted)时清掉,否则会内存泄漏
vue 复制代码
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'

const timer = ref(null)

onMounted(() => {
    // 挂载时:开个定时器
    timer.value = setInterval(() => console.log('tick'), 1000)
})

onUnmounted(() => {
    // 卸载时:清掉定时器(不清就会一直跑,内存泄漏)
    clearInterval(timer.value)
})
</script>

⚠️ 这是后端几乎不操心的点------后端请求结束,资源自动回收;前端组件卸载了,如果你不手动清理定时器/监听器,它们会一直赖在内存里
一句话 :前端组件有「生老病死」,你要学会「在挂载时启动、在卸载时清理」。尤其是定时器、监听器这类「副作用」,不清理就会内存泄漏。


七、一张总览图:后端思维 vs 前端思维

最后,把全文的转变浓缩成一张表,帮你把整个「思维迁移」刻进脑子:

维度 后端(你熟悉) 前端(Vue 视角)
世界观 请求驱动 状态驱动
核心命题 这个请求返回什么 当前状态显示什么
变量寿命 局部变量,随请求而生灭 响应式状态,随组件而生灭
数据流 请求 → Controller → Service → DAO 状态 → 视图(单向),事件 → 改状态
更新方式 重新执行流程 改状态,视图自动更新
传数据给后端 接收请求 发请求(GET query / POST body / 路径)
取后端数据 同步 return 结果 异步 await 拿数据
异步处理 几乎同步 Promise / async-await,处理 loading/空/错误
数据隔离 作用域 / 事务 / 线程局部 组件私有 + props/emit 通信
共享数据 数据库 / 缓存 状态提升 / Pinia
生命周期 框架托管 挂载 / 更新 / 卸载,手动清理副作用

#mermaid-svg-K3M3KVlJi8fZcLUW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K3M3KVlJi8fZcLUW .error-icon{fill:#552222;}#mermaid-svg-K3M3KVlJi8fZcLUW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K3M3KVlJi8fZcLUW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K3M3KVlJi8fZcLUW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K3M3KVlJi8fZcLUW .marker.cross{stroke:#333333;}#mermaid-svg-K3M3KVlJi8fZcLUW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K3M3KVlJi8fZcLUW p{margin:0;}#mermaid-svg-K3M3KVlJi8fZcLUW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster-label text{fill:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster-label span{color:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster-label span p{background-color:transparent;}#mermaid-svg-K3M3KVlJi8fZcLUW .label text,#mermaid-svg-K3M3KVlJi8fZcLUW span{fill:#333;color:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW .node rect,#mermaid-svg-K3M3KVlJi8fZcLUW .node circle,#mermaid-svg-K3M3KVlJi8fZcLUW .node ellipse,#mermaid-svg-K3M3KVlJi8fZcLUW .node polygon,#mermaid-svg-K3M3KVlJi8fZcLUW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K3M3KVlJi8fZcLUW .rough-node .label text,#mermaid-svg-K3M3KVlJi8fZcLUW .node .label text,#mermaid-svg-K3M3KVlJi8fZcLUW .image-shape .label,#mermaid-svg-K3M3KVlJi8fZcLUW .icon-shape .label{text-anchor:middle;}#mermaid-svg-K3M3KVlJi8fZcLUW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K3M3KVlJi8fZcLUW .rough-node .label,#mermaid-svg-K3M3KVlJi8fZcLUW .node .label,#mermaid-svg-K3M3KVlJi8fZcLUW .image-shape .label,#mermaid-svg-K3M3KVlJi8fZcLUW .icon-shape .label{text-align:center;}#mermaid-svg-K3M3KVlJi8fZcLUW .node.clickable{cursor:pointer;}#mermaid-svg-K3M3KVlJi8fZcLUW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K3M3KVlJi8fZcLUW .arrowheadPath{fill:#333333;}#mermaid-svg-K3M3KVlJi8fZcLUW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K3M3KVlJi8fZcLUW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K3M3KVlJi8fZcLUW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K3M3KVlJi8fZcLUW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K3M3KVlJi8fZcLUW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K3M3KVlJi8fZcLUW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster text{fill:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW .cluster span{color:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K3M3KVlJi8fZcLUW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K3M3KVlJi8fZcLUW rect.text{fill:none;stroke-width:0;}#mermaid-svg-K3M3KVlJi8fZcLUW .icon-shape,#mermaid-svg-K3M3KVlJi8fZcLUW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K3M3KVlJi8fZcLUW .icon-shape p,#mermaid-svg-K3M3KVlJi8fZcLUW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K3M3KVlJi8fZcLUW .icon-shape .label rect,#mermaid-svg-K3M3KVlJi8fZcLUW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K3M3KVlJi8fZcLUW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K3M3KVlJi8fZcLUW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K3M3KVlJi8fZcLUW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 前端:状态驱动
状态 state
视图自动渲染
事件
后端:请求驱动
请求进来
算结果
返回

一句话 :后端到前端的转变,本质是「从『过程』到『状态』」------不再问「这一步该怎么算」,而是问「这个状态,页面该长什么样」。抓住了这个,剩下的都是细节。


写在最后

我们从后端老手那句「前端又乱又绕」出发,把前端最核心的思维转变过了一遍。回顾这条主线:

  1. 最根本的转变:从「请求驱动」到「状态驱动」------视图是状态的函数,改状态视图自动变;
  2. 变量流转:变量是「响应式状态」,常驻页面、改了自动更新,数据流是单向的;
  3. 给后端传数据 :后端怎么收、前端就怎么发,但请求是异步的,要 await
  4. 处理后端数据:拿 JSON → 塞状态 → 视图更新,还要管 loading/空/错误三件套;
  5. 数据隔离 :组件是隔离单元,props 往下、emit 往上,共享数据靠状态提升或 Pinia;
  6. 生命周期:挂载时启动、卸载时清理,副作用不清理就内存泄漏。

最后送你一句话:

前端难,不是难在语法,而是难在「换一种看待数据的方式」。 你习惯了「一次请求算一个结果」,前端却要你「维护一堆状态,让视图永远和状态保持同步」。

但换个角度想,这也正是前端迷人的地方------你写的不是「流程」,而是一个「会自己跟随数据变化的活画面」。当你第一次看到「改了状态、页面自己就变了」的那一刻,你会突然理解:哦,原来这就是前端的「状态驱动」。

相关推荐
RD_daoyi16 分钟前
谷歌改写了76%的标题:超60字符的,95%会被谷歌自己重写
大数据·服务器·开发语言·前端·搜索引擎·html
IMPYLH24 分钟前
HTML 的 <map> 元素
前端·html
宿67439 分钟前
vue3-vite
前端·vue.js
风骏时光牛马43 分钟前
云原生驱动模型工具高效落地与规模化赋能
前端
zhanghaha131444 分钟前
HTML系列教程:8_HTML 文本格式化标签
前端·css·html
梦想的旅途21 小时前
基于企业微信API的微应用前端与后端架构设计
前端·状态模式·企业微信
烟漠河洛1 小时前
海外仓库存周转慢的破局点在于重构多仓数据同步架构
重构·状态模式
JavaGuide2 小时前
一个 SKILL.md 拿下 2.3 万 Star:让 Codex/Claude Code 少说废话、先给答案
前端·后端
晴天163 小时前
多个 VS Code 项目会导致 Chrome 和 Electron 应用一起卡住?-Day30
前端·chrome·electron