目录

🪝 Vue 3 + TypeScript 项目搭建

# Vue 3 创建项目

vue create project-name
1

创建 Vue 脚手架时选择的 featue:

  • Babel
  • TypeScript
  • Vuex
  • CSS 预处理器
  • 语法检查
image-20220428111752017 image-20220428112237499

# 代码规范

# .editorconfig 配置

EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。

# http://editorconfig.org

root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Prettier 工具

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

  • 安装 prettier
npm install prettier -D
1
  • 配置 .prettierrc 文件:

    • useTabs :使用 tab 缩进还是空格缩进,选择 false;

    • tabWidth :tab 是空格的情况下,是几个空格,选择 2 个;

    • printWidth :当行字符的长度,推荐 80,也有人喜欢 100 或者 120;

    • singleQuote :使用单引号还是双引号,选择 true ,使用单引号;

    • trailingComma :在多行输入的尾逗号是否添加,设置为 none

    • semi :语句末尾是否要加分号,默认值 true ,选择 false 表示不加;

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": true,
  "trailingComma": "none",
  "semi": false
}
1
2
3
4
5
6
7
8
  • 创建 .prettierignore 忽略文件:
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*
1
2
3
4
5
6
7
8
9
  • 测试 prettier 是否生效

    • 测试一:在代码中保存代码;

    • 测试二:配置一次性修改的命令;

package.json 中配置一个 scripts

    "prettier": "prettier --write ."
1
image-20220428113828750

# 使用 ESLint 检测代码规范

在前面创建项目的时候,就选择了 ESLint ,所以 Vue 会默认帮助我们配置需要的 ESLint 环境。

  • 解决 eslint 和 prettier 冲突的问题:
  • 安装插件:(vue 在创建项目时,如果选择 prettier + ESlint,那么这两个插件会自动安装)
npm i eslint-plugin-prettier eslint-config-prettier -D
1

eslintrc.js 中添加 prettier 插件:

  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/typescript/recommended",
    "@vue/prettier",
    "@vue/prettier/@typescript-eslint",
    'plugin:prettier/recommended' // 添加
  ],
1
2
3
4
5
6
7
8

# Before commit 代码规范 /husky 使用

虽然我们已经要求项目使用 eslint 了,但是不能保证组员提交代码之前都将 eslint 中的问题解决掉了:

  • 也就是我们希望保证代码仓库中的代码都是符合 eslint 规范的;

  • 那么我们需要在组员执行 git commit 命令的时候对其进行校验,如果不符合 eslint 规范,那么自动通过规范进行修复

那么如何做到这一点呢?可以通过 Husky 工具:

  • husky 是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commit、commit-msg、pre-push。

这里我们可以使用自动配置命令:

npx husky-init && npm install
1

image-20220428114752669

点击查看

这里会做三件事:

  • 安装 husky 相关的依赖:

image-20210723112648927

  • 在项目目录下创建 .husky 文件夹:
npx huksy install
1

image-20210723112719634

  • 在 package.json 中添加一个脚本:

image-20210723112817691

接下来,我们需要去完成一个操作:在进行 commit 时,执行 lint 脚本:

image-20210723112932943

这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。

# Git Commit 规范

# 代码提交风格

通常我们的 git commit 会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制

但是如果每次手动来编写这些是比较麻烦的事情,可以使用一个工具:Commitizen(Commitizen 是一个帮助我们编写规范 commit message 的工具)。

  • 安装 Commitizen

    npm install commitizen -D
    
    1
  • 安装 cz-conventional-changelog,并且初始化 cz-conventional-changelog:

    npx commitizen init cz-conventional-changelog --save-dev --save-exact
    
    1

    这个命令会帮助我们安装 cz-conventional-changelog:

    image-20210723145249096

    并且在 package.json 中进行配置:

    "config": {
      "commitizen": {
        "path": "./node_mudules/cz-conventional-changelog"
      }
    }
    
    1
    2
    3
    4
    5

配置完 Commitizen 后,提交代码需要使用 npx cz

  • 选择 type:
Type 作用
feat 新增特性 (feature)
fix 修复 Bug (bug fix)
docs 修改文档 (documentation)
style 代码格式修改 (white-space, formatting, missing semi colons, etc)
refactor 代码重构 (refactor)
perf 改善性能 (A code change that improves performance)
test 测试 (when adding missing tests)
build 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)
ci 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
chore 变更构建流程或辅助工具 (比如更改测试环境)
revert 代码回退
点击查看
  • 第二步选择本次修改的范围(作用域)

image-20210723150147510

  • 第三步选择提交的信息

image-20210723150204780

  • 第四步提交详细的描述信息

image-20210723150223287

  • 第五步是否是一次重大的更改

image-20210723150322122

  • 第六步是否影响某个 open issue

image-20210723150407822

我们也可以在 scripts 中构建一个命令来执行 cz:

image-20210723150526211

# 代码提交验证

如果我们按照 cz 来规范了提交风格,但是依然有同事通过 git commit 按照不规范的格式提交应该怎么办呢?

  • 我们可以通过 commitlint 来限制提交;

安装 @commitlint/config-conventional 和 @commitlint/cli

npm i @commitlint/config-conventional @commitlint/cli -D
1

在根目录创建 commitlint.config.js 文件,配置 commitlint

module.exports = {
  extends: ['@commitlint/config-conventional']
}
1
2
3

使用 husky 生成 commit-msg 文件,验证提交信息:

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
1

(拦截提交)

# 第三方库集成

# vue.config.js 配置

vue.config.js 有三种配置方式:

  • 方式一:直接通过 CLI 提供给我们的选项来配置
    • 比如 publicPath:配置应用程序部署的子目录(默认是 / ,相当于部署在 https://www.my-app.com/ );
    • 比如 outputDir:修改输出的文件夹;
  • 方式二:通过 configureWebpack 修改 webpack 的配置:
    • 可以是一个对象,直接会被合并
    • 可以是一个函数,会接收一个 config,可以通过 config 来修改配置;
  • 方式三:通过 chainWebpack 修改 webpack 的配置:

# vue-router 集成

安装 vue-router 的最新版本:

npm install vue-router@next
1

创建 router 对象( router/main.js ):

import { createRouter, createWebHashHistory } from 'vue-router'
import { RouteRecordRaw } from 'vue-router' // 约束routes的类型

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    redirect: '/main'
  },
  {
    path: '/main',
    component: () => import('../views/main/main.vue')
  },
  {
    path: '/login',
    component: () => import('../views/login/login.vue')
  }
]

const router = createRouter({
  routes,
  history: createWebHashHistory()
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

安装 router( main.ts ):

import router from './router'

createApp(App).use(router).mount('#app')
1
2
3

App.vue 中配置跳转:

<template>
  <div id="app">
    <router-link to="/login">登录</router-link>
    <router-link to="/main">首页</router-link>
    <router-view></router-view>
  </div>
</template>
1
2
3
4
5
6
7

# vuex 集成

安装最新版 vuex:

npm install vuex@next
1

创建 store 对象:

import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      name: 'coderwhy'
    }
  }
})

export default store
1
2
3
4
5
6
7
8
9
10
11

安装 store:

createApp(App).use(router).use(store).mount('#app')
1

在 App.vue 中使用:

<h2>{{ $store.state.name }}</h2>
1

# element-plus 集成

Element Plus,桌面端组件库

  • 相信很多同学在 Vue2 中都使用过 element-ui,而 element-plus 正是 element-ui 针对于 Vue3 开发的一个 UI 组件库;
  • 它的使用方式和很多其他的组件库是一样的,所以学会 element-plus,其他类似于 ant-design-vue、NaiveUI、VantUI 都是差不多的;

安装 element-plus

$ npm install element-plus
1

# 全局引入

一种引入 element-plus 的方式是全局引入,代表的含义是所有的组件和插件都会被自动注册:

import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'

import router from './router'
import store from './store'

createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
1
2
3
4
5
6
7

# 局部引入

使用 unplugin-vue-components 实现按需引入:

  • 安装 unplugin-vue-components
npm i unplugin-vue-components unplugin-auto-import -D
1
  • vue.config.js (这里是 Vue-CLI s 配置方法,其他脚手架需要参考文档)

    const AutoImport = require('unplugin-auto-import/webpack')
    const Components = require('unplugin-vue-components/webpack')
    const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
    
    module.exports = {
        configureWebpack: {
          plugins: [
            AutoImport({
              resolvers: [ElementPlusResolver()],
            }),
            Components({
              resolvers: [ElementPlusResolver()],
            }),
          ],
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 然后在 main.ts 全局注册组件:

    import {
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge,
    } from 'element-plus'
    import 'element-plus/dist/index.css'
    
    const app = createApp(App)
    
    const components = [
      ElButton,
      ElTable,
      ElAlert,
      ElAside,
      ElAutocomplete,
      ElAvatar,
      ElBacktop,
      ElBadge
    ]
    
    for (const cpn of components) {
      app.component(cpn.name, cpn)
    }
    
    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

from: 实现自动引入 + 按需引入 element-plus 原来如此简单 - SegmentFault 思否 (opens new window)

(在非 <template> 中使用组件仍然需要手动引入)

(最新引入还是得看官方文档,特别是引入 CSS 包,一旦找不到样式就没有效果。找 bug 都快怀疑人生了)

点击查看
  • 安装 webpack-bundle-analyzer 看打包信息:
$ npm install --save-dev webpack-bundle-analyzer
1
npm run build --report
1

不用做任何配置,就可以看到终端打印出来打包后各文件大小。

如果想看详细的打包后文件的信息,可以在 vue.config.js 中做如下配置:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    configureWebpack: {
        plugins: [
            new BundleAnalyzerPlugin()
        ]
    }
}
1
2
3
4
5
6
7
8
9

然后打开地址 http://127.0.0.1:8888 就可以了。
据我个人测试,完整引入时,打包后 chunk.js 文件大小为 400k 左右。按需引入后,打包后 chunk.js 文件大小为 200k 左右。

附加内容:Vue-CLI 环境变量

# axios 集成

安装 axios:

npm install axios
1

封装 axios:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { Result } from './types'
import { useUserStore } from '/@/store/modules/user'

class HYRequest {
  private instance: AxiosInstance

  private readonly options: AxiosRequestConfig

  constructor(options: AxiosRequestConfig) {
    this.options = options
    this.instance = axios.create(options)

    this.instance.interceptors.request.use(
      (config) => {
        const token = useUserStore().getToken
        if (token) {
          config.headers.Authorization = `Bearer ${token}`
        }
        return config
      },
      (err) => {
        return err
      }
    )

    this.instance.interceptors.response.use(
      (res) => {
        // 拦截响应的数据
        if (res.data.code === 0) {
          return res.data.data
        }
        return res.data
      },
      (err) => {
        return err
      }
    )
  }

  request<T = any>(config: AxiosRequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      this.instance
        .request<any, AxiosResponse<Result<T>>>(config)
        .then((res) => {
          resolve((res as unknown) as Promise<T>)
        })
        .catch((err) => {
          reject(err)
        })
    })
  }

  get<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request({ ...config, method: 'GET' })
  }

  post<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request({ ...config, method: 'POST' })
  }

  patch<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request({ ...config, method: 'PATCH' })
  }

  delete<T = any>(config: AxiosRequestConfig): Promise<T> {
    return this.request({ ...config, method: 'DELETE' })
  }
}

export default HYRequest
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# tsconfig.json

tsconfig.json・TypeScript 中文网・TypeScript——JavaScript 的超集 (tslang.cn) (opens new window)

{
  // 编译配置
  "compilerOptions": {
    // 目标代码
    "target": "esnext",
    // 目标代码要使用的模块化方案
    "module": "esnext",
    // 开启严格模式
    "strict": true,
    // jsx进行怎样的处理
    "jsx": "preserve",
    // 辅助导入功能
    "importHelpers": true,
    // 按照node解析模块
    "moduleResolution": "node",
    // 跳过一些第三方库的类型监测(axios/loadash)
    "skipLibCheck": true,
    // esModule和commonJS混合使用
    // export defalt / module.exports = {}
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    // 生成映射文件 (ts=>js)
    "sourceMap": true,
		// 文件路径解析时基本路径
    "baseUrl": ".",
    // 指定具体要解析使用的类型 webpack
    "types": ["webpack-env"],
    // 路径解析匹配(类似webpack alias)
    "paths": {
      "@/*": ["src/*"]
    },
    // 可以指定在项目中可以使用哪些库的类型(Proxy/window/Document)
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
  },
  // 要解析的文件后缀名
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  // 排除的文件
  "exclude": ["node_modules"]
}
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

# shims-vue.d.ts

类型声明:

/* eslint-disable */
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
1
2
3
4
5
6

规范、限制组件中配置传入的类型。 prepssetup() …。

📢 上次更新: 2022/09/02, 10:18:16