【Vue/JS/TS】+【前端日常开发】:从【变量声明/函数写法/类型标注】到【落地实操】,彻底搞懂【前端可维护代码】的最佳写法,避开团队协作高频坑!
📑 文章目录
- 一、先定一个总原则:让代码回答 3 个问题
- 二、变量声明:const 优先,let 必要;别用 var
- 2.1 规则一:默认用 const,能不变就不变
- 2.2 规则二:对象/数组“内容可变”要自觉
- 2.3 规则三:作用域要清楚,尽量避免“跨块复用同名”
- 2.4 变量命名(你会在 Vue 里反复用到)
- 三、函数写法:优先“清晰 + 可复用”;区分声明与箭头
- 3.1 规则一:函数做工具就写“函数声明”,做回调就写“箭头函数”
- 3.2 规则二:箭头函数逻辑简单就用隐式返回;复杂就用大括号 + 显式 return
- 3.3 规则三:参数处理清晰,默认值优先,避免“神秘 undefined”
- 3.4 规则四:尽量“早返回”,减少深层嵌套
- 3.5 规则五:纯函数优先,副作用要“隔离”
- 四、TS 类型标注:标注边界,能推断就推断;别滥用 any
- 4.1 规则一:不要到处写类型;优先让 TS 推断简单情况
- 4.2 规则二:公开函数/工具函数,建议给入参和返回值
- 4.3 规则三:类型别写成“装饰品”,要覆盖真实约束
- 4.4 规则四:联合类型要配合“判别方式”或类型保护(type guard)
- 4.5 规则五:空值处理要“可读”,优先 ?? / 可选链 ?.
- 4.6 规则六:尽量少用类型断言(as),它只能当“最后一层保险”
- 五、一套完整实战示例:Vue 场景下的变量/函数/类型怎么选
- 5.1 TS 版本:推荐写法(简洁又规范)
- 5.2 反例:同样的需求,用 JS/TS 常见写法会怎么“变味”
- 六、日常落地清单:写完代码前你可以自检这 6 条
- 🔍 系列模块导航
同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。
(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)
很多前端开发者都会遇到一个瓶颈:
代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;
一个人写没问题,一到团队协作就各种混乱、踩坑、返工。
想写出干净、优雅、可维护的专业代码,
靠的不是天赋,而是体系化的规范 + 真实实战经验。
这一系列《前端规范实战》,我会用大白话 + 真实业务场景,
不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。
帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。
做了 7 年前端后我越来越确定:规范不是“为了显得严谨”,而是为了在你每天写的代码里,减少歧义、降低出错率、让团队更快读懂。尤其是 Vue 项目里,代码往往会被拆到很多文件、很多watch/effect/computed里,变量声明、函数写法、类型标注只要稍微“不自洽”,后面就会反复踩同一类坑。
这篇文章不讲玄学底层原理,只讲“日常写代码到底怎么选、为什么这么选、踩坑会踩在哪”,并且每个规则都给出小白也能照着用的完整示例。你可以直接把它当成“编码时的选择题”。
一、先定一个总原则:让代码回答 3 个问题
写任何一段 JS/TS 代码,都尽量让读者(包括未来的你)在 3 秒内看懂:
- 这段代码的“东西”是什么(变量/参数/返回值的语义)
- 这段代码会不会变化(变量是否可变、函数是否有副作用)
- 类型上有哪些约束(TS 能推断就推断,推断不了就明确标注边界)
接下来我们按你关心的 3 个核心语法部分来落地:变量声明 / 函数写法 / 类型标注。
二、变量声明:const优先,let必要;别用var
2.1 规则一:默认用const,能不变就不变
在 JS 里,变量声明最容易带来的 bug 是“以为不会变,结果被改了”,然后你在别处使用了旧值,出现错位、渲染异常、计算结果不对。
推荐:
- “只赋值一次”的变量:用
const - “需要重新赋值”的变量:用
let - **不使用 **
var(作用域、提升行为更容易造成误解)
反例(可读性差 + 未来容易被改)
lettotal=0;for(constpofproducts){total+=p.price;}total=total*1.1;// 未来改动时很容易忽略“total”本来不该变正例(更清晰:total 的含义更稳定)
consttotal=products.reduce((sum,p)=>sum+p.price,0);constdiscountTotal=total*1.1;⬆ 返回目录
2.2 规则二:对象/数组“内容可变”要自觉
很多人以为const obj = {}就代表“不能改”,这不成立。const只是保证变量绑定不被重新指向,但对象内部仍然可以被改。
你可以这样理解:
const:变量名别换指向(不要再obj = xxx)- 对象内部修改:要么明确接受可变,要么尽量用不可变写法(更适合状态管理)
反例(隐蔽的可变)
constuser={name:'Alice',age:20};user.age=21;// 读者可能以为你在做不可变更新,但你在原地修改正例(不可变更新:更利于追踪变更)
constuser={name:'Alice',age:20};constupdatedUser={...user,age:21};⬆ 返回目录
2.3 规则三:作用域要清楚,尽量避免“跨块复用同名”
let/const都是块级作用域(block scope)。同一个块里避免反复声明、避免在不同层级用同名变量,会让读者在脑内“跳转”。
反例(同名遮蔽,踩坑常见)
letkeyword='Vue';if(search){letkeyword=search;// 遮蔽了外层 keyword,后续你可能以为 keyword 一直是同一个}正例(用更明确的变量名)
letkeyword='Vue';if(search){keyword=search;}⬆ 返回目录
2.4 变量命名(你会在 Vue 里反复用到)
下面是我建议的“能直接用”的命名习惯:
- 普通值:
camelCase,语义清晰(例如userName、totalPrice) - 布尔值:优先加语义前缀
is/has/can/should(例如isLoading、hasMore) - 数组:复数名
items、users、products - 事件/回调:
onXxx(例如onSubmit、onClose) - 临时变量:尽量短但别乱缩写(例如
tmp可以,但别变成t、x1这种不可读)
⬆ 返回目录
三、函数写法:优先“清晰 + 可复用”;区分声明与箭头
3.1 规则一:函数做工具就写“函数声明”,做回调就写“箭头函数”
这不是绝对,但能让团队形成一致的阅读节奏:
- 作为“可复用工具函数”(文件顶部、export、util):倾向
function xxx(...) {} - 作为“回调”(例如
map/filter/watch里的一次性逻辑):倾向const xxx = () => {}或内联箭头
例子:工具函数用声明
functionformatMoney(cents){return(cents/100).toFixed(2);}例子:回调用箭头
consttotal=items.reduce((sum,item)=>sum+item.price,0);⬆ 返回目录
3.2 规则二:箭头函数逻辑简单就用隐式返回;复杂就用大括号 + 显式 return
简单表达式:
constisHot=(p)=>p.score>=90;复杂逻辑:
constnormalizeProduct=(p)=>{constprice=p.priceCents??0;return{...p,price};};这样做的好处是:读者不用猜你的函数到底会不会做额外工作。
⬆ 返回目录
3.3 规则三:参数处理清晰,默认值优先,避免“神秘 undefined”
很多坑来自这种写法:你在函数里不断判断x && ...,结果逻辑变得很难读。
反例(读者要脑内推导大量情况)
functioncalc(min,max){if(min&&max)returnmax-min;if(min)returnmin;return0;}正例(用默认值 + 明确语义)
functioncalc(min=0,max=0){if(max>0)returnmax-min;returnmin;}⬆ 返回目录
3.4 规则四:尽量“早返回”,减少深层嵌套
Vue 写watch或处理筛选时,最常见的代码风格问题就是深层if/else。
反例(嵌套过深)
functionfilterProducts(products,keyword){constres=[];for(constpofproducts){if(keyword){if(p.name.includes(keyword)){res.push(p);}}else{res.push(p);}}returnres;}正例(早返回,逻辑更顺)
functionfilterProducts(products,keyword){if(!keyword)returnproducts;returnproducts.filter((p)=>p.name.includes(keyword));}⬆ 返回目录
3.5 规则五:纯函数优先,副作用要“隔离”
在 Vue 里:
计算数据(
computed)尽量纯:同样输入得到同样输出请求/日志/写状态这类副作用放到合适的地方:
actions、watch回调内部等
纯函数示例:
functioncalcCartTotal(items){returnitems.reduce((sum,it)=>sum+it.quantity*it.price,0);}副作用示例:
asyncfunctionloadProducts(api){constres=awaitapi.getProducts();// 这里才开始处理“请求结果到状态”的逻辑returnres;}⬆ 返回目录
四、TS 类型标注:标注边界,能推断就推断;别滥用any
TS 的核心目标不是“把代码变长”,而是让你在最该出错的地方更早发现问题。所以类型标注要遵循一个非常实用的策略:
规则总纲:在“边界处”标注类型,在“内部”尽量让 TS 推断。
边界通常是:
- 函数参数 / 返回值(尤其是对外暴露的函数)
- API 请求数据的入口
- 对象结构很复杂、很容易写错的地方
4.1 规则一:不要到处写类型;优先让 TS 推断简单情况
反例(冗长但没带来额外安全)
constkeyword:string='Vue';constpage:number=1;正例(短且不牺牲安全)
constkeyword='Vue';constpage=1;⬆ 返回目录
4.2 规则二:公开函数/工具函数,建议给入参和返回值
尤其是你写到 util、通用函数、团队会复用的逻辑里。
typeMoneyCents=number;functionformatMoney(cents:MoneyCents):string{return(cents/100).toFixed(2);}⬆ 返回目录
4.3 规则三:类型别写成“装饰品”,要覆盖真实约束
你要的是“约束”,不是“占位”。例如 API 数据通常是最危险的边界。
场景:商品列表接口
假设接口返回结构如下:
id必须存在且是字符串priceCents可能是数字tags可能为空
我们在 TS 里可以这样建模:
typeProduct={id:string;name:string;priceCents:number;tags?:string[];};typeFilters={keyword?:string;minPriceCents?:number;maxPriceCents?:number;};如果你在后续筛选里把priceCents当成字符串用,TS 就能在你写代码时提醒你。
⬆ 返回目录
4.4 规则四:联合类型要配合“判别方式”或类型保护(type guard)
反例(看起来能编过,但可能埋雷)
functiongetLabel(v:string|number){// 你没有区分类型就直接当字符串用returnv.toUpperCase();// number 没有 toUpperCase}正例(判别联合 + 分支清晰)
functiongetLabel(v:string|number){if(typeofv==='string')returnv.toUpperCase();returnString(v);}⬆ 返回目录
4.5 规则五:空值处理要“可读”,优先??/ 可选链?.
很多 Vue/TS 项目坑来自null/undefined混用。建议你在严格模式下保持习惯:
- 有默认值:优先用
?? - 可能为 null:优先用
?. - 避免到处写强行断言
as
constpriceCents=product.priceCents??0;constfirstTag=product.tags?.[0]??'';⬆ 返回目录
4.6 规则六:尽量少用类型断言(as),它只能当“最后一层保险”
as可以让 TS 假装你说的是对的,但如果你其实判断错了,就会把运行时风险带回来。
更推荐写“类型保护”:
functionisProduct(value:any):valueisProduct{return(value&&typeofvalue.id==='string'&&typeofvalue.name==='string'&&typeofvalue.priceCents==='number');}⬆ 返回目录
五、一套完整实战示例:Vue 场景下的变量/函数/类型怎么选
下面这个示例我会做成“你可以直接套进项目的写法”。场景:商品列表页需要:
获取商品(边界)
根据筛选条件计算展示列表(纯函数优先)
格式化金额(工具函数)
更新 UI 所需的数据(状态层处理副作用)
5.1 TS 版本:推荐写法(简洁又规范)
typeProduct={id:string;name:string;priceCents:number;tags?:string[];};typeFilters={keyword?:string;minPriceCents?:number;maxPriceCents?:number;};functionformatMoney(cents:number):string{return(cents/100).toFixed(2);}functionmatchesFilters(p:Product,filters:Filters):boolean{constkeyword=filters.keyword??'';constminPrice=filters.minPriceCents??0;constmaxPrice=filters.maxPriceCents??Number.POSITIVE_INFINITY;constbyKeyword=keyword?p.name.includes(keyword):true;constbyMin=p.priceCents>=minPrice;constbyMax=p.priceCents<=maxPrice;returnbyKeyword&&byMin&&byMax;}functionbuildDisplayList(products:Product[],filters:Filters){// 纯函数:同样输入得到同样输出returnproducts.filter((p)=>matchesFilters(p,filters)).map((p)=>({id:p.id,title:p.name,priceText:formatMoney(p.priceCents),tags:p.tags??[],}));}你可以注意到这些“规范点”在一起工作时的效果:
变量:默认用
const,只有需要重新赋值才用let(此例基本不用let)函数:工具函数用声明,回调用箭头;逻辑清晰
类型:只在关键边界处建模(
Product/Filters),内部让 TS 推断
⬆ 返回目录
5.2 反例:同样的需求,用 JS/TS 常见写法会怎么“变味”
// 反例:不建模,滥用 any,导致可读性和安全性都下降functionbuildDisplayList(products:any[],filters:any){constres=[];for(constpofproducts){// p.priceCents 可能不是 number,你没约束也没处理if(filters.keyword&&!p.name.includes(filters.keyword))continue;constpriceText=(p.priceCents/100).toFixed(2);// 运行时才知道会不会炸res.push({id:p.id,title:p.name,priceText,tags:p.tags});}returnres;}这个反例会带来三个典型问题:
可读性:
any让读者无法判断结构是什么可维护性:未来接口一变,你不会在编译期得到提醒
风险滞后:错误往往到运行时才暴露
⬆ 返回目录
六、日常落地清单:写完代码前你可以自检这 6 条
你不需要背很多条,只要养成“写完快速自检”的习惯:
这段代码里我有没有“随便用 let/var”,而其实只需要
const?有无对象内部被我悄悄改了,导致外部状态不可追踪?
函数是否逻辑被我写得很深?有没有可以拆成纯函数/工具函数?
回调里我是否写成了“既做判断又做副作用”的大杂烩?
TS 里我有没有把关键边界参数/返回值用
any或缺失类型,导致约束失效?null/undefined我有没有用清晰的??/?.处理?有没有靠“运气”写断言?
⬆ 返回目录
总结:简洁不是随便,规范是让你更快更稳
const优先:减少“变量会变”的认知成本,让代码更稳定函数写法要遵循“清晰表达”:工具函数清晰可复用,回调函数紧凑好读
TS 类型标注要守边界:能推断就推断,推断不了就在关键位置补上约束
大多数线上坑不是你不会写,而是你“写了但读不懂 / 约束没生效 / 风险滞后到运行时”
⬆ 返回目录
🔍 系列模块导航
📝 编码语法规范
这是前端规范实战系列中第二个模块,当编码语法规范模块更新完成之后会附上此模块的跳转链接,方便同学们阅读学习。
更新中,敬请期待~
👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~
📚 系列总览
「前端规范实战系列」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。
更新中,敬请期待~
⬆ 返回目录
技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护。
哪怕每次只吃透一条规范,长期下来,差距会非常明显。
后续我会持续更新前端规范、工程化、可维护代码相关实战干货,
帮你告别面条代码、维护噩梦,在开发与面试中更有底气。
觉得有用欢迎点赞 + 收藏 + 关注,不错过每一篇实战内容。
我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~