《如何写出高质量的前端代码》学习笔记
网络请求存在的问题
//UserList组件内部
getUsers(){
axios.get('/api/v1/users', {
params: {
page: 1,
page_size: 10
}
}).then(response =>{
if(response.status === 200 && response.data.code === 200){
this.users = response.data.data || []
}
}).catch(e =>{
alert(e.message)
console.log(e)
})
}
在开发中,直接使用像 axios 这样的库进行网络请求可能会导致以下问题:
- 硬编码 URL:直接在代码中使用字符串形式的 URL,导致后端接口变更时需要在多个地方修改。
- 重复的成功/失败处理逻辑:每个请求都需要重复编写相同的逻辑。
- 组件内方法不应直接进行网络请求:这会导致代码难以复用和维护。
- 直接依赖第三方库:这会导致项目与库深度耦合,不利于后期更换库。
接口地址的封装
建议将所有接口地址封装为常量,以避免硬编码。可以使用对象、数组或字符串的形式来存储接口地址和请求方法。
对象形式
export const userApis = {
getUsers: {
url: '/api/v1/users',
method: 'GET'
},
addUser: {
url: '/api/v1/user',
method: 'POST'
},
getUserDetail: {
url: '/api/v1/user/{id}',
method: 'GET'
},
updateUser: {
url: '/api/v1/user/{id}',
method: 'PUT'
},
deleteUser: {
url: '/api/v1/user/{id}',
method: 'DELETE'
}
}
数组形式
export const userApis1 = {
getUsers: ['GET', '/api/v1/users'],
addUser: ['POST', '/api/v1/user'],
getUserDetail: ['GET','/api/v1/user/{id}'],
updateUser: ['PUT', '/api/v1/user/{id}'],
deleteUser: ['DELETE','/api/v1/user/{id}']
}
字符串形式
export const userApis = {
getUsers: 'GET /api/v1/users',
addUser: 'POST /api/v1/user',
getUserDetail: 'GET /api/v1/user/{id}',
updateUser: 'PUT /api/v1/user/{id}',
deleteUser: 'DELETE /api/v1/user/{id}'
}
接口服务的封装
将网络请求封装到 service 层,避免在组件中直接调用接口。这样可以减少代码重复,降低对后端接口设计的耦合。
// /domain/user/service.js
import userApis from './api.js';
export function getUsers(page=1, page_size=-1){
return request(userApis.getUsers, {
params: {
page: page,
page_size: page_size
}
})
}
网络请求方法的封装
封装一个通用的 request 方法,避免直接使用 axios 等库。这样可以在需要时更换底层库而不影响业务代码。
import axios from "axios";
import { Notification } from 'element-ui';
const instance = axios.create();
const CancelToken = axios.CancelToken;
//处理url中的参数
instance.interceptors.request.use(function (config) {
let url = config.url;
let params = config.params;
url = url.replace(/{(\w+)}/g, function (match, $1) {
if(params[$1]){
let value = params[$1];
delete params[$1];
return encodeURIComponent(value);
}
});
config.url = url;
return config;
});
//提取请求url中的method
instance.interceptors.request.use(function (config) {
let urlConfig = config.url.split(/\s+/);
if(urlConfig.length > 1){
config.method = urlConfig[0].toLowerCase();
config.url = urlConfig[1];
}
return config;
});
//处理获取取消请求的配置
instance.interceptors.request.use(function (config) {
if(config.getCancelMethod && typeof config.getCancelMethod === 'function'){
config.cancelToken = new CancelToken(function executor(c) {
config.getCancelMethod(c);
})
delete config.getCancelMethod;
}
return config;
});
// 成功、失败处理
instance.interceptors.response.use(function (response) {
let {code, data, message} = response.data || {};
if(code && code === 200){
return Promise.resolve(data)
}else{
Notification({
message: message || '接口错误',
type: 'error'
});
return Promise.reject(response.data)
}
}, function (error) {
if(error instanceof axios.AxiosError){
switch (error.response.status){
case 401:
location.href = '/login';
return
default: {
Notification({
message: error.message || '接口异常',
type: 'error'
});
}
}
}
return Promise.reject(error);
});
export default instance
项目结构和开发流程
建议将项目分为基础层、领域层和应用层:
- 基础层:如
request.js,封装通用的工具。 - 领域层:如
api.js和service.js,封装业务逻辑。 - 应用层:具体的页面和组件,调用
service提供的方法。
├── base # 基础层
│ └── utils
│ └── request.js # 封装的网络请求
├── domain # 领域层
│ └── user
│ ├── api.js # 用户接口配置
│ └── service.js # 用户相关请求配置
└── src # 应用层
└── pages # 页面组件
└── user-list # 请求 /domain/user/service 中的方法
开发流程
- 阅读 API 文档,创建
api.js进行接口配置。 - 创建
service服务,封装业务模块的增删改查方法。 - 创建业务组件,调用
service服务。
总结
- 所有的API不能硬编码,必须封装为常量
- 所有的增删改查必须封装为service服务,不能在业务模块中单独实现
- 任何业务代码和service中都不能出现第三方库调用