Skip to content

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

这个命令将会:

  1. 更新package.json依赖
  2. umirc.ts配置文件中添加以下配置:
    typescript
    tailwindcss: {},
    plugins: ["@umijs/plugins/dist/tailwindcss"],
  3. 创建tailwind.config.js文件,配置Tailwind CSS的内容路径,使用CommonJS模块导出模式指定组件路径
  4. 生成tailwind.css文件,包含Tailwind的基础样式

通过以上配置,我们可以直接在项目中使用Tailwind CSS的类名来设计组件样式,大大提高了开发效率。

以上就是inChat项目的初始化搭建过程啦😃