【Vue/JS/TS】+【前端日常开发】:从【变量声明/函数写法/类型标注】到【落地实操】,彻底搞懂【前端可维护代码】的最佳写法,避开团队协作高频坑!

📑 文章目录
- [一、先定一个总原则:让代码回答 3 个问题](#一、先定一个总原则:让代码回答 3 个问题)
- [二、变量声明:const 优先,let 必要;别用 var](#二、变量声明:const 优先,let 必要;别用 var)
- [2.1 规则一:默认用 const,能不变就不变](#2.1 规则一:默认用 const,能不变就不变)
- [2.2 规则二:对象/数组"内容可变"要自觉](#2.2 规则二:对象/数组“内容可变”要自觉)
- [2.3 规则三:作用域要清楚,尽量避免"跨块复用同名"](#2.3 规则三:作用域要清楚,尽量避免“跨块复用同名”)
- [2.4 变量命名(你会在 Vue 里反复用到)](#2.4 变量命名(你会在 Vue 里反复用到))
- [三、函数写法:优先"清晰 + 可复用";区分声明与箭头](#三、函数写法:优先“清晰 + 可复用”;区分声明与箭头)
- [3.1 规则一:函数做工具就写"函数声明",做回调就写"箭头函数"](#3.1 规则一:函数做工具就写“函数声明”,做回调就写“箭头函数”)
- [3.2 规则二:箭头函数逻辑简单就用隐式返回;复杂就用大括号 + 显式 return](#3.2 规则二:箭头函数逻辑简单就用隐式返回;复杂就用大括号 + 显式 return)
- [3.3 规则三:参数处理清晰,默认值优先,避免"神秘 undefined"](#3.3 规则三:参数处理清晰,默认值优先,避免“神秘 undefined”)
- [3.4 规则四:尽量"早返回",减少深层嵌套](#3.4 规则四:尽量“早返回”,减少深层嵌套)
- [3.5 规则五:纯函数优先,副作用要"隔离"](#3.5 规则五:纯函数优先,副作用要“隔离”)
- [四、TS 类型标注:标注边界,能推断就推断;别滥用 any](#四、TS 类型标注:标注边界,能推断就推断;别滥用 any)
- [4.1 规则一:不要到处写类型;优先让 TS 推断简单情况](#4.1 规则一:不要到处写类型;优先让 TS 推断简单情况)
- [4.2 规则二:公开函数/工具函数,建议给入参和返回值](#4.2 规则二:公开函数/工具函数,建议给入参和返回值)
- [4.3 规则三:类型别写成"装饰品",要覆盖真实约束](#4.3 规则三:类型别写成“装饰品”,要覆盖真实约束)
- [4.4 规则四:联合类型要配合"判别方式"或类型保护(type guard)](#4.4 规则四:联合类型要配合“判别方式”或类型保护(type guard))
- [4.5 规则五:空值处理要"可读",优先 ?? / 可选链 ?.](#4.5 规则五:空值处理要“可读”,优先 ?? / 可选链 ?.)
- [4.6 规则六:尽量少用类型断言(as),它只能当"最后一层保险"](#4.6 规则六:尽量少用类型断言(as),它只能当“最后一层保险”)
- [五、一套完整实战示例:Vue 场景下的变量/函数/类型怎么选](#五、一套完整实战示例:Vue 场景下的变量/函数/类型怎么选)
- [5.1 TS 版本:推荐写法(简洁又规范)](#5.1 TS 版本:推荐写法(简洁又规范))
- [5.2 反例:同样的需求,用 JS/TS 常见写法会怎么"变味"](#5.2 反例:同样的需求,用 JS/TS 常见写法会怎么“变味”)
- [六、日常落地清单:写完代码前你可以自检这 6 条](#六、日常落地清单:写完代码前你可以自检这 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(作用域、提升行为更容易造成误解)
反例(可读性差 + 未来容易被改)
js
let total = 0;
for (const p of products) {
total += p.price;
}
total = total * 1.1; // 未来改动时很容易忽略"total"本来不该变
正例(更清晰:total 的含义更稳定)
js
const total = products.reduce((sum, p) => sum + p.price, 0);
const discountTotal = total * 1.1;
[⬆ 返回目录](#⬆ 返回目录)
2.2 规则二:对象/数组"内容可变"要自觉
很多人以为 const obj = {} 就代表"不能改",这不成立。const 只是保证变量绑定不被重新指向,但对象内部仍然可以被改。
你可以这样理解:
const:变量名别换指向(不要再obj = xxx)- 对象内部修改:要么明确接受可变,要么尽量用不可变写法(更适合状态管理)
反例(隐蔽的可变)
js
const user = { name: 'Alice', age: 20 };
user.age = 21; // 读者可能以为你在做不可变更新,但你在原地修改
正例(不可变更新:更利于追踪变更)
js
const user = { name: 'Alice', age: 20 };
const updatedUser = { ...user, age: 21 };
[⬆ 返回目录](#⬆ 返回目录)
2.3 规则三:作用域要清楚,尽量避免"跨块复用同名"
let/const 都是块级作用域(block scope)。同一个块里避免反复声明、避免在不同层级用同名变量,会让读者在脑内"跳转"。
反例(同名遮蔽,踩坑常见)
js
let keyword = 'Vue';
if (search) {
let keyword = search; // 遮蔽了外层 keyword,后续你可能以为 keyword 一直是同一个
}
正例(用更明确的变量名)
js
let keyword = '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 = () => {}或内联箭头
例子:工具函数用声明
js
function formatMoney(cents) {
return (cents / 100).toFixed(2);
}
例子:回调用箭头
js
const total = items.reduce((sum, item) => sum + item.price, 0);
[⬆ 返回目录](#⬆ 返回目录)
3.2 规则二:箭头函数逻辑简单就用隐式返回;复杂就用大括号 + 显式 return
简单表达式:
js
const isHot = (p) => p.score >= 90;
复杂逻辑:
js
const normalizeProduct = (p) => {
const price = p.priceCents ?? 0;
return { ...p, price };
};
这样做的好处是:读者不用猜你的函数到底会不会做额外工作。
[⬆ 返回目录](#⬆ 返回目录)
3.3 规则三:参数处理清晰,默认值优先,避免"神秘 undefined"
很多坑来自这种写法:你在函数里不断判断 x && ...,结果逻辑变得很难读。
反例(读者要脑内推导大量情况)
js
function calc(min, max) {
if (min && max) return max - min;
if (min) return min;
return 0;
}
正例(用默认值 + 明确语义)
js
function calc(min = 0, max = 0) {
if (max > 0) return max - min;
return min;
}
[⬆ 返回目录](#⬆ 返回目录)
3.4 规则四:尽量"早返回",减少深层嵌套
Vue 写 watch 或处理筛选时,最常见的代码风格问题就是深层 if/else。
反例(嵌套过深)
js
function filterProducts(products, keyword) {
const res = [];
for (const p of products) {
if (keyword) {
if (p.name.includes(keyword)) {
res.push(p);
}
} else {
res.push(p);
}
}
return res;
}
正例(早返回,逻辑更顺)
js
function filterProducts(products, keyword) {
if (!keyword) return products;
return products.filter((p) => p.name.includes(keyword));
}
[⬆ 返回目录](#⬆ 返回目录)
3.5 规则五:纯函数优先,副作用要"隔离"
在 Vue 里:
-
计算数据(
computed)尽量纯:同样输入得到同样输出 -
请求/日志/写状态这类副作用放到合适的地方:
actions、watch回调内部等
纯函数示例:
js
function calcCartTotal(items) {
return items.reduce((sum, it) => sum + it.quantity * it.price, 0);
}
副作用示例:
js
async function loadProducts(api) {
const res = await api.getProducts();
// 这里才开始处理"请求结果到状态"的逻辑
return res;
}
[⬆ 返回目录](#⬆ 返回目录)
四、TS 类型标注:标注边界,能推断就推断;别滥用 any
TS 的核心目标不是"把代码变长",而是让你在最该出错的地方更早发现问题。所以类型标注要遵循一个非常实用的策略:
规则总纲:在"边界处"标注类型,在"内部"尽量让 TS 推断。
边界通常是:
- 函数参数 / 返回值(尤其是对外暴露的函数)
- API 请求数据的入口
- 对象结构很复杂、很容易写错的地方
4.1 规则一:不要到处写类型;优先让 TS 推断简单情况
反例(冗长但没带来额外安全)
ts
const keyword: string = 'Vue';
const page: number = 1;
正例(短且不牺牲安全)
ts
const keyword = 'Vue';
const page = 1;
[⬆ 返回目录](#⬆ 返回目录)
4.2 规则二:公开函数/工具函数,建议给入参和返回值
尤其是你写到 util、通用函数、团队会复用的逻辑里。
ts
type MoneyCents = number;
function formatMoney(cents: MoneyCents): string {
return (cents / 100).toFixed(2);
}
[⬆ 返回目录](#⬆ 返回目录)
4.3 规则三:类型别写成"装饰品",要覆盖真实约束
你要的是"约束",不是"占位"。例如 API 数据通常是最危险的边界。
场景:商品列表接口
假设接口返回结构如下:
id必须存在且是字符串priceCents可能是数字tags可能为空
我们在 TS 里可以这样建模:
ts
type Product = {
id: string;
name: string;
priceCents: number;
tags?: string[];
};
type Filters = {
keyword?: string;
minPriceCents?: number;
maxPriceCents?: number;
};
如果你在后续筛选里把 priceCents 当成字符串用,TS 就能在你写代码时提醒你。
[⬆ 返回目录](#⬆ 返回目录)
4.4 规则四:联合类型要配合"判别方式"或类型保护(type guard)
反例(看起来能编过,但可能埋雷)
ts
function getLabel(v: string | number) {
// 你没有区分类型就直接当字符串用
return v.toUpperCase(); // number 没有 toUpperCase
}
正例(判别联合 + 分支清晰)
ts
function getLabel(v: string | number) {
if (typeof v === 'string') return v.toUpperCase();
return String(v);
}
[⬆ 返回目录](#⬆ 返回目录)
4.5 规则五:空值处理要"可读",优先 ?? / 可选链 ?.
很多 Vue/TS 项目坑来自 null/undefined 混用。建议你在严格模式下保持习惯:
- 有默认值:优先用
?? - 可能为 null:优先用
?. - 避免到处写强行断言
as
ts
const priceCents = product.priceCents ?? 0;
const firstTag = product.tags?.[0] ?? '';
[⬆ 返回目录](#⬆ 返回目录)
4.6 规则六:尽量少用类型断言(as),它只能当"最后一层保险"
as 可以让 TS 假装你说的是对的,但如果你其实判断错了,就会把运行时风险带回来。
更推荐写"类型保护":
ts
function isProduct(value: any): value is Product {
return (
value &&
typeof value.id === 'string' &&
typeof value.name === 'string' &&
typeof value.priceCents === 'number'
);
}
[⬆ 返回目录](#⬆ 返回目录)
五、一套完整实战示例:Vue 场景下的变量/函数/类型怎么选
下面这个示例我会做成"你可以直接套进项目的写法"。场景:商品列表页需要:
-
获取商品(边界)
-
根据筛选条件计算展示列表(纯函数优先)
-
格式化金额(工具函数)
-
更新 UI 所需的数据(状态层处理副作用)
5.1 TS 版本:推荐写法(简洁又规范)
ts
type Product = {
id: string;
name: string;
priceCents: number;
tags?: string[];
};
type Filters = {
keyword?: string;
minPriceCents?: number;
maxPriceCents?: number;
};
function formatMoney(cents: number): string {
return (cents / 100).toFixed(2);
}
function matchesFilters(p: Product, filters: Filters): boolean {
const keyword = filters.keyword ?? '';
const minPrice = filters.minPriceCents ?? 0;
const maxPrice = filters.maxPriceCents ?? Number.POSITIVE_INFINITY;
const byKeyword = keyword ? p.name.includes(keyword) : true;
const byMin = p.priceCents >= minPrice;
const byMax = p.priceCents <= maxPrice;
return byKeyword && byMin && byMax;
}
function buildDisplayList(products: Product[], filters: Filters) {
// 纯函数:同样输入得到同样输出
return products
.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 常见写法会怎么"变味"
ts
// 反例:不建模,滥用 any,导致可读性和安全性都下降
function buildDisplayList(products: any[], filters: any) {
const res = [];
for (const p of products) {
// p.priceCents 可能不是 number,你没约束也没处理
if (filters.keyword && !p.name.includes(filters.keyword)) continue;
const priceText = (p.priceCents / 100).toFixed(2); // 运行时才知道会不会炸
res.push({ id: p.id, title: p.name, priceText, tags: p.tags });
}
return res;
}
这个反例会带来三个典型问题:
-
可读性:
any让读者无法判断结构是什么 -
可维护性:未来接口一变,你不会在编译期得到提醒
-
风险滞后:错误往往到运行时才暴露
[⬆ 返回目录](#⬆ 返回目录)
六、日常落地清单:写完代码前你可以自检这 6 条
你不需要背很多条,只要养成"写完快速自检"的习惯:
-
这段代码里我有没有"随便用 let/var",而其实只需要
const? -
有无对象内部被我悄悄改了,导致外部状态不可追踪?
-
函数是否逻辑被我写得很深?有没有可以拆成纯函数/工具函数?
-
回调里我是否写成了"既做判断又做副作用"的大杂烩?
-
TS 里我有没有把关键边界参数/返回值用
any或缺失类型,导致约束失效? -
null/undefined我有没有用清晰的??/?.处理?有没有靠"运气"写断言?
[⬆ 返回目录](#⬆ 返回目录)
总结:简洁不是随便,规范是让你更快更稳
-
const优先:减少"变量会变"的认知成本,让代码更稳定 -
函数写法要遵循"清晰表达":工具函数清晰可复用,回调函数紧凑好读
-
TS 类型标注要守边界:能推断就推断,推断不了就在关键位置补上约束
-
大多数线上坑不是你不会写,而是你"写了但读不懂 / 约束没生效 / 风险滞后到运行时"
[⬆ 返回目录](#⬆ 返回目录)
🔍 系列模块导航
📝 编码语法规范
这是前端规范实战系列中第二个模块,当编码语法规范模块更新完成之后会附上此模块的跳转链接,方便同学们阅读学习。
更新中,敬请期待~
👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~
📚 系列总览
「前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。
更新中,敬请期待~
[⬆ 返回目录](#⬆ 返回目录)
技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护。
哪怕每次只吃透一条规范,长期下来,差距会非常明显。
后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。
觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。
我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~