2006 字
约 6 分钟
10
S14-J07 · Vue 2/3 笔记合并:响应式边界、路由、状态与项目结构

适用读者:写过 Vue 但被"改了数据视图不更新"折磨过的人;要在两套 API 间做技术选择的人。
前置:S14-J06、B03(响应式入门)、J01(VueRouter)、B05(工程化)。
覆盖旧文:4400(Vue2 基础)、4401(Vue3)、4402(Vuex)、4403(工程化开发)、4406(路由)、4405(尚品汇)、4511/4513/4514(小兔鲜儿)、4512(CSS 高级,项目用)→ 11 篇 / 约 20 万字符。
扩展点:E-S14-023(三种 API 形态的实质差异:this、复用、类型)、E-S14-024(ref/reactive 的五个响应式边界)、E-S14-025(路由守卫执行顺序与权限落点)。
下一篇:S14-A03 · AI 时代 UI 生成方法论。


1. 一句话结论

Vue 的响应式只有一条规则:"渲染时读到的属性"才会被追踪,"没读到的"改了也不更新。所有"数据变了视图不动"的故障,都是这条规则的推论——reactive 解构、新增属性、数组下标、Object.freeze、跨组件传引用。

出处:A-4400/A-4401(两套 API)、A-4402(Vuex)、A-4403(工程化)、A-4406(路由)。


2. 三种 API 形态的实质差异(E-S14-023)

维度 Options API(Vue 2 起) Composition API(Vue 3) <script setup>(语法糖)
组织方式 按选项分组(data/methods/computed) 按逻辑分组(一个函数一件事) 同 Composition,但自动暴露
this 有,指向组件实例 没有 没有
逻辑复用 mixin(来源不明、命名冲突) 组合式函数(useXxx) 同
类型推导 弱(需 Vue.extend/装饰器) 好(TS 友好) 最好
适用 老项目维护 新项目、复杂逻辑 新项目默认
<!-- Vue 2 / Options -->
<script>
export default {
  data() { return { count: 0, list: [] } },
  computed: { double() { return this.count * 2 } },
  methods: { inc() { this.count++ } }
}
</script>

<!-- Vue 3 / script setup -->
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
const list = ref<string[]>([])
const double = computed(() => count.value * 2)
const inc = () => { count.value++ }
</script>

判据:"逻辑复用"才是 Composition 的真正卖点——同一个功能(分页、请求、表单校验)能收成一个函数并用参数化差异,而不是散落在 data/methods/watch 三处。


3. 五个响应式边界(E-S14-024)

// ① reactive 解构会丢响应性
const state = reactive({ n: 0 })
const { n } = state          // ❌ n 是普通数字快照
const n = toRef(state, 'n')  // ✅ 或直接用 state.n

// ② reactive 对原始类型无效
const s = reactive(0)        // ❌ 不报错但无响应
const r = ref(0)             // ✅

// ③ 整体替换 reactive 对象会断链
let state = reactive({ a: 1 })
state = reactive({ a: 2 })   // ❌ 模板还指着旧代理
Object.assign(state, { a: 2 })  // ✅

// ④ 数组按索引赋值在 Vue2 中不触发(Vue3 Proxy 已修复)
// Vue2: vm.$set(list, 0, x) / Vue3: list[0] = x 即可

// ⑤ Object.freeze / 非响应式数据源
const raw = Object.freeze({ a: 1 })   // 不会建立响应
边界 症状 修法
解构丢响应 改 state.n 视图不动 toRefs/toRef 或不解构
reactive(原始值) 静默失效 用 ref
替换整个 reactive 视图停留在旧值 Object.assign 或 ref 包对象
Vue2 索引/新增属性 视图不更新 $set / $delete(Vue3 无需)
ref 忘记 .value 模板里自动解包,JS 里必须 .value 统一约定

深层响应式:ref(obj) 会对对象内部做深层代理;若只要浅层,用 shallowRef(大对象/第三方实例的性能优化)。


4. 状态管理:Vuex 与 Pinia(4402 的比对)

维度 Vuex 4 Pinia
变更方式 必须 commit('mutation'),同步 直接改 store.count++
异步 dispatch('action') action 就是普通函数
模块 modules + namespaced 每个 store 独立定义
TS 支持 需额外类型体操 原生友好
devtools 支持 支持(时间旅行)
适用 老项目 Vue 3 新项目默认
// Pinia
export const useCart = defineStore('cart', {
  state: () => ({ items: [] }),
  getters: { count: (s) => s.items.length },
  actions: { add(item) { this.items.push(item) } }
})

判据:状态该不该进 store——跨组件共享 + 需要持久/可回溯才进;组件内状态留在组件里(避免 store 变成"全局变量袋")。


5. 路由:history vs hash、守卫顺序、权限落点(E-S14-025)

模式 URL 需要服务端配合 适用
createWebHistory /user/1 需要 fallback(try_files … /index.html) 生产推荐(见 J06 §3)
createWebHashHistory /#/user/1 不需要 静态托管、内嵌页面

守卫执行顺序(必背):

组件内 beforeRouteLeave → 全局 beforeEach → 路由独享 beforeEnter
→ 组件内 beforeRouteEnter → 全局 beforeResolve → 全局 afterEach
router.beforeEach(async (to) => {
  if (!to.meta.requiresAuth) return true
  const ok = await checkLogin()
  if (!ok) return { name: 'login', query: { redirect: to.fullPath } }   // 未登录回登录页
})
权限落点 做法 边界
路由级 meta.requiresAuth + 守卫 前端守卫不是安全边界(接口必须自己鉴权)
菜单级 按权限过滤路由表 动态 addRoute 后要 router.replace 一次
按钮级 指令/组件封装 v-perm 仅 UI 层,接口仍需校验

判据:前端权限只解决"看不看得见",接口鉴权才解决"能不能做"——把两者混为一谈是常见漏洞来源。


6. 三套项目结构的共性(尚品汇 / 小兔鲜儿 / 学成在线)

src/
├─ api/            按域拆模块,统一 request 封装(拦截器处理 token/错误码)
├─ assets/         图片、字体(可被构建处理)
├─ components/     通用组件(无业务依赖)
├─ composables/    useXxx(分页、请求、校验)
├─ layouts/        布局壳(头部/侧栏/内容区)
├─ router/         路由表 + 守卫
├─ stores/         Pinia 模块
├─ styles/         变量、reset、工具类
├─ utils/          纯函数
└─ views/          页面(对应路由)
共性 说明
页面 = 路由单元 views 与 router 一一对应,组件不放业务请求
请求集中 api/ + 统一实例,避免每页各自 fetch
通用组件与业务组件分离 业务组件放页面目录下,避免 components 膨胀
样式三件套 reset + 变量(design token)+ 工具类

7. 反例:六种"经典不更新/不生效"

写法 问题
const { list } = reactive({ list: [] }) 解构丢响应
ref 在 JS 里当数字用(忘 .value) 改的是先前的值
watch 监听 reactive 对象整体 需 deep: true 或监听具体字段
守卫里 next() 与 return 混用 双次执行/死循环
用 hash 模式却配了 history 的 fallback 无影响但配置误导
把权限判断只写在前端守卫 接口可被直接调用

8. 自测

  1. Options 与 Composition 的实质差异(三条)?
  2. ref 与 reactive 的边界?reactive 为什么不能装原始值?
  3. 解构丢响应怎么修?
  4. 路由守卫的完整顺序?前端权限的边界?
  5. Vuex 与 Pinia 的关键差别?
答案
  1. this 有无、复用机制(mixin vs composable)、类型推导。
  2. ref 可装任意值需 .value;reactive 只能对象/数组,因代理需 target 为对象。
  3. toRef/toRefs 或不解构。
  4. leave → beforeEach → beforeEnter → beforeRouteEnter → beforeResolve → afterEach;前端只管可见性。
  5. Vuex 强制 mutation、模块需 namespaced;Pinia 直接改 state、原生 TS。

9. 下一篇

S14-A03 · AI 时代 UI 生成方法论 —— 从形容词到规格书:视觉层级、留白与对齐、颜色与字体的预算、状态三件套、习惯用法与"AI 味"的来源。


本篇属于系列《S14 · 前端与可视化》。旧文来源:A-4400–4406、A-4405、A-4511–4514;扩展点 E-S14-023/024/025。库版本按原文写作时点,落地前需核对官方 release(标待核)。

S14-J07 · Vue 2/3 笔记合并:响应式边界、路由、状态与项目结构
https://www.clxhxhhr.top/posts/5383/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。