资讯动态

const在Vue开发中的深度实践:从ES6到组件状态管理的正确用法

发布时间:2026/10/9 17:23:34 来源:尧图企业网站定制
1. 理解const的本质——从JavaScript到Vue的桥梁1.1 const的声明规则与块级作用域很多刚接触Vue的开发者在写到生命周期、路由配置或者组件状态时经常会遇到const这个关键字。它看起来很简单就是“声明一个常量”但实际在Vue项目中const的用法远不止“固定一个值”这么简单。我见过太多因为const误用导致的诡异bug比如数据没反应、组件不渲染、ESLint直接报错。所以这一篇我想从Vue实际开发的角度把const彻底讲透。先回归JavaScript基础。const是在ES6引入的用来声明一个只读的常量引用。它的核心规则有三条必须在声明时初始化不能先声明后赋值。声明的是“引用不可变”而不是“值不可变”。对于基本类型number、string、boolean等赋值后确实不能改变但对于对象、数组、函数等引用类型实际上可以通过修改属性或元素来改变内容。拥有块级作用域只在当前代码块{}内有效不会提升到函数或全局顶部。举个例子const name vue; name react; // 报错Assignment to constant variable. const user { login: admin }; user.login user; // 正常因为修改的是对象属性 user {}; // 报错Assignment to constant variable.很多新手在这里犯迷糊为什么user.login能改但user {}不行因为user变量存储的是对象的引用地址user.login user是修改地址指向的那个对象引用本身没变而user {}等于让变量重新指向一个新对象引用变了所以const不允许。在Vue中这种“引用不变但内容可变”的特性非常有价值。比如一个组件实例的data函数返回的对象你肯定不希望有人把它整体替换掉但又允许内部属性随用户操作而变化。用const声明恰恰能表达这种意图这个引用是固定的它的“内容”是响应式可变的。1.2 为什么Vue开发中要首选constVue的响应式系统依赖对变量引用和值的追踪。如果你用var声明变量一不小心就可能造成全局污染或者变量提升导致渲染结果不可预期。而let虽然解决了块级作用域但允许重新赋值这在团队协作中会增加代码审查的负担——别人看到let就要考虑这个变量在后续逻辑里是否被改过。const的好处是一旦赋值引用锁定代码的可预测性大大增强。尤其是在编写Vue组件时你希望组件状态一旦初始化某些核心引用不要被意外替换。比如export default { data() { const initialForm { name: , age: 0 }; return initialForm; } }如果你用let initialForm有可能在后续某个方法里不小心执行initialForm {}这样data对象就变成了全新的对象Vue的响应式依赖关系就会丢失视图不再更新。而用const这种风险从语法层面就被杜绝了。所以现代Vue项目里几乎所有的默认值、配置对象、枚举常量都推荐用const声明。另外Vue的模板编译和渲染函数本身也大量依赖const来保存中间计算结果。比如在render函数中你会看到render(h) { const { title, content } this; return h(div, [h(h1, title), h(p, content)]); }这里的解构赋值用const就是告诉阅读代码的人这些值在本作用域内不会重新指向别的地方帮助维护者快速建立心智模型。2. const在Vue组件中的核心应用场景2.1 定义常量配置与枚举值在Vue业务开发中最常遇到的就是要把一些不会变的配置抽出来。比如状态码、权限标识、路由名称、菜单类型等。这些值如果硬编码在组件里后期维护就是灾难。正确的做法是单独建一个常量文件用const定义所有固定值。我习惯在项目src目录下建一个constants文件夹里面按模块拆分。比如一个订单模块// src/constants/order.js export const ORDER_STATUS { PENDING: 0, PAID: 1, SHIPPED: 2, COMPLETED: 3, CANCELLED: 4 }; export const ORDER_STATUS_TEXT { [ORDER_STATUS.PENDING]: 待支付, [ORDER_STATUS.PAID]: 已支付, [ORDER_STATUS.SHIPPED]: 已发货, [ORDER_STATUS.COMPLETED]: 已完成, [ORDER_STATUS.CANCELLED]: 已取消 }; export const ORDER_PAGE_SIZE 20;在组件中使用时import { ORDER_STATUS, ORDER_STATUS_TEXT } from /constants/order; export default { data() { return { currentOrderStatus: ORDER_STATUS.PENDING }; }, computed: { statusText() { return ORDER_STATUS_TEXT[this.currentOrderStatus]; } } }为什么用const而不是Object.freeze因为const锁定的是变量引用而Object.freeze会冻结对象本身使其属性不可修改。但在Vue中如果你把Object.freeze的对象作为响应式数据Vue会跳过它的响应式转换这反而可能导致某些场景下无法追踪变化。所以常规做法就是const定义并导出内部属性保持可读配合ESLint规则禁止修改常量文件里的属性。另一个常见场景是定义路由表。在Vue Router中路由配置通常是个数组你肯定不希望路由表在运行过程中被整体替换。所以// src/router/index.js const routes [ { path: /, name: Home, component: Home }, { path: /about, name: About, component: About } ]; const router createRouter({ history: createWebHistory(), routes });这里用const声明routes和router既防止外部意外重置也保证了路由表的稳定引用。2.2 在模板、计算属性、方法中正确使用const在Vue模板中const不能直接写在模板表达式里因为模板本身是一个字符串解析上下文不是JavaScript块。但是你可以通过计算属性、方法或者组件实例属性来间接引用常量。比如模板里想要根据状态显示不同样式div :classgetStatusClass(currentStatus)订单状态/div对应的方法methods: { getStatusClass(status) { const classMap { 0: status-pending, 1: status-paid, 2: status-shipped }; return classMap[status] || status-default; } }这里的classMap用const因为它的映射关系是固定不变的这样每次调用方法时都会重新创建这同一个对象引用虽然每次创建新对象但引用在本作用域内不重指向性能上没问题代码语义也更清晰。在计算属性中使用const最典型的是做数据处理computed: { filteredList() { const { searchText, list } this; if (!searchText) return list; return list.filter(item item.name.includes(searchText)); } }searchText和list通过解构赋值用const固定代表本次计算中它们不会被重新赋值这样Vue在依赖追踪时也不会产生混乱。需要注意的一个细节在methods中如果方法内部需要临时变量且后续要重新赋值那就用let。比如循环计数、累积值等。const只适用于那些定义后就不改引用的场景。我见过有人无脑全用const结果循环里写了个const index然后执行index直接报错。所以要分清“不应变”和“暂时用一下”。2.3 在Vuex/Pinia状态管理中const的使用细节状态管理是Vue项目里const出镜率最高的地方。以Vuex为例定义mutations时通常需要一个常量类型来表示动作名称这样能避免字符串拼写错误也方便全局搜索。// src/store/mutation-types.js export const SET_USER_INFO SET_USER_INFO; export const LOGOUT LOGOUT;然后在store中使用import { SET_USER_INFO, LOGOUT } from ./mutation-types; export default { mutations: { [SET_USER_INFO](state, userInfo) { state.userInfo userInfo; }, [LOGOUT](state) { state.userInfo null; } } }这里SET_USER_INFO和LOGOUT用const声明并导出意味着它们在整个应用生命周期中引用固定不会因为任何操作被重新指向。如果在某个组件里不小心写了SET_USER_INFO OTHER就会立刻在编译期收到错误这就是防护价值。对于Pinia定义store时也推荐用constimport { defineStore } from pinia; export const useUserStore defineStore(user, { state: () ({ name: admin, token: }), actions: { login(token) { this.token token; } } });useUserStore是一个函数它在模块导入时就被定义这个函数引用必须固定否则其他组件如果重新赋值就会导致同一个store被多次创建状态无法共享。所以这里const是必须的。在Pinia的store内部定义的getter或action中我也经常用const做局部固定值actions: { async fetchUser() { const response await fetch(/api/user); const userData await response.json(); this.user userData; } }response和userData都是只使用一次的中间变量用const声明意图明确它们只是临时接收数据后续不需要重新指向别处。3. const、let、var三者的差异与Vue中的最佳实践3.1 作用域、提升、可变性的对比很多Vue面试题会问“const和let有什么区别”但放到真实项目里最大的区别是“能不能重新赋值”。先看一张对比表特性varletconst块级作用域否函数级是是变量提升是初始化为undefined是但进入暂时性死区是但进入暂时性死区重新赋值可以可以不可以声明时初始化否否必须同名重复声明可以不可以不可以在Vue开发中因为每个组件都是独立的模块var几乎没有任何存在意义。var的函数级作用域容易导致变量泄漏到全局尤其在使用Vue Router、Pinia等全局状态时可能引起跨组件污染。let和const的区别就一个能不能重新赋值。但这一个区别就足以让团队在代码审查时快速判断变量的生命周期。我自己的经验是在Vue项目中默认全部用const只有极少数情况下需要重新赋值的变量才用let。比如循环计数器for (let i 0; i 10; i)需要累加的结果值let total 0; total item.price;条件分支里要重新赋值的中间状态let message ; if (error) message 失败;除此之外一律用const。这个规则听起来简单但实操中很多人做不到因为懒或者没意识到风险。3.2 Vue项目中的变量声明规范与代码检查为了让团队代码风格统一强烈建议在Vue项目的ESLint配置里开启相关规则。如果你的项目是Vue CLI创建的标准模板默认已经配置了prefer-const规则。提示prefer-const是ESLint核心内置规则它会自动检测哪些变量没有重新赋值但用了let然后给出警告或错误。开启后你几乎不用手动提醒同事。在.eslintrc.js或eslint.config.js中module.exports { rules: { prefer-const: [error, { destructuring: any, // 解构赋值中只要有一处重新赋值就允许用let ignoreReadBeforeAssign: false }] } };一般Vue CLI项目是vue/cli-plugin-eslint里面默认配置了prefer-const: [error, { destructuring: any }]。这样的好处是如果解构赋值时某个属性需要重新赋值则整个解构可以用let否则强制const。另一个常见配置是no-const-assign这个规则专门检查是否对const变量重新赋值如果出现就会报错防止运行时崩溃。默认也是开启的。在Vue项目里还有一个容易被忽略的地方data()函数返回的对象属性往往被误以为要用const声明。但实际上data()返回的对象是组件实例的属性它的属性值是可以变化的用const声明那个返回的对象变量只是为了固定引用而不是固定属性。比如data() { const form { name: , age: 0 }; return form; }这里form用const没问题因为form这个变量在data函数内部只负责构造并返回不会重新赋值。如果后续组件方法里要修改this.name或this.age那是响应式系统的事和form的声明方式无关。4. 实战重构一个Vue组件中的变量声明4.1 初始代码与问题分析假设我们有一个商品列表组件老代码写着export default { data() { let a 10; let b 20; return { count: a b, list: [] }; }, methods: { load() { let url /api/goods; let params { page: 1, size: 20 }; let response fetch(url, params); let data response.data; this.list data.goodsList; this.count data.total; } } }这段代码的问题很明显a和b在data里从未重新赋值用let语义不清晰。url、params、response、data都是只读用的临时值用let没有意义。如果后续有人在某个方法里不小心写了url /otherESLint不会报错但可能导致状态混乱。另外data函数中的count初始值用a b计算完全可以用const声明局部变量避免它们暴露在外。4.2 重构步骤与对比效果重构后的代码export default { data() { const INIT_COUNT 10; // 固定初始值 const STEP 20; return { count: INIT_COUNT STEP, list: [] }; }, methods: { async load() { const url /api/goods; const params { page: 1, size: 20 }; try { const response await fetch(url, { method: POST, body: JSON.stringify(params) }); const data await response.json(); this.list data.goodsList; this.count data.total; } catch (e) { console.error(商品加载失败, e); } } } }这个版本把所有不需要重新赋值的变量都用const。区别在哪读代码的人一眼就知道url、params、response、data这些中间变量永远不会被重定向逻辑链路更清晰。更重要的是如果团队里有人想“优化”某个变量他必须先考虑这个变量能不能被重赋值这无形中减少了bug。还有一个细节INIT_COUNT和STEP用大写命名这是一种约定俗成表示它们是常量。在Vue组件内如果你有真正的常量与实例无关、不依赖this建议放在模块顶层或单独的常量文件而不是放在data里因为data里的数据会被Vue响应式化不必要的响应式追踪会浪费性能。比如// 放在组件模块顶层 const INIT_COUNT 10; const STEP 20; export default { data() { return { count: INIT_COUNT STEP, list: [] }; } }这样INIT_COUNT和STEP不参与响应式系统每次组件实例化时只是读取它们的值性能更好。这就是const在Vue中配合模块化思维的正确姿势。5. 常见问题与排查技巧实录5.1 const定义的数组或对象为什么能修改这是面试高频题也是实际开发中很多人疑惑的“既然const是常量为什么const arr [1,2,3]; arr.push(4)能成功”答案前面说过const锁定的是“绑定”不是“值”。数组和对象的内容是“值”的一部分修改内容不算重新绑定。但在Vue中这个特性常常带来一个陷阱。比如你用const定义了一个数组然后把它作为组件的data属性data() { const arr [1, 2, 3]; return { items: arr }; }如果你在某个方法里直接执行this.items.push(4)Vue能检测到变化页面更新。但如果你执行this.items arr假设arr是常量你只能让它指向同一个数组所以这种操作本身没问题。真正的坑在于如果你用const定义了一个数组然后在外部用const newArr this.items接下来newArr.push(5)因为两个变量指向同一个数组所以也会改变items。这不算bug但你需要明白“引用共享”的概念。排查技巧如果你发现const数组内容被意外修改先检查是否有其他变量引用了同一个数组。使用JSON.parse(JSON.stringify())深拷贝或者展开运算符[...arr]来创建独立副本可以避免这种共享问题。5.2 在Vue生命周期中const的作用域陷阱一个常见的错误是在生命周期里定义const变量然后在后续生命周期里试图访问它。比如created() { const timer setInterval(() { console.log(tick); }, 1000); }, beforeDestroy() { clearInterval(timer); // 报错timer is not defined }这里timer在created函数内是局部存在beforeDestroy根本访问不到。用const只是让它在该作用域内固定并不能跨生命周期传递。正确做法是把timer定义在组件实例上created() { this.timer setInterval(() { console.log(tick); }, 1000); }, beforeDestroy() { clearInterval(this.timer); }这种情况下this.timer是实例属性赋值用和const无关。但这个例子很重要很多人误以为const能定义全局常量其实它只能锁定“当前作用域”的变量绑定。在Vue生命周期中如果你想在多个钩子间共享必须把变量挂到this上或用模块级const。5.3 ESLint配置const相关的规则实际项目中ESLint的no-use-before-define规则也会影响const的使用。因为const有暂时性死区如果在声明之前使用某个const变量ESLint可能会警告或报错。比如console.log(A); const A 1;这会在运行时抛ReferenceErrorESLint的no-use-before-define会提前抓住它。在Vue组件中如果你在一个方法里引用了一个在模块顶层定义的const常量但该常量在组件定义之后才声明也可能触发规则。所以最好把所有模块级常量放在文件顶部。另外vue/no-side-effects-in-computed-properties规则虽然和const无直接关系但它会检查计算属性里是否对const变量重新赋值。如果你在计算属性里写this.xxx something而这个xxx恰好是const定义导出的就会报错。虽然计算属性内本来不该有副作用但这类规则能帮你在编码阶段就发现问题。5.4 速查表const在Vue中的常见坑与解决场景坑解决组件data中定义数组/对象内容可以被修改容易误以为常量不可变明确“引用固定”概念需要时用深拷贝/展开运算符生命周期间共享值const局部变量无法跨生命周期访问挂到this上或使用模块级const导出Vuex mutations类型字符串拼写错误导致匹配失败用const定义mutation type并统一导出Pinia store的定义重新赋值store变量导致状态丢失强制使用const声明store实例模板内使用常量模板无法直接识别const变量通过计算属性或方法返回常量值循环中试图改变迭代值for (const i...)直接报错循环计数器用let其他临时变量用const我个人在实战中还有一个习惯在data()函数里如果某个变量不参与响应式但又要被模板使用我会用computed代替。因为data里的任何属性都会被Vue加入响应式系统即使你不修改它也会增加代理开销。比如const PI 3.14159; export default { data() { return { radius: 10 }; }, computed: { area() { return PI * this.radius * this.radius; } } }这里PI是模块级常量area是计算属性它依赖响应式的radius但PI本身不参与响应式。这样的结构既清晰又高效。最后再分享一个Vue 3的自定义指令场景。如果你写了一个自定义指令想在指令生命周期里定义一个只读的配置对象const vFocus { mounted: (el) { const defaultColor #333; el.style.color defaultColor; } };defaultColor用const确保在指令钩子内部不会被意外重写这在封装公共库时尤其重要——库的代码质量直接影响所有使用者的稳定性。关于const在Vue中的应用我也只是把日常踩过的坑和总结的经验梳理了一遍。其实归根到底就是让代码的“不可变性”意图更清晰。你在编写Vue组件时试着问自己一句这个变量后续会不会被重新赋值不会就用const。养成这个习惯你写的代码会越来越让人放心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