你好,我是杨文坚。

这是我们基础篇部分的最后一节课,来聊聊Vue 3 项目开发的代码规范实践。我们会以企业级项目的视角,实现项目的代码风格限制和代码质量检查,为你后续Vue 3项目进阶开发的代码规范打好基础。

为什么要专门留一节课来讲代码规范呢?

我们都知道,企业级项目通常都是团队多人合作进行开发和维护的。多人合作必然就逃不开个人的开发习惯和技术能力的差异,而技术能力的不足又会很直接体现在平时的坏习惯之中。比如说,有些开发者习惯性地在JavaScript 代码里写一堆 console.log 代码进行功能调试,这个习惯其实很不好,如果在项目里,console.log 频繁使用不规范是很容易导致内存泄露的。

但开发者也是人,或多或少都有“坏习惯”,特别是遇到项目工期紧张,经常顾不上项目代码的规范和质量,只要功能实现就好了。

这些不可控的项目和人员因素,会让不规范代码慢慢积累,各种“小坑”密集沉淀,量变引发质变形成“大坑”,最终的结果就是项目代码难以维护,项目功能经常出问题。

为了规避这些问题,我们先把Vue 3 项目的代码规范梳理成多个维度来进行讲解。

Vue 3 项目代码规范有哪些方面?

首先,我们必须要理清楚一个概念,Vue 3 项目说到底本质是JavaScript项目,既然是JavaScript项目,那我们就可以把项目代码规范分解成以下三个方面:

  1. 强类型语言开发JavaScript;
  2. 代码格式规范;
  3. 代码质量检查。

我们先来看第一点,用强类型语言开发JavaScript。 一般用强类型的语言开发JavaScript应用,即使用TypeScript进行开发。TypeScript的显式类型,可以减少JavaScript语言因为弱类型原因带来的潜在问题,例如常见的浏览器运行时,对象属性不存在的问题。

而且使用TypeScript,也可以增强代码的可读性,由于所有代码里的数据都经过TypeScript定义的数据类型,在代码阅读过程中,我们能很清晰地了解业务代码中的数据流向和数据变化。

第二点就是代码格式规范。 一般是在开发过程中,我们都会用工具来统一代码风格,方便代码的阅读,更方便团队的开发合作。在企业中,团队多人协作的项目经常遇到不同开发习惯导致代码风格不一致,例如代码缩进长度、代码每行最长长度、文件最多行数等代码格式。

多人协同开发的情况下,如果每人编写的代码格式不一致,就会降低代码的可读性和维护性。你可以设想一下,跟你一起开发项目的同事在代码里留下一个上千行代码的文件让你来修改,你会不会感觉到压力很大?如果这上千行代码的每行缩进长度都很随意,那你会不会更加崩溃?

那么既然要用工具来统一规范代码风格,在Vue 3项目中,我们需要用到什么工具来规范限制呢?

目前主流的代码格式规范工具是 通过ESLint和Prettier结合来限制开发规范。其中ESLint功能是代码格式化和代码质量检查;而Prettier的功能是代码格式化,而且可以利用在VS Code编辑器里的Prettier插件,实现每次保存代码文件,自动格式化代码规范,无需人工调整代码规范。

最后一点,代码质量检查。 也就是检查代码质量,在开发过程中减少代码中潜在的问题。这个是最重要的,如果代码质量规范限制严格,可以大大减少线上问题。比如某个变量忘记声明就直接使用,还有上面提到过的滥用console.log带来的内存泄露的隐患等。当然这些代码质量问题都可以通过工具ESLint来检查和发现。

那么接下来,我们如何在实际项目开发中落实这些规范和要求呢?我们按照前面说的三个关键点一一来讲解。

如何在Vue3项目中快速使用TypeScript?

说到TypeScript,你应该或多或少听说过,它是JavaScript的“超集”,也就是说JavaScript是TypeScript的子集。众所周知,JavaScript是弱类型的语法,定义的数据可以随意赋值其它数据类型,这个弱类型特性很容易给代码在运行时候留下隐患。

TypeScript的出现很大程度避免了这些潜在的类型隐患,它主要是在开发过程结合VS Code等编辑器对进行代码里的类型推断检查,并且报出警告甚至错误。同时,TypeScript在编译成JavaScript时候,也会再次进行类型推断检查,如果有类型使用不规范,也会报错中断编译。

我们现在举一个例子对比说明一下。这是存在属性访问问题的JavaScript代码:

1
2
3
4
const obj = {
a: null
};
console.log(obj.a.b + 1);

这段JavaScript代码在开发过程中,如果不注意很难发现潜在的问题,一般要等代码在浏览器里运行的时候才能发现问题,如下图所示:

等在浏览器运行时候才能发现,就为时已晚了,可能已经影响到用户使用了。但如果换成TypeScript,我们就可以在“开发阶段”和“代码编译阶段”及早发现问题,在代码编译发布前提前把隐患暴露出来,你先看下这段代码:

1
2
3
4
5
6
7
8
9
10
11
// 用 type 定义数据类型 MyObject
type MyObject = {
a: null;
};

// 用类型 MyObject 来声明变量 obj
const obj: MyObject = {
a: null
};

console.log(obj.a.b + 1);

