inChat项目构建过程
一、创建项目与axios的二次封装
1.1 项目初始化
bash
# 搭建项目
npx create-umi@latest chat-web-frontend
# 安装依赖
cd chat-web-frontend
npm i socket.io-client
npm i axios
# 其他依赖...
1.2 二次封装axios
项目使用了@umijs/plugin-request
插件,并结合MUI组件实现请求提示功能。
1.2.1 封装axios请求库
这里使用MUI组件来提示请求成功/失败,结合了umi-request以及React 18的createRoot API。
1.2.2 定义通用接口与状态码映射
typescript
/**
* 定义响应数据的通用接口
* @template T 响应数据的类型
* @property {number} code 状态码
* @property {T} data 响应数据
* @property {string} message 响应消息
* @property {boolean} success 请求是否成功
*/
export interface ResponseData<T = any> {
code: number;
data: T;
message: string;
success: boolean;
}
// HTTP状态码对应的错误信息映射表
const codeMessage: Record<number, string> = {
200: '服务器成功返回请求的数据',
201: '新建或修改数据成功',
202: '一个请求已经进入后台排队(异步任务)',
204: '删除数据成功',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作',
401: '用户没有权限(令牌、用户名、密码错误)',
403: '用户得到授权,但是访问是被禁止的',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作',
406: '请求的格式不可得',
410: '请求的资源被永久删除,且不会再得到的',
422: '当创建一个对象时,发生一个验证错误',
500: '服务器发生错误,请检查服务器',
502: '网关错误',
503: '服务不可用,服务器暂时过载或维护',
504: '网关超时',
};
1.2.3 使用MUI的Snackbar显示通知
typescript
const showNotification = (message: string, description: string, severity: 'error' | 'warning' | 'info' | 'success' = 'error') => {
// 创建一个容器用于挂载通知组件
const container = document.createElement('div');
document.body.appendChild(container);
// 创建React 18的根节点,用于渲染通知组件
const root = createRoot(container);
// 定义关闭通知的逻辑
const handleClose = () => {
// 卸载根节点,移除容器
root.unmount();
container.remove();
};
// 渲染Snackbar组件
root.render(
<Snackbar
open={true}
autoHideDuration={6000}
onClose={handleClose}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }}>
{/* 显示通知的标题 */}
<div style={{ fontWeight: 'bold' }}>{message}</div>
{/* 显示通知的描述 */}
<div>{description}</div>
</Alert>
</Snackbar>
);
}
1.2.4 自定义错误处理函数
typescript
// 处理HTTP请求过程中发生的错误
const errorHandler = (error: ResponseError) => {
const { response } = error; // 获取响应对象
// 检查响应是否存在且状态码有效
if (response && response.status) {
// 根据状态码获取对应的错误信息
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response; // 获取状态码和请求的URL
// 显示请求错误的通知
showNotification(
`请求错误 ${status}: ${url}`, // 通知标题
errorText, // 通知描述
'error' // 通知的严重性
);
// 401 未授权时跳转到登录页
if (status === 401) {
localStorage.removeItem('token'); // 移除本地存储的token
window.location.href = '/login'; // 跳转到登录页面
}
} else if (!response) {
// 如果没有响应,显示网络异常的通知
showNotification(
'网络异常', // 通知标题
'网络异常,无法连接服务器', // 通知描述
'error' // 通知的严重性
);
}
return Promise.reject(error);
};
1.2.5 配置请求实例
typescript
/**
* 创建默认配置的请求实例
* 配置基础URL、超时时间和默认请求头
*/
const request = extend({
prefix: process.env.API_URL || 'http://localhost:3006',
timeout: 10000,
errorHandler,
credentials: 'include', // 默认携带cookie
});
/**
* 请求拦截器
* 在发送请求前对请求配置进行处理
*/
request.interceptors.request.use((url, options) => {
// 从本地存储中获取token
const token = localStorage.getItem('token');
// 设置Authorization头部,如果token存在则使用Bearer模式
const authHeader = { Authorization: token ? `Bearer ${token}` : '' };
// 返回修改后的请求配置
return {
url,
options: {
...options,
headers: {
...options.headers,
...authHeader, // 将Authorization头部添加到请求头中
},
},
};
});
/**
* 响应拦截器
* 在接收到响应后对响应数据进行处理
*/
request.interceptors.response.use(async (response) => {
// 克隆响应以便解析JSON数据
const data = await response.clone().json();
// 检查响应数据的状态码是否为200
if (data && data.code !== 200) {
// 处理业务错误,显示通知
showNotification(
'请求失败',
data.message || '未知错误',
'error'
);
// 401: 未登录或token过期,处理跳转到登录页面
if (data.code === 401) {
localStorage.removeItem('token'); // 移除token
window.location.href = '/login'; // 跳转到登录页面
}
return Promise.reject(data); // 返回被拒绝的Promise
}
return response; // 返回原始响应
});
// 请求配置类型定义
interface CustomRequestConfig {
errorConfig: {
adaptor: (resData: any) => any; // 适配器函数
};
middlewares: Array<(ctx: any, next: () => Promise<void>) => Promise<void>>; // 中间件数组
requestInterceptors: Array<(url: string, options: any) => { url: string; options: any }>; // 请求拦截器数组
responseInterceptors: Array<(response: any) => any>; // 响应拦截器数组
}
// 导出请求配置
export const requestConfig: CustomRequestConfig = {
errorConfig: {
adaptor: (resData) => {
return {
...resData,
success: resData.code === 200, // 判断请求是否成功
errorMessage: resData.message, // 错误信息
};
},
},
middlewares: [
async (ctx, next) => {
// 请求前处理
await next(); // 继续执行下一个中间件
// 请求后处理
},
],
requestInterceptors: [
(url, options) => {
return { url, options }; // 返回请求的url和options
},
],
responseInterceptors: [
(response) => {
return response; // 返回响应
},
],
};
1.3 实现登录逻辑
在完成axios的二次封装后,我们可以开始对接登录接口。以下是接口定义格式:
typescript
import request from '@/util/https';
import { ResponseData } from '@/util/https';
// 定义登录接口返回的数据类型
interface LoginResponse {
token: string;
user?: {
// id: string;
username: string;
};
}
/**
* 登录API
* @param username 用户名
* @param password 密码
* @returns 返回包含token和用户信息的响应
*/
export async function loginAPI(username: string, password: string) {
return request<ResponseData<LoginResponse>>('/api/users/login', {
method: 'POST',
data: {
username,
password
}
});
}
1.4 样式设计(Tailwind CSS)
接口初步对接成功后,开始设计CSS。这里采用Tailwind CSS进行开发。
1.4.1 安装Tailwind CSS
bash
pnpm install -D tailwindcss postcss autoprefixer
1.4.2 配置Tailwind CSS
由于应用了Umi,配置Tailwind CSS变得非常简单:
bash
npx umi g tailwindcss
这个命令将会:
- 更新
package.json
依赖 - 在
umirc.ts
配置文件中添加以下配置:typescripttailwindcss: {}, plugins: ["@umijs/plugins/dist/tailwindcss"],
- 创建
tailwind.config.js
文件,配置Tailwind CSS的内容路径,使用CommonJS模块导出模式指定组件路径 - 生成
tailwind.css
文件,包含Tailwind的基础样式
通过以上配置,我们可以直接在项目中使用Tailwind CSS的类名来设计组件样式,大大提高了开发效率。
以上就是inChat项目的初始化搭建过程啦😃