在构建 AI 聊天界面、实时日志展示等场景中,“流式输出”(Streaming Output)是提升用户体验的关键。而 Vue 3 凭借其轻量、高效的响应式系统,成为实现此类交互的理想前端框架。本文将带你从零搭建一个 Vue 3 项目,并深入理解其核心机制——响应式数据如何自动驱动视图更新。
一、项目基建:为什么用 Vite?
✅ Vite 是什么?
Vite(法语“快”)是由 Vue 作者尤雨溪开发的新一代前端构建工具。它利用 原生 ES 模块(ESM) 和 按需编译 技术,彻底改变了传统打包工具(如 Webpack)的开发体验。
🚀 核心优势
| 特性 | 说明 |
|---|---|
| 极速冷启动 | 无需打包整个应用,直接通过浏览器原生 ESM 加载模块 |
| 热更新(HMR)极快 | 修改代码后,仅更新受影响组件,毫秒级反馈 |
| 开箱即用 | 内置对 Vue、React、TypeScript、CSS 预处理器的支持 |
🔧 初始化项目命令
bash
编辑
npm init vite@latest my-streaming-app -- --template vue
或交互式选择:
bash
编辑
npm init vite
# → 项目名:my-streaming-app
# → 框架:Vue
# → 变体:JavaScript
生成的项目结构:
text
编辑
my-streaming-app/
├── src/
│ ├── App.vue ← 根组件(入口)
│ └── main.js ← 应用挂载点
├── index.html ← HTML 入口
└── package.json
💡
src/是你未来 90% 开发工作的主战场。
二、Vue 单文件组件(SFC):三明治架构
每个 .vue 文件都是一个独立、可复用的 UI 单元,包含三个部分:
| 部分 | 作用 | 示例 |
|---|---|---|
<script setup> | 组件逻辑(数据、方法、导入) | import { ref } from 'vue' |
<template> | 视图结构(HTML + Vue 指令) | {{ count }} |
<style scoped> | 局部样式(scoped 避免污染) | .container { padding: 16px; } |
📌 App.vue 是根组件,最终会被
main.js挂载到index.html的<div id="app">上。
三、核心突破:从“命令式”到“声明式”编程
❌ 传统 JavaScript(命令式)
你要手动控制每一步 DOM 操作:
js
编辑
let count = 0;
button.onclick = () => {
count++;
document.querySelector('.count').innerText = count; // 手动更新 DOM
};
问题:逻辑与 DOM 强耦合,难以维护,易出错。
✅ Vue 响应式(声明式)
你只关心 “数据是什么” ,不关心 “怎么更新 DOM” :
vue
编辑
<script setup>
import { ref } from 'vue';
let count = ref(0); // 响应式数据
const increment = () => count.value++; // 修改数据
</script>
<template>
<div>{{ count }}</div> <!-- 自动更新! -->
<button @click="increment">+1</button>
</template>
结果:数据变 → 视图自动变,Vue 替你操作 DOM。
四、深入 ref:Vue 3 响应式基石
🔑 ref 是什么?
- 用于将基本类型(number, string, boolean)包装成响应式对象。
- 返回一个
{ value: ... }结构的对象(称为 RefImpl 实例)。
📜 使用规范
js
编辑
import { ref } from 'vue';
// 1. 创建响应式数据
const count = ref(111); // 不是数字,是 { value: 111 }
// 2. 读取/修改必须通过 .value
console.log(count.value); // 111
count.value = 222; // 触发响应式更新
// 3. 在模板中直接写 {{ count }},Vue 自动解包 .value
⚠️ 为什么需要 .value?
- JavaScript 中基本类型是值传递,无法被 Proxy 监听。
ref将其包装为对象,Vue 通过监听.value的get/set实现响应式。
🌟 模板中无需
.value:Vue 编译器会自动处理,这是语法糖!
五、代码逐行解析(你的 Demo)
vue
编辑
<script setup>
// 1. 按需导入 ref(ES6 解构)
import { ref } from 'vue';
// 2. 创建响应式变量(初始值 111)
let count = ref(111);
// 3. 打印查看结构:RefImpl { value: 111 }
console.log(count);
// 4. 2秒后修改值 → 视图自动更新为 222
setTimeout(() => {
count.value = 222;
}, 2000);
</script>
<template>
<div class="container">
<!-- 插值表达式:自动显示 count.value -->
{{ count }}
<label> 输入:</label>
</div>
</template>
<style scoped>
/* scoped:样式仅作用于当前组件 */
</style>
✅ 效果:页面先显示 111,2 秒后自动变为 222,无任何 DOM 操作代码。
六、关键概念速记表
| 概念 | 一句话总结 |
|---|---|
| Vite | 极速初始化 Vue 项目的现代构建工具,src/ 是开发主目录 |
| 单文件组件 (.vue) | script(逻辑)+ template(视图)+ style(样式)三位一体 |
| 响应式 | 数据驱动视图,Vue 自动同步,告别手动 DOM 操作 |
ref | 包装基本类型为响应式对象,修改需用 .value,模板中自动解包 |
<script setup> | Vue 3 组合式 API 语法糖,省去 export default,更简洁 |
{{ }} | 模板插值语法,用于消费响应式数据 |
scoped | 样式局部作用域,避免组件间样式冲突 |
七、拓展思考:为流式输出做准备
虽然当前 Demo 只是静态更新,但响应式机制正是流式输出的基础。
未来你可以这样扩展:
js
编辑
// 模拟流式接收数据(如 SSE / WebSocket)
const message = ref('');
// 每收到一个 chunk,追加到 message
socket.onmessage = (chunk) => {
message.value += chunk.data; // 视图自动逐字更新!
};
✅ 利用
ref的响应式特性,每次追加文本都会触发局部重渲染,实现“打字机”效果。
八、注意事项 & 最佳实践
- 基本类型用
ref,对象/数组优先用reactive(但ref更通用,可跨组件传递)。 - 不要直接给
ref赋原始值:count = 222会破坏响应式,必须count.value = 222。 - 模板中永远不要写
.value:这是常见新手错误。 <script setup>是 Vue 3 推荐写法,比 Options API 更灵活,适合组合逻辑。