这段代码是将前面存在问题的JavaScript代码改造成了TypeScript代码,我们只是加上了TypeScript类型定义和变量类型声明,在开发阶段就能基于VS Code编辑器发现错误了,如下图所示:

在TypeScript代码编译成JavaScript代码过程中,也会报错,如下图所示:

通过这些例子的对比你可以看到,同样是一段有问题的功能代码,使用TypeScript就能及早发现问题所在,并且在开发过程中还能基于编辑器提醒或者编译过程中断报错及早发现问题。

不过这里有一个注意点,就是 浏览器里并不能直接运行TypeScript代码,TypeScript代码最终还是需要编译成JavaScript代码才能在浏览器中运行。所以,TypeScript的作用主要是在开发阶段和编译阶段辅助开发者排除掉代码的类型隐患,而不是避免隐患。

但是毕竟不是所有人都会TypeScript,如果你对它并不熟悉,又想快速上手Vue 3项目的开发,有什么好方法呢?

我刚刚说了,TypeScript是为了开发阶段排除类型隐患,而且最终是编译成JavaScript代码,所以,小白想要快速上手,将TypeScript到Vue 3项目,核心就在于 对所有数据进行TypeScript类型声明,至于后续的一些高阶用法,你到时候按需查官方文档现学现用就好了。

我现在把上节课的课程案例(基于Pinia的商品订单应用),转换成TypeScript代码,给你讲解一下如何改造成TypeScript项目。

如果我们要把上节课的项目改造成TypeScript项目,需要做以下的TypeScript项目配置四个步骤。

第一步:安装 Vue 3 TypeScript项目需要用到的npm模块依赖:

  • 执行安装依赖命令:npm i -D typescript  @vue/tsconfig;
  • 其中typescript模块是TypeScript官方的编译模块,@vue/tsconfig是Vue 3 官方的配置模块,我们可以基于这个模块来直接使用官方的推荐配置,减少初始化配置的成本。

第二步:配置TypeScript的配置文件 tsconfig.config.json:

  • 在项目的根目录下创建名为 tsconfig.config.json 的文件,文件内容如下代码所示:
1
2
3
4
5
6
7
8
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": "."
}
}

由于Vite自带了TypeScript的编译处理,所以我们就不需要新增其它开发配置了。

第三步:修改源码文件的TypeScript文件类型:

  • Vue模板文件设置TypeScript语言类型 lang=“ts”;
  • JS文件改成TS文件。

第四步:给项目源码文件的数据添加TypeScript类型声明。

先在项目根目录创建一个给Vue文件的类型声明文件 env.d.ts:

1
/// <reference types="vite/client" />

再创建一个类型的文件 src/types.ts,主要给项目业务逻辑需要用到的数据进行类型声明,具体内容如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import type { Store } from 'pinia';

// 声明订单商品数据类型
export interface MyItem {
name: string;
price: number;
count: number;
}

// 声明基于Pinia的公共数据类型
export interface MyState {
text: string;
list: MyItem[];
}

// 声明基于Pinia的Getters读数据方法的数据类型
export type MyStoreGetters = {
totalPrice(state: MyState): number;
};

// 声明基于Pinia的Actions操作数据方法的数据类型
export interface MyStoreActions {
updateText(text?: string): void;
increase(index: number): void;
decrease(index: number): void;
}

// 声明基于Pinia的公共数据及其操作方法的聚合数据类型
export type MyStore = Store<
'my-store',
MyState,
MyStoreGetters,
MyStoreActions
>;

接下来,我们就可以将这个项目的数据类型给所有TypeScript代码引用了。下面就是Pinia的公共数据操作文件改造成TypeScript代码内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { defineStore } from 'pinia';
import type { MyState, MyStoreGetters, MyStoreActions } from './types';

export const useMyStore = defineStore<
'my-store',
MyState,
MyStoreGetters,
MyStoreActions
>('my-store', {
state: () => ({
text: '环城东路888号',
list: [
{ name: '苹果', price: 20, count: 0 },
{ name: '香蕉', price: 12, count: 0 },
{ name: '梨子', price: 15, count: 0 }
]
}),

getters: {
totalPrice(state) {
let total = 0;
state.list.forEach((item) => {
total += item.price * item.count;
});
return total;
}
},

actions: {
updateText(text?: string) {
if (text) {
this.text = text;
}
},

increase(index: number) {
this.list[index].count += 1;
},

decrease(index: number) {
if (this.list[index].count > 0) {
this.list[index].count -= 1;
}
}
}
});

你可以看到,改造量非常小,我们只需要给相关方法和数据加上类型的声明,这个声明可以在开发代码和编译代码期间做类型推断,尽早发现代码中潜在的数据操作隐患。
至于剩下的其它Vue文件怎么改造,我们以订单商品列表的Vue文件为例看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="v-list">
<div class="v-list-item" v-for="(item, index) in myStore.list" :key="index">
<span class="text">{{ item.name }}</span>
<span class="text">单价: {{ item.price }}</span>
<button class="btn" v-on:click="myStore.decrease(index)">-</button>
<span class="count"> {{ item.count }}</span>
<button class="btn" v-on:click="myStore.increase(index)">+</button>
</div>
</div>
</template>

<script setup lang="ts">
import type { MyStore } from '../types';
import { useMyStore } from '../store';
const myStore: MyStore = useMyStore();
</script>

你会发现Vue文件的TypeScript改造量也很小,就是