news 2026/9/19 11:33:02

从写Demo到落地中后台:Vue 3学习路线与工程化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从写Demo到落地中后台:Vue 3学习路线与工程化实践

从“照着文档能写出计数器”到“真拿到一个中后台项目就发懵”,这个断层我猜很多学 Vue 的人都经历过。后来我才想明白,问题不在于 API 背得少,而是把 Vue 当成了一个接受“点对点输入”的工具,没有把它放进真实工程里去理解边界。这篇内容不是再列一遍官方文档,而是记录我一路从十几个小 Demo、手写响应式系统、到最终落地多个 Vue 3 管理后台项目的完整过程;我会把每个阶段真正卡住我的问题、我当时找到的解法、以及事后看来的反思都讲清楚,希望能给正在这条路上“闯关”的人一点参考。

整个学习过程回过头看,其实是三个台阶:先会写实例,再理解框架内部机制,最后才学会用项目化的思维去组织代码。前两步决定了你能不能写出“能跑”的页面,第三步决定了你能不能写出“别人能接手、三个月后自己还能改得动”的系统。下面我按这条路线展开。

1. 定路线之前,先认清 Vue 学习路上最常见的三个断层

1.1 教程只会教你写“玩具代码”,真实业务从来不是这样

大多数入门实例都是计数器、待办清单、点击切换 tab。这类例子的共同点是:数据都在本地、操作是同步的、没有权限概念、不需要和后端对接口。可真实项目里的页面,几乎每个列表都伴随异步加载、loading 态、空数据态、失败重试,甚至还有“接口返回结构随时会被后端改”这种不可控因素。

我印象最深的一次是:我用 Vue 写了一个非常标准的“商品列表 + 搜索 + 分页”页面试图,业务逻辑看起来很完整,结果接到真实接口后,发现后端返回的是一个嵌套两层的数据结构,列表数据在data.list.records,总数在data.list.total,这在我本地 mock 时完全没考虑到。改起来不难,但如果是项目里几十个页面都存在这种“字段结构特化”,你就会开始意识到:写例子时追求的是语法正确,写项目时追求的是一层能容错的抽象

所以我的建议是:入门阶段可以跟着实例走,但每学完一个语法点,一定要主动问自己一个问题——如果这个数据来自接口,如果用户操作很快,如果请求失败,代码还能不能保持合理?把这三个“如果”加进你自己的 Demo 里,练习的价值会翻倍。

1.2 版本与写法割裂,旧资料会把你带进坑里

网上关于 Vue 的文章数量非常多,但相当一部分还停留在 Vue 2 的选项式 API 时代。新人在搜索“Vue 组件通讯”时,很容易看到一份用data(){ return {} }methods: {}computed: {}写的老代码,然后在自己 Vue 3 +<script setup>项目里怎么也对不上。

这里需要明确一点:<script setup>是 Vue 3 的推荐写法,它让组件逻辑更收敛,也天然适合组合式函数(composable)的复用;而选项式 API 在 Vue 3 里虽然还能用,但如果刚入门,我更建议直接以官方文档为准,只看 Vue 3 版本的讲解。判断一篇资料是否适合当前版本,最简单的办法是看它开头有没有注明“Vue 3.x”,以及代码里是否出现createApp而不是new Vue

版本割裂带来的另一个问题是依赖版本。vue-router有 3.x 和 4.x,pinia替代vuexcreate-vue替代@vue/cli作为官方脚手架。如果照着 2020 年以前的文章做环境配置,很可能会在依赖安装阶段就卡死。我自己的习惯是:先跑一遍官方npm create vue@latest生成的模板,再根据需求裁剪,不在“环境配置”这种没有技术含量的环节浪费时间。

1.3 框架只是拼图的一部分,工程化才是“项目化”的真正含义

“从实例到项目化”这句话听起来像是 Vue 本身的使用深度问题,但实际上,当你开始做真实项目时,需要掌握的东西会扩展成一张网:构建工具(Vite/Webpack)、路由、状态管理、代码规范、HTTP 请求封装、Mock、代码评审、部署流水线、甚至是和后端联调时的沟通方式。

Vue 只负责其中的“视图层 + 响应式状态”部分。如果只盯 Vue 而忽略其他环节,就会出现“单页面上手,一整套系统却搞不定”的尴尬。所以学习路线的设定应该考虑技术栈的完整度,而不是在一个点上无限深挖。我的顺序是:先通过实例熟悉 Vue 语法,再深入读响应式源码,再把路由、状态管理、接口层串起来做一个小中后台,最后才谈性能优化和架构设计。

2. 从实例起步:组件、响应式和“最小可运行”的边界感

2.1 别急着背 API,先让页面“动”起来

我最早接触 Vue 时,最容易犯的毛病是:读文档能读懂,关上文档什么都写不出来。后来发现解决这个问题的办法只有一个——跟着例子亲手敲一遍,并且改掉几个变量看看现象。比如最经典的“点击按钮切换列表显示状态”:

<script setup> import { ref } from 'vue' const showList = ref(true) const items = ref(['Vue Router', 'Pinia', 'Vite']) function toggleList() { showList.value = !showList.value } </script> <template> <button @click="toggleList"> {{ showList ? '隐藏列表' : '显示列表' }} </button> <ul v-if="showList"> <li v-for="(item, index) in items" :key="index">{{ item }}</li> </ul> </template>

这个例子虽然简单,但能解释一个非常核心的概念:数据驱动视图。页面里唯一可变的变量是showList,按钮只是修改这个变量的值,DOM 会自动跟着变。和 jQuery 时代“手动append()/remove()”的方式相比,Vue 把“改数据”和“改页面”解耦了,这正是前端框架存在的核心价值。

第一次写这种代码时,我建议你刻意观察一下:只要数据变化能引起视图变化,就说明响应式系统已经在工作。如果页面没变,优先检查是不是把ref包装后的值写成了showList = false,而不是showList.value = false。这个初级的坑,能让你很快建立起“包装对象”的概念雏形。

2.2 组件通讯:从 props / emit 到理解“单向数据流”

等到能熟练用v-ifv-forv-model写简单交互后,下一步就是组件化。组件化的本质是拆解功能边界,而组件通讯则是给这些边界“传话”。

Vue 里最基础的通讯方式就两种:父组件通过props向下传数据;子组件通过emit向上抛事件。同时还有一个规则叫单向数据流:数据只能从上往下传,子组件不能直接修改父组件的状态。这个规则初看很死板,但它是后期维护性的保障。如果父子组件能互相随意改数据,代码会很快失控,你根本不知道一个变量在哪个环节被谁改过。

下面简单展示一个父子通讯的例子:

<!-- Parent.vue --> <script setup> import { ref } from 'vue' import ChildItem from './ChildItem.vue' const keyword = ref('') function handleSearch(val) { keyword.value = val // 在这里发起真实搜索请求 } </script> <template> <ChildItem :keyword="keyword" @search="handleSearch" /> </template>
<!-- ChildItem.vue --> <script setup> const props = defineProps({ keyword: { type: String, default: '' } }) const emit = defineEmits(['search']) function handleInput(e) { emit('search', e.target.value) } </script> <template> <input :value="props.keyword" @input="handleInput" /> </template>

在这个结构里,keyword的“拥有者”始终是父组件。子组件只负责把用户输入往上抛,父组件决定这个值要存到哪里去。我第一次意识到这种设计的好处,是在接手一个遗留项目的时候:因为所有状态都有明确的“归属组件”,页面一出 bug 就能顺着数据流向快速定位,而不需要满项目搜变量赋值的代码。

2.3 插槽和 keep-alive:先理解适用场景,再决定要不要用

组件通讯解决完了,紧接着会遇到一类问题:某个组件大部分结构是相同的,只有局部内容需要每个页面自定义。这时就该用插槽(slot)。一个卡片组件就是典型场景:

<template> <div class="card"> <div class="card-header"> <slot name="title">默认标题</slot> </div> <div class="card-body"> <slot>默认内容</slot> </div> </div> </template>

插槽的作用不是“少写几行代码”,而是把“通用骨架”和“业务内容”分离开。这个抽象思想比插槽本身的写法重要得多。

keep-alive则是另一个很实用但容易误用的特性。它用来缓存组件实例,避免切换路由或切换 tab 时重复渲染、丢失状态。比如一个带有搜索条件的列表页,用户切到别的 tab 再切回来,通常希望搜索词和当前页码还在,这时keep-alive就很有用。但要注意:缓存意味着组件不会重新走 mounted 生命周期,因此在onActivated里刷新数据比在onMounted里更可靠。这个细节很容易被新手忽略,等遇到“数据不刷新”的问题时,第一反应不应该是怪框架,而是检查生命周期用错了。

3. 拆过响应式之后,才算真正打开 Vue 3 的大门

3.1 从“跟着用”到理解底层:手写一套极简响应式系统

学习 Vue 3,无论如何绕不开reactiverefeffectcomputed这几个 API。很多人用得很溜,却不明白ref的值为什么一定要带.valuecomputed为什么会缓存。我建议你拿出一个下午,用原生 Proxy 手写一个极简版响应式系统,做完之后很多困惑会瞬间解开。

核心思路只有两步:依赖收集触发更新。当读取某个响应式对象的属性时,把当前正在执行的“副作用函数”记录到这个属性的依赖列表里;当修改这个属性时,把依赖列表里的函数全部重新执行一遍。用代码表达是这样的:

let activeEffect = null const bucket = new WeakMap() function track(target, key) { if (!activeEffect) return let depsMap = bucket.get(target) if (!depsMap) bucket.set(target, (depsMap = new Map())) let deps = depsMap.get(key) if (!deps) depsMap.set(key, (deps = new Set())) deps.add(activeEffect) } function trigger(target, key) { const depsMap = bucket.get(target) if (!depsMap) return const deps = depsMap.get(key) deps && deps.forEach(fn => fn()) } function reactive(obj) { return new Proxy(obj, { get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) return true } }) } function effect(fn) { activeEffect = fn fn() activeEffect = null }

这段代码可以跑通最核心的链路:

const state = reactive({ count: 0 }) effect(() => { console.log('count is:', state.count) }) state.count = 1 // 控制台输出: // count is: 0 // count is: 1

第一行日志是effect执行时主动读取state.count触发的,第二行日志是赋新值时被trigger触发重新执行的效果。真实 Vue 源码还会处理嵌套对象、数组变化侦测、依赖清理、批量调度等大量细节,但核心骨架就是这个模型。搞清楚这一层,你再去看 Vue 官方文档里的“响应式原理”部分,会顺畅很多。

3.2 computed 的缓存逻辑和 ref 的包装套路

computed在极简响应式系统上只多了一层“缓存”逻辑:依赖的数据没变,就直接返回上一次计算结果,不再执行计算函数。为什么要有缓存?因为计算属性可能依赖多个响应式值,而且计算过程可能很重;如果没有缓存,每次读取都会重新计算,页面性能就会浪费在“计算没有被任何人改变的重复结果”上。

ref的实现其实也非常朴素:把一个普通值包装成一个带value属性的对象,然后用reactive处理这个对象。所以你会发现ref包裹对象时,内部会走reactive;这也是为什么ref({ count: 0 })也能实现深层响应式。至于“为什么一定要.value”,是因为 Proxy 只能代理对象,无法直接代理原始值(字符串、数字、布尔值),只能通过一层壳来保证读写都能被劫持。当你手写一遍后,这个“为什么”就不再是死记硬背的面试题,而是自然逻辑。

3.3 从手写响应式延伸出去:理解更新粒度和 nextTick

手写响应式还有一个隐藏收获,就是理解 Vue 的更新并不是“数据一改,立刻改 DOM”。真实环境中,同一事件循环里可能多次修改数据,如果每次都立刻更新视图,会造成大量重复渲染。Vue 的做法是把同一轮的更新任务收集起来,放到微任务队列中,等同步代码执行完再统一处理,这个机制就是nextTick的底层背景。

与之相关的一个经典“老坑”是:在 Vue 2 里,直接通过索引修改数组元素,页面可能不更新,因为Object.defineProperty侦测不到索引变化的。到 Vue 3 使用 Proxy 后,这个问题基本消失了,但“修改数据后想立刻拿到最新 DOM”的需求依然存在,这时你就需要用nextTick

import { nextTick } from 'vue' const list = ref([]) list.value = await fetchList() await nextTick() // 此时 DOM 已经完成更新,可以安全读取元素尺寸、滚动高度等

理解这个机制,比背 API 更能帮你预判“什么时候页面还停留在旧状态”。遇到这类问题,我会优先问自己:数据有没有真正变化?变化后触发的更新有没有被批量合并?我是不是在同一个同步过程中提前读取了 DOM?

4. 从“能跑”到“能维护”:项目化过程中必须补的课

4.1 没有约束的组件开发,再多组件也是一团乱麻

当我开始把几个页面组合成一个真实项目时,第一个冲击来自目录结构。写 Demo 时所有组件放在同一个文件里没有感觉,但一旦页面达到十几个、组件几十个,没有明确的目录规划,就是一个灾难现场。

我后来长期在使用的 Vue 3 项目目录结构是这样的:

src/ ├── api/ # 所有接口定义文件,按业务模块拆分 ├── assets/ # 静态资源 ├── components/ # 通用基础组件(按钮、弹窗、表格封装等) ├── composables/ # 组合式函数,比如 useTable、useAuth、usePagination ├── layouts/ # 布局组件,负责侧边菜单、顶栏、内容区结构 ├── router/ │ ├── routes.ts # 静态路由表 │ ├── guard.ts # 全局前置守卫 │ └── dynamic.ts # 根据权限动态添加的路由 ├── stores/ # Pinia 状态 ├── styles/ # 全局样式与变量 ├── types/ # TypeScript 类型定义 └── views/ # 页面级组件,按业务模块建文件夹

这个结构的核心思想是:按“职责”而不是按“页面”分类。接口归接口、状态归状态、页面归页面;组合式函数单独抽取出来,方便多页面复用。项目初期可能觉得多套了一层目录有点“重”,项目一复杂就能体会到,定位代码的时间会显著缩短。

4.2 路由不再只是“换页面”,而是权限和布局的契约

实例阶段的路由往往只是配一个pathcomponent,而真实项目里路由承担的职责要多很多:登录鉴权、页面标题、菜单生成、按钮权限、动态添加页面。

以中后台最常见的“权限路由”为例,我通常的做法是:先配置一套静态路由(登录页、404、无权限页),登录拿到用户角色和权限码之后,再根据权限表过滤路由配置,用router.addRoute()动态注册。同时在前置守卫里做统一的登录判断:

router.beforeEach((to) => { const userStore = useUserStore() if (!userStore.token && to.path !== '/login') { return { path: '/login', query: { redirect: to.fullPath } } } if (userStore.token && to.path === '/login') { return { path: '/' } } document.title = to.meta?.title ? `${to.meta.title} - 管理系统` : '管理系统' })

这套逻辑并不复杂,但把“哪些页面可见”“没登录去哪里”“打开页面的标题是什么”都封装在路由层,页面代码就能专心做业务。也有人把所有权限判断写在页面内部,短期内能跑,后续一旦权限模型调整,需要改的页面数量就会让人崩溃。

4.3 状态管理:从到处 emit,到用 store 统一汇总

随着项目变大,你会发现有些状态被很多不相关的组件共享,比如用户信息、当前选中的组织、全局缓存列表。Vue 的官方推荐方案现在是 Pinia,它比 Vuex 更轻量,TypeScript 支持也更好。一个典型的使用场景是登录状态:

import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', name: '', roles: [] }), actions: { async login(username, password) { const res = await loginApi({ username, password }) this.token = res.token this.name = res.name this.roles = res.roles localStorage.setItem('token', this.token) }, async logout() { this.token = '' this.name = '' this.roles = [] localStorage.removeItem('token') } } })

很多人问:什么时候该用 Pinia,什么时候不需要?我的判断标准是:当一个状态需要被两个及以上无直接父子关系的组件共享时,就应该放进 store。如果只是一个组件内部的临时状态,用ref就够了;强行把一切都塞进 store,反而会让全局变量泛滥,逻辑变得很难追。

4.4 接口层封装:和后端联调的第一道防线

项目化之后,HTTP 请求不能再在页面里到处fetch。统一封装请求层是收益最高的一件事,我会做三件事:创建 axios 实例、设置请求拦截器、设置响应拦截器。

import axios from 'axios' import { useUserStore } from '@/stores/user' import { ElMessage } from 'element-plus' const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) http.interceptors.request.use((config) => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }) http.interceptors.response.use( (res) => res.data, (err) => { if (err.response?.status === 401) { // 令牌失效,跳转登录页 window.location.href = '/login' } else { ElMessage.error(err.message || '请求失败') } return Promise.reject(err) } )

这样封装之后,页面里的请求代码只需要关心业务数据,不需要重复处理令牌注入和错误弹窗。同时也建议在项目初期就引入 Mock 方案,让前端不依赖后端也可以并行开发。很多团队用 vite-plugin-mock 或 MSW 都是不错的选择;关键是接口类型一旦和后端约定好,前端就能先按契约开发,而不是干等。

5. 踩坑与排障:播放器、地图、Debug 的真实案例

5.1 在 Vue 里播放 m3u8:视频流方案的选型与细节

视频直播/点播在前端项目中是个高频需求,尤其是 m3u8 格式的 HLS 流。在 Vue 中做这件事,最简单的方式是直接用 video.js,因为它自带 HLS 解析能力,不需要再额外处理格式逻辑:

import videojs from 'video.js' import 'video.js/dist/video-js.css' const playVideo = (videoElement, src) => { const player = videojs(videoElement, { sources: [{ src, type: 'application/x-mpegURL' }], controls: true, fluid: true }) return player }

如果你开发的是 uniapp 或移动端场景,uni-video直接支持网络视频流则更省事。而如果用原生video标签播放 m3u8 遇到兼容问题,可以考虑 hls.js 来处理流分段加载,再通过MediaSource喂给 video 标签。

这个需求常见的坑有三个:一是视频源跨域,播放器请求不到分片;二是自动播放策略,浏览器会禁止带声音的自动播放,需要设置 muted;三是组件销毁时没有释放播放器实例,导致内存和网络请求泄漏。最后一点我吃过亏,所以一定会在onUnmounted里调用player.dispose()

5.2 腾讯地图、WebRTC 与 Vue 的“生命周期错位”

地图类和音视频能力通常都是“原生外部对象”,不是 Vue 管理的 DOM,接入时最常遇到的冲突是生命周期错位。以腾讯地图为例,通常流程是:在onMounted里初始化地图实例,然后朝指定 DOM 容器里渲染;组件卸载时,需要把地图实例销毁。如果你在setup里直接初始化,此时 DOM 还不存在,就会得到一个空容器错误。

WebRTC 在 Vue 中的接入也类似:先getUserMedia拿到MediaStream,再把流塞到<video>标签的srcObject上;组件关闭时,要主动stream.getTracks().forEach(track => track.stop()),否则摄像头麦克风会一直被占用。核心思想是:外部 SDK 的生命周期必须挂在 Vue 组件生命周期上,不能靠浏览器默认回收

5.3 Vue 里的调试思维:从 console.log 到断点排查

很多人遇到代码不生效,第一反应就是疯狂打console.log。这没有错,但要讲究方式。我推荐的排查顺序是:

  1. 先用 Vue Devtools 看数据是否正确。如果ref的值不对,问题在数据来源;如果值对了但页面没变,问题在模板绑定或渲染逻辑。
  2. 再看 Network 面板验证接口返回。很多时候“页面 Bug”其实是后端返回了意外数据结构。
  3. 最后才用断点,在具体的事件处理函数里逐步排查。

一个常见现象是:修改了ref的值,控制台打印也变了,页面就是不更新。这时要检查三件事:是不是把ref当作普通属性直接赋值了;是不是在模板里用了v-for但没有写key,导致复用了错误的 DOM;是不是数据变化发生在setTimeout或异步回调里,而组件已经被缓存或销毁了。

Vue Devtools 是我觉得最有价值的工具,它把每个组件的 props、state、computed 都摊开,你能直观看到响应式依赖是怎么一层层传下去的。学会用它,比背二十个 API 实在得多。

5.4 开源后台方案(比如 vben)应该怎么用

提到 Vue 中后台项目,绕不开像 vben 这样的高质量开源前端框架。很多人看到这类项目第一反应是:里面功能好全,直接复制过来改吧。我的经验是:不要着急复制,先把它的目录结构、组件封装思路、权限模型、请求层设计通读一遍

vben 这类项目真正有价值的地方,不是它能跑通登录,而是它呈现了一整套“行业级约定”:接口层怎么抽、鉴权怎么拆、路由怎么和菜单联动、组件怎么设计才能保持一致性。对照自己的项目,把它的思路挑出来用到适合你的地方;如果你照搬整个项目,后续升级依赖、修改样式、适配自己团队的规范,成本会大到让你怀疑人生。

5.5 从开源项目里偷师,而不是复制粘贴

另外,关于开源项目还有一个收获:它们通常对 TypeScript 的支持非常好。早期我为了省事全部写 JavaScript,后来在改造一个旧项目时慢慢补类型,才发现类型系统对“重构”的重要性。Vue 3 + TypeScript 的组合,最明显的好处是改数据结构和接口字段时,编译期就能告诉你哪些地方需要同步修改;这在多人协作的项目里几乎是救命级别的功能。

如果你还没有用过 TypeScript,我建议在独立的小 Demo 里先跑通defineProps<{ item: IProduct }>()这种写法,再逐步扩展到项目。不要一步到位追求所有文件都严格类型化,可以从 api 层的接口类型开始。

6. 面试怎么问,学习就怎么补:一些常被考察的 Vue 知识点

6.1 高频考点,其实都是项目中真实面对过的选择

“为什么 Vue 3 用 Proxy 替换 Object.defineProperty?”“nextTick 的作用是什么?”“key 在 v-for 里到底有什么用?”“路由懒加载怎么做?”这些面试题如果孤立地去背,会很痛苦;但如果你带着真实项目经验去审视,会发现答案都是很自然的设计结论。

我整理过一张对照表,用“面试题”和“我在项目里的真实场景”把知识点串起来:

面试高频问题项目中的真实对应
响应式原理为什么某个数据改了,页面自动更新;为什么用ref包原始值时必须.value
nextTick数据更新后立刻操作 DOM,比如通过v-if切换元素后测量高度
组件通讯方式父子用 props/emit,跨层级用 provide/inject,全局共享用 Pinia
路由守卫未登录跳转、页面标题设置、动态权限路由注册
性能优化大数据量列表用虚拟滚动、长列表加key、防止不必要的 reactive 深层代理
v-model 原理本质是 value + input 事件的语法糖,自定义组件也能用它
虚拟 DOM每次数据更新不是直接操作 DOM,而是先 diff 再批量更新

这种“以项目见原理”的方式,是我认为最高效的学习路径。面试官真正想听的,也不是背诵定义,而是你能不能在具体业务里解释“为什么这样设计”。

6.2 顺带聊聊 Vue 和 React 的差异

学 Vue 到一定阶段的人,几乎都会遇到“Vue 和 React 到底怎么选”的讨论。以我两边都轻度使用的感受来说,核心差异主要在三点:

  • 更新模型:Vue 的响应式系统可以精确到“哪个组件依赖了哪个状态”,状态变了直接触发对应组件更新;React 则通常从上往下重新渲染,再通过 memo 等方式控制优化。
  • 逻辑组织:Vue 3 的组合式 API 和 React Hooks 都在解决“逻辑复用”问题,但 Vue 的组合式函数是在setup上下文中执行的,不需要像 Hooks 那样严格遵守调用顺序规则,心智负担相对低一些。
  • 学习曲线:Vue 模板语法更接近传统 HTML,新手容易上手;React 则更强调 JavaScript 表达能力,写多了之后会反过来影响你对组件抽象的理解。

我的态度是:不存在绝对的优劣,关键看团队的现有技术栈和项目类型。Vue 在中小团队、后台管理类场景中效率很高;React 的生态和灵活性同样有大量应用场景。两边都动手写两个 Demo,比在网上看谁说服谁更有价值。

7. 关于“闯关”这件事,最后再分享几句

把 Vue 从实例学到项目化,最大的体会是:能力和认知是交替突破的,不是线性的。你可能花了两天写了一个完美运行的组件,却在接手项目时被目录结构、环境变量、权限模型各种细节打得措手不及。这很正常,说明你已经从写“单个功能的视角”切换到“维护系统的视角”,而这个视角切换往往需要靠一段时间的真实项目浸泡才能完成。

如果你正处在“实例都会,项目不会”的阶段,我的建议是:找一个不算太大但有完整业务链的中后台项目模板(比如联系人管理、工单系统、内容管理),自己从头把它实现一遍。过程中不用追求把所有功能都写得多华丽,但要强迫自己和真实场景对抗——接口延迟、按钮重复提交、表格大数据量、路由刷新丢状态,这些才是“项目化”路上的真正怪。

等你把第一版磕磕绊绊写完之后,再回头看同一个项目,大概率会觉得这里能拆个 composable,那里应该加个类型,路由守卫还能再收敛一点。这种“看不下去旧代码”的感觉,就是你把 Vue 内化成自己能力的最好证明。闯关没有终点,能不断从自己的项目里找出问题并重构,就已经走在一条很靠谱的路上了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 11:32:16

MCP协议与Serverless融合:重构企业AI工具调度架构

简介&#xff1a;在AI Agent大规模落地企业场景的进程中&#xff0c;模型能力本身往往不是瓶颈&#xff0c;真正决定智能化上限的&#xff0c;是模型能否稳定、安全地调用内部工具与服务。MCP&#xff08;Model Context Protocol&#xff09;正是为解决这一痛点而生的标准化协议…

作者头像 李华
网站建设 2026/9/19 11:31:11

火场监控去烟:残差检测与Dense U-Net串联网络复现

简介&#xff1a;这份PDF文献面向图像处理、计算机视觉方向的研究者与消防救援技术相关人员&#xff0c;聚焦火场灰度图像中烟雾干扰导致监控画面模糊、对比度下降的问题&#xff0c;提出一套基于深度学习的去烟算法方案。资源为单篇学术论文&#xff0c;压缩包内仅含1个PDF文件…

作者头像 李华
网站建设 2026/9/19 11:31:07

Flutter for OpenHarmony网络层封装与dio实战

1. Flutter for OpenHarmony 网络层封装实战在跨平台开发领域&#xff0c;Flutter for OpenHarmony 作为华为推出的创新解决方案&#xff0c;为开发者提供了全新的技术可能性。作为一名长期深耕 Flutter 生态的开发者&#xff0c;我在实际项目中深刻体会到网络层封装的重要性。…

作者头像 李华
网站建设 2026/9/19 11:30:56

OpenClaw部署避坑实录:本地折腾不如云服务器稳定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 11:30:54

STM32CubeIDE历史版本合集:工程兼容性与版本选择避坑指南

1. 手头这个版本合集的由来&#xff1a;为什么嵌入式开发者需要一份“版本仓库”先说一个我自己的真实经历。去年年中&#xff0c;我接手一个已经量产的传感器项目&#xff0c;用的是一年前发布的STM32CubeIDE版本&#xff0c;工程里配好了全套外设初始化代码&#xff0c;HAL库…

作者头像 李华