# 搭建
vue create vue3-system
1
- 整体代码风格文件,能保证在不同的编辑器代码风格统一
# https://EditorConfig.org
[*] # 表示所有文件适用
charset = utf-8
indent_style = space # 缩进风格 tab / space
indent_size = 4 # 缩进大小
end_of_line = lf # 控制换行类型(cr | lf | crlf)
insert_final_newline = true # 尾部加一行空行
trim_trailing_whitespace = true # 去除尾部空格
[*.md]
trim_trailing_whitespace = false # md文件关闭尾部去空格
max_line_length = off # md文件关闭尾部去空格
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
vscode需要安装插件才能读取该文件的风格
EditorConfig for VS Code
- vscode的settings.json文件加两行,即可在保存时自动格式化
"eslint.autoFixOnSave": true,
"editor.formatOnSave": true
1
2
2
- 自动调整一些细节代码风格。能够一次性格式化所有文件代码
vscode 有
Prettier - Code formatter
插件,安装才会生效
- 项目内安装
npm i prettier -D
1
- 根目录新建文件
.prettierrc
{
// tab缩进大小,默认为2
"tabWidth": 4,
// 使用tab缩进,默认false
"useTabs": false,
// 使用分号, 默认true
"semi": true,
// 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)
"singleQuote": false,
// 行尾逗号,默认none,可选 none|es5|all
// es5 包括es5中的数组、对象
// all 包括函数对象等所有可选
"trailingComma": "all",
// 对象中的空格 默认true
// true: { foo: bar }
// false: {foo: bar}
"bracketSpacing": true,
// JSX标签闭合位置 默认false
// false: <div
// className=""
// style={{}}
// >
// true: <div
// className=""
// style={{}} >
"jsxBracketSameLine": false,
// 箭头函数参数括号 默认avoid 可选 avoid| always
// avoid 能省略括号的时候就省略 例如x => x
// always 总是有括号
"arrowParens": "avoid",
"printWidth": 120
}
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
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
.prettierignore
文件说明哪些文件是忽略的
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- package.json文件新建脚本,自动格式化所有文件
"prettier": "prettier --write ."
1
- 根目录创建
.eslintrc.js
文件 - vscode下载eslint插件
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint',
'plugin:prettier/recommended'
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
// 如果需要关闭某一项错误检测,复制错误提示最后的检测项,填到这里'off'
// '@typescript-eslint/no-var-requires': 'off',
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eslint和一些其他的规范可能冲突,如prettier。cli配置了选项就是默认安装好的。
npm i eslint-plugin-prettier eslint-config -prettier -D
1
- 设置一个限制,在git commit的时候就进行代码检查。
工具:git husky
npx husky-init && npm install
1
- 生成的husky/pre-commit就是在执行
git commit
之前会拦截并执行的操作。可以在里面执行lint的格式化操作
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint
1
2
3
4
2
3
4
这样能保证推进代码库的代码都是符合lint规范的。
- 通过commitlint来限制提交
npm i @commitlint/config-conventional @commitlint/cli -D
1
- 在根目录创建commitlint.config.js文件,配置commitlint
module.exports = {
extends: ['@commitlint/config-conventional']
}
1
2
3
2
3
- 使用husky生成commit-msg文件,验证提交信息:
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
1
- 规范commit信息工具:
commitizen
npm i commitizen -D
npx commitizen init cz-conventional-changelog --save-dev --save-exact
1
2
3
2
3
以后commit版本的时候,使用命令
git add .
npx cz
## 或者设置脚本 "commit": "cz"
1
2
3
4
5
2
3
4
5
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 | 代码回退 |
- vue.config.js (opens new window)可以增加webpack的配置,和默认配置进行合并
module.exports = {
// publicPath: './', // 开发测试打包引用成相对路径
outputDir: './build',
configureWebpack: {
resolve: {
alias: {
components: '@/components',
},
},
},
devServer: {
// proxy:开发模式中,所有【/api】开头的请求路径都使用node代理请求,再把数据还给浏览器
proxy: {
'/api': {
target: 'http://localhost:8889',
changeOrigin: true, // 跨域
pathRewrite: {
'^/api': '/',
},
},
},
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 路由初始配置
npm i vue-router@next
1
src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/views/home/home.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<template>
<div>
<router-link to="/">主页</router-link>
<router-link to="/login">登录</router-link>
<router-view></router-view>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
<style lang="less"></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# VueX初始配置
npm i vuex@next
1
src/store/index.ts
import { createStore } from 'vuex';
const store = createStore({
state: {
counter: 0,
},
});
export default store;
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<template>
<div>
{{ counter }}
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
const store = useStore();
const counter = ref(store.state.counter);
return { counter };
},
});
</script>
<style scoped></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Element-plus
- Element-plus (opens new window)为vue3定制的桌面端组件库
npm i element-plus
1
- 全局引用
// main.ts
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
app.use(ElementPlus);
1
2
3
4
2
3
4
- 按序引用需要引用
组件
、组件样式
、base样式
- 可以用
babel-plugin-import
来帮助引入
npm i unplugin-vue-components unplugin-auto-import -D
1
// main.ts
import 'element-plus/dist/index.css';
1
2
2
// vue.config.js
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
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
全局内引入了样式,组件内无需任何导入,直接使用需要的组件就可以。
# axios
- axios (opens new window):将ajax基于promise封装出来的网络请求库
npm i axios
1
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(res => console.log(res.data));
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.all(
[
axios.get(url),
axios.get(url),
axios.get(url),
]
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
instance.defaults.timeout = 2500;
1
2
3
4
2
3
4
- 创建实例进行二次配置能力
const instance = axios.create();
instance.defaults.baseURL = 'https://api.example.com';
instance.defaults.timeout = 2500;
instance.get('/longRequest', {
timeout: 5000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
});
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 拦截取消
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('https://httpbin.org/get');
source.cancel('Operation canceled by the user.');
1
2
3
4
5
2
3
4
5
- 拦截器
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
src
|-service
| |-login
| | |-login.ts
| |-request
| | |-type.ts
| | |-request.ts
| | |-config.ts
| |-index.ts
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 根据环境导出一些对应的配置,如
baseUrl
let BASE_URL = '';
const TIME_OUT = 2000;
if (process.env.NODE_ENV === 'production') {
BASE_URL = '生产环境url';
} else if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://localhost:8888';
} else {
BASE_URL = '测试环境url';
}
export { BASE_URL, TIME_OUT };
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 将axios进行二次封装
- 可以提高代码复用性
- 可以降低代码对第三方库的耦合性
import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { AxiosInterceptorConfig, Interceptor } from './type';
class MyRequest {
instance: AxiosInstance;
interceptor: Interceptor | undefined;
constructor(config: AxiosInterceptorConfig) {
this.instance = axios.create(config);
this.interceptor = config.interceptor;
this.initInterceptors();
}
initInterceptors(): void {
this.instance.interceptors.request.use(
this.interceptor?.requestSuccessInterceptor,
this.interceptor?.requestErrorInterceptor
);
this.instance.interceptors.response.use(
this.interceptor?.responseSuccessInterceptor,
this.interceptor?.responseErrorInterceptor
);
}
request(config: AxiosRequestConfig): Promise<AxiosResponse> {
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then(res => resolve(res.data))
.catch(err => reject(err));
});
}
get(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.request({ ...config, method: 'GET' });
}
post(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.request({ ...config, method: 'POST' });
}
put(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.request({ ...config, method: 'PUT' });
}
delete(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.request({ ...config, method: 'DELETE' });
}
}
export default MyRequest;
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
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
- 将自定义ts类型抽出来,提高代码可读性
import { AxiosRequestConfig } from 'axios';
export interface Interceptor {
requestSuccessInterceptor?: (config: AxiosInterceptorConfig) => AxiosInterceptorConfig;
requestErrorInterceptor?: (config: AxiosInterceptorConfig) => AxiosInterceptorConfig;
responseSuccessInterceptor?: (config: AxiosInterceptorConfig) => AxiosInterceptorConfig;
responseErrorInterceptor?: (config: AxiosInterceptorConfig) => AxiosInterceptorConfig;
}
export interface AxiosInterceptorConfig extends AxiosRequestConfig {
interceptor?: Interceptor;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 导出请求实例对象。如果有多种请求,请求不同baseUrl的数据,则可以创建多个请求对象
import MyRequest from './request/request';
import { BASE_URL, TIME_OUT } from './request/config';
export default new MyRequest({
baseURL: BASE_URL,
timeout: TIME_OUT,
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
// login/login.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import requestIst from '../';
export function login(config: AxiosRequestConfig): Promise<AxiosResponse> {
config.url = '/login';
return requestIst.post(config);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { login } from '@/service/login/login';
export default defineComponent({
setup() {
let data = ref({});
login({
data: {
name: 'ls',
},
}).then(res => (data.value = res));
return { data };
},
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 区分环境
- 常见三种环境:
- 开发环境
- 生产环境
- 测试环境
- 常见三种根据环境区分变量的方式:
- 手动修改
- process.env.NODE_ENV进行读取
- 编写不同环境的配置文件
- webpack的definePlugin插件会在不同的环境下注入不同的值
let BASE_URL = "";
if (process.env.NODE_ENV === 'production') {
BASE_URL = '生产环境url';
} else if (process.env.NODE_ENV === 'development') {
BASE_URL = '开发环境url';
} else {
BASE_URL = '测试环境url';
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 根目录下编写对应的文件,在不同的执行环境下会自动注入不同的环境变量
定义
# .env.production
BASE_URL=生产环境
1
2
2
# .env.production
BASE_URL=开发环境
1
2
2
# .env.production
BASE_URL=测试环境
1
2
2
使用
// 项目内
console.log(process.env.VUE_APP_BASE_URL);
1
2
2
# 其他技巧
- ref获取子组件时定义类型,如何准确的定义typescript类型
import ChildCompnent from './comp/ChildCompnent';
setup() {
const child = ref<InstanceType<typeof ChildCompnent>>();
child.value?.fn();
}
1
2
3
4
5
2
3
4
5