【前端】基于vite的react项目工程化(2)

278 阅读13分钟

1.前言

【技术/前端】基于vite的react项目工程化(1) 中将项目所需要的各种工程化检测添加完成,接着来完善项目中所需要的基础类库,具体包括:

  • 1.目录划分与创建
  • 2.axios封装
  • 3.antd组件库的引入
  • 4.react-router路由库引入
  • 5.zustand状态管理库引入
  • 6.跨域处理
  • 7.区分环境
  • ...

2.目录划分与创建

为了更好管理代码,需要将特定功能代码进行划分管理

当前模版包括:

  • .husky
  • node_modules
  • public
  • src
    • api: 后台api
    • assets:静态资源
    • components:通用组件
    • constant: 常量
    • config:公用配置
    • hook:react的hooks
    • layout:布局组件
    • pages:具体页面
    • router:路由
    • store:状态管理
    • types:类型文件
    • utils:工具函数
    • App.tsx:app组件
    • main.tsx:文件挂载点
    • vite-env.d.ts:vite声明文件
  • index.html
  • package.json
  • pnpm-lock.yaml
  • tsconfig.json
  • tsconfig.node.json
  • vite-plugin-eslint.d.ts
  • vite.config.ts
  • .editorconfig
  • .eslintrc.cjs
  • .gitignore
  • .prettierrc.cjs
  • .stylelintrc.json
  • commitlint.config.js:规范git提交信息
  • LICENSE
  • README.md

3.axios封装

axios 是基于 promise 的 HTTP 客户端,支持发送,拦截,转换请求和响应

在进行http请求时,请求结果中会包含请求响应码, 其分类如下:

  1. 信息响应中 (100199)
  2. 成功响应 (200299)
  3. 重定向消息 (300399)
  4. 客户端错误响应 (400499)
  5. 服务端错误响应 (500599)

首先安装依赖 pnpm install axios,然后在utils/request.ts中封装,需要考虑以下问题:

  • 1.前后端采用json通信
  • 2.请求响应码在200-399之间的都属于正常请求,400-499之间属于请求出错,500-599属于服务器出错

首先创建axios实例,将其配置抽离到constants中

import axios from 'axios'
import { BASE_URL, REQUEST_TIMEOUT, TIMEOUT_ERRORMESSAGE } from '../constant'

const instance = axios.create({
  baseURL: BASE_URL,
  timeout: REQUEST_TIMEOUT,
  timeoutErrorMessage: TIMEOUT_ERRORMESSAGE,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})
// constant.ts
export const BASE_URL = '/api'
export const REQUEST_TIMEOUT = 8000
export const TIMEOUT_ERRORMESSAGE = '请求超时,请稍后重试'

接着添加请求拦截器

import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'

// 2.请求拦截器
// 请求拦截器配置
const requestConfig = (config: InternalAxiosRequestConfig) => {
  // 2.1 添加loading效果
  // 2.2 添加ak用于鉴权
  // 返回设置好的配置
  return config
}
instance.interceptors.request.use(requestConfig, (error: AxiosError) => Promise.reject(error))

这里就需要封装loading效果,先在public/css/loading.css中添加以下代码:

#loading {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}

#loading .loading {
  animation: rotate linear 3s infinite;
}
#loading .loading.spin {
  animation: rotate linear 3s infinite;
}
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

接着在index.html添加loading的div和引入css文件:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
    <link rel="stylesheet" href="css/loading.css">
  </head>
  <body>
    <div id="root"></div>
    <!-- loading效果 -->
    <div id="loading" style="display: none">
      <svg
        t="1682858040467"
        class="icon loading"
        viewBox="0 0 1024 1024"
        version="1.1"
        xmlns="http://www.w3.org/2000/svg"
        p-id="7810"
        width="64"
        height="64"
      >
        <path
          d="M511.882596 287.998081h-0.361244a31.998984 31.998984 0 0 1-31.659415-31.977309v-0.361244c0-0.104761 0.115598-11.722364 0.115598-63.658399V96.000564a31.998984 31.998984 0 1 1 64.001581 0V192.001129c0 52.586273-0.111986 63.88237-0.119211 64.337537a32.002596 32.002596 0 0 1-31.977309 31.659415zM511.998194 959.99842a31.998984 31.998984 0 0 1-31.998984-31.998984v-96.379871c0-51.610915-0.111986-63.174332-0.115598-63.286318s0-0.242033 0-0.361243a31.998984 31.998984 0 0 1 63.997968-0.314283c0 0.455167 0.11921 11.711527 0.11921 64.034093v96.307622a31.998984 31.998984 0 0 1-32.002596 31.998984zM330.899406 363.021212a31.897836 31.897836 0 0 1-22.866739-9.612699c-0.075861-0.075861-8.207461-8.370021-44.931515-45.094076L195.198137 240.429485a31.998984 31.998984 0 0 1 45.256635-45.253022L308.336112 263.057803c37.182834 37.182834 45.090463 45.253022 45.41197 45.578141A31.998984 31.998984 0 0 1 330.899406 363.021212zM806.137421 838.11473a31.901448 31.901448 0 0 1-22.628318-9.374279L715.624151 760.859111c-36.724054-36.724054-45.018214-44.859267-45.097687-44.93874a31.998984 31.998984 0 0 1 44.77618-45.729864c0.32512 0.317895 8.395308 8.229136 45.578142 45.411969l67.88134 67.88134a31.998984 31.998984 0 0 1-22.624705 54.630914zM224.000113 838.11473a31.901448 31.901448 0 0 0 22.628317-9.374279l67.88134-67.88134c36.724054-36.724054 45.021826-44.859267 45.097688-44.93874a31.998984 31.998984 0 0 0-44.776181-45.729864c-0.32512 0.317895-8.395308 8.229136-45.578142 45.411969l-67.88134 67.884953a31.998984 31.998984 0 0 0 22.628318 54.627301zM255.948523 544.058589h-0.361244c-0.104761 0-11.722364-0.115598-63.658399-0.115598H95.942765a31.998984 31.998984 0 1 1 0-64.00158h95.996952c52.586273 0 63.88237 0.111986 64.337538 0.11921a31.998984 31.998984 0 0 1 31.659414 31.97731v0.361244a32.002596 32.002596 0 0 1-31.988146 31.659414zM767.939492 544.058589a32.002596 32.002596 0 0 1-31.995372-31.666639v-0.361244a31.998984 31.998984 0 0 1 31.659415-31.970085c0.455167 0 11.754876-0.11921 64.34115-0.11921h96.000564a31.998984 31.998984 0 0 1 0 64.00158H831.944685c-51.936034 0-63.553638 0.111986-63.665624 0.115598h-0.335957zM692.999446 363.0176a31.998984 31.998984 0 0 1-22.863126-54.381656c0.317895-0.32512 8.229136-8.395308 45.41197-45.578141l67.88134-67.884953A31.998984 31.998984 0 1 1 828.693489 240.429485l-67.892177 67.88134c-31.020013 31.023625-41.644196 41.759794-44.241539 44.393262l-0.697201 0.722488a31.908673 31.908673 0 0 1-22.863126 9.591025z"
          fill="#1677ff"
          p-id="7811"
        ></path>
      </svg>
      <p>Loading....</p>
    </div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

最后将效果实现在utils/loading.ts:

let count = 0
let loadingTimeoutId: NodeJS.Timeout | number

export const showLoading = () => {
  clearTimeout(loadingTimeoutId)
  if (count === 0) {
    const loading = document.getElementById('loading')
    loading?.style.setProperty('display', 'flex')
  }
  count++
}

export const hideLoading = () => {
  count--
  if (count === 0) {
    loadingTimeoutId = setTimeout(() => {
      const loading = document.getElementById('loading')
      loading?.style.setProperty('display', 'none')
    }, 500)
  }
}

然后还需要封装localstorage:

type Storable = string | number | boolean | null | { [key: string]: Storable } | Storable[]

function checkKey(key: string, action: string) {
  if (!key) {
    throw new Error(`${action}操作失败:缺少key`)
  }
}

export default {
  set(key: string, value: Storable) {
    checkKey(key, 'set')

    if (typeof value === 'undefined') {
      throw new Error(`${key}值为undefined`)
    }

    let valueToStore: string

    switch (typeof value) {
      case 'object':
        if (value === null) {
          valueToStore = 'null'
        } else {
          valueToStore = JSON.stringify(value)
        }
        break
      default:
        valueToStore = value.toString()
    }

    localStorage.setItem(key, valueToStore)
  },

  get<T extends Storable = Storable>(key: string): T | undefined {
    checkKey(key, 'get')

    const rawValue = localStorage.getItem(key)

    if (!rawValue) {
      return undefined
    }

    try {
      if (rawValue === 'true') return true as never as T
      if (rawValue === 'false') return false as never as T
      if (rawValue === 'null') return null as never as T
      return JSON.parse(rawValue) as T
    } catch (error) {
      if (error instanceof SyntaxError) {
        return rawValue as never as T
      }
      throw error
    }
  },

  remove(key: string) {
    checkKey(key, 'remove')

    localStorage.removeItem(key)
  },

  clear(): void {
    localStorage.clear()
  },

  getAllKeys(): string[] {
    return Object.keys(localStorage)
  },

  has(key: string) {
    checkKey(key, 'has')

    return localStorage.getItem(key) !== null
  }
}

接着就可以完善axios的请求拦截器了:

import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'
import { BASE_URL, REQUEST_TIMEOUT, TIMEOUT_ERRORMESSAGE } from '../constant'
import { showLoading } from './loading'
import storage from './localStorage'
// 1.创建axios实例
const instance = axios.create({
  baseURL: BASE_URL,
  timeout: REQUEST_TIMEOUT,
  timeoutErrorMessage: TIMEOUT_ERRORMESSAGE,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

// 2.请求拦截器
// 请求拦截器配置
const requestConfig = (config: InternalAxiosRequestConfig) => {
  // 2.1 InternalAxiosRequestConfig声明isShowLoading和isShowError
  if (config.isShowLoading) showLoading()
  // 2.2 添加ak用于鉴权
  const token = storage.get('AccessToken')
  if (token) config.headers!.Authorization = 'Bearer ' + token
  // 返回设置好的配置
  return config
}
instance.interceptors.request.use(requestConfig, (error: AxiosError) => Promise.reject(error))

接下来开始写响应拦截器:

import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { BASE_URL, CODE_MESSAGE, REQUEST_TIMEOUT, SUCCESSCODE, TIMEOUT_ERRORMESSAGE } from '../constant'

// ...

// 响应拦截器
// 响应拦截器配置
const responseData = async ({ config, data, status, statusText }: AxiosResponse) => {
   // 隐藏loading图标
  hideLoading()
  // 获得状态码
  let code: number = data && data['code'] ? data['code'] : status
  // 0-200内的状态码全设置成200
  if (SUCCESSCODE.includes(data['code'])) code = 200
  // 处理不同的状态码
  switch (code) {
    case 200:
      return data
    case 401:
      // TODO: 刷新令牌
      break
  }
  // 显示错误
  if (config.isShowError === true) {
    const errorMessage =
      data && data['message'] ? data['message'] : CODE_MESSAGE[code] ? CODE_MESSAGE[code] : statusText
    // TODO: 应该弹出提醒
    console.log(errorMessage)
  }
  return Promise.reject(data)
}
instance.interceptors.response.use(responseData, (error: AxiosError) => {
  return Promise.reject(error.message)
})

接着来导出请求方法:

import { IConfig, Result } from '../types'
// ...
export default {
  get<T>(
    url: string,
    params?: object,
    options: IConfig = { isShowLoading: true, isShowError: true }
  ): Promise<Result<T>> {
    return instance.get(url, { params, ...options })
  },
  post<T>(url: string, params?: object, options: IConfig = { isShowLoading: true, isShowError: true }): Promise<T> {
    return instance.post(url, params, options)
  }
}

这里导入了2个类型定义:IConfig, Result

// 返回的结果类型
export interface Result<T> {
 code: number
 data: T
 message: string
}
export interface IConfig {
 isShowLoading?: boolean
 isShowError?: boolean
}

此时可以来测试一下,在App.tsx中编写测试代码:

import { useEffect } from 'react'
import request from './utils/request'

function App() {
  useEffect(() => {
    fetchPosts()
  }, [])
  const fetchPosts = async () => {
    const response = await request.post('/user/login', { username: 'user1', password: '111111' })
    console.log(response)
  }
  return <div className='test'>hello</div>
}

export default App

这里的路由是由nestjs生成的,这个以后再详细说,但如果直接运行就会报错 截屏2024-04-08 20.12.31.png 截屏2024-04-08 20.12.56.png

这是因为没有跨域,所以还需要配置一下vite跨域,具体如下:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    host: 'localhost',
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
  // ...
})

可以看到此时使用了import path from 'path',会报类型错误,这需要安装对应依赖pnpm i @types/node -D此时再来运行,结果如下: 截屏2024-04-08 20.17.16.png 这样就能将数据正常请求回来,但这里应该把请求的逻辑放在api文件夹下:

import { Result, userLoginParamType, userLoginResponseType } from '../types'
import request from '../utils/request'

// 用户登陆
export function userLoginApi(params: userLoginParamType): Promise<Result<userLoginResponseType>> {
  return request.post('/user/login', params)
}

这里还需要把类型定义抽离到types文件夹中

// 用户登陆参数类型
export interface userLoginParam {
  username: string
  password: string
}
// 用户登陆返回结果类型
export interface userLoginResponseType {
  data?: {
    accessToken: string
    refreshToken: string
  }
}

而App.tsx文件中代码如下:

import { useEffect } from 'react'
import { userLoginApi } from './api'

function App() {
  useEffect(() => {
    fetchPosts()
  }, [])
  const fetchPosts = async () => {
    const response = await userLoginApi({ username: 'user1', password: '111111' })
    console.log(response)
  }
  return <div className='test'>hello</div>
}

export default App

结果如下:

截屏2024-04-08 20.24.33.png

4.引入antd

在开发过程中使用组件库能极大减少工作量,这里使用antd

首先安装antd: pnpm i antd, 目前(2024/04/09)版本是^5.16.1

前面封装axios的responseData时候,显示错误是console.log(errorMessage)可以改成antd的message

// request.ts
// ...
// 3.响应拦截器
// 响应拦截器配置
const responseData = async ({ data, status }: AxiosResponse) => {
  // 此时应该是100 - 399之间的状态码
  // 隐藏loading图标
  hideLoading()
  // 获得状态码
  const code: number = data && data['code'] ? data['code'] : status
  // 处理不同的状态码
  switch (code) {
    case 200:
      return data
    default:
      return data
  }
}
instance.interceptors.response.use(responseData, (error: AxiosError) => {
  // 此时应该是400 - 599之间的状态码
  // 在此拦截以后,后面使用时就不需要再try/catch了,只需要判断结果是不是undefined
  hideLoading()
  // 当请求失败且有响应时,处理状态码
  if (error.response) {
    // 对于400及以上的错误码进行处理
    if (error.response.status >= 400) {
      // 处理401刷新token
      if (error.response.status === 401) {
        // TODO:刷新token
      }
      // 弹窗错误消息
      const errorMessage = CODE_MESSAGE[error.response.status] || '未知错误'
      message.error(errorMessage)
    }
  } else {
    // 发生了一些意外错误,如请求被阻止、取消或配置错误
    message.error(CODE_MESSAGE[500])
  }
  // 错误在这里处理完成了不用往下传递了
  // return Promise.reject(error)
})
//constant/index.ts

export const BASE_URL = '/api'
export const REQUEST_TIMEOUT = 8000
export const TIMEOUT_ERRORMESSAGE = '请求超时,请稍后重试'
export const SUCCESSCODE = [100, 399]
export const CODE_MESSAGE: { [key: number]: string } = {
  200: '200 服务器成功返回请求的数据',
  201: '201 新建或修改数据成功',
  202: '202 一个请求已经进入后台排队(异步任务)',
  204: '204 删除数据成功',
  400: '400 发出的请求有错误',
  401: '401 用户没有权限(令牌、用户名、密码错误)',
  403: '403 用户得到授权,但是访问是被禁止的',
  404: '404 发出的请求不存在,服务器没有进行操作',
  406: '406 请求的格式不可得',
  410: '410 请求的资源被永久删除,且不会再得到的',
  422: '422 当创建一个对象时,发生一个验证错误',
  500: '500 服务器发生错误,请检查服务器',
  502: '502 网关错误',
  503: '503 服务不可用,服务器暂时过载或维护',
  504: '504 网关超时'
}

这里对响应拦截器重新进行了设计,当状态码为100-399之间时,代码会进入responseData,当状态码为400-599之间时,代码会进入后面的错误回调函数,将错误在此拦截后,后面使用时就不需要再try/catch了,只需要判断结果是不是undefined即可

接着来测试一下是否好用,先把后端服务器直接停掉,此时状态码必然是500,就不会往后传递了

截屏2024-04-09 11.28.05.png 重新启动后端服务器,但把访问的路径改错,效果如下:

import { Result, userLoginParamType, userLoginResponseType } from '../types'
import request from '../utils/request'

// 用户登陆
export function userLoginApi(params: userLoginParamType): Promise<Result<userLoginResponseType>> {
  return request.post('/user/loginx', params)
}

截屏2024-04-09 11.29.42.png 再把路径改对,结果如下: 截屏2024-04-09 11.30.29.png

5.引入react-router

对于单页面应用,路由是实现切换不同页面的必要组件,本模版使用react-router

首先安装依赖 pnpm i react-router-dom,当前(2024/04/09)版本是"react-dom": "^18.2.0","react-router-dom": "^6.22.3"

接着就可以去引入了,在路由系统中,以下页面是基本页面,应创建在 src/pages 目录中:

  • 欢迎页 :向用户呈现应用首页。
  • 登录页面:为用户提供输入凭证以访问特定资源的界面。
  • 403页面:当正在访问的用户没有足够权限时,展示此页面。
  • 404页面:用户尝试访问不存在的路由时,展示此页面。

欢迎页面 (src/pages/Welcome/Welcome.tsx):

export function Welcome() {
  return <div>欢迎页</div>;
}

登录页面 (src/pages/Login/Login.tsx):

export function Login() {
  return <div>登录页</div>;
}

权限不足页面 (src/pages/403.tsx):

export default function Error403() {
  return <div>403未授权</div>
}

页面未找到 (src/pages/404.tsx):

export default function Error404() {
  return <div>404页面未找到</div>
}

接着在src/router/index.tsx 文件中定义 routes 配置数组,其中包含每个路由的路径和对应的页面组件。

import { Navigate, createBrowserRouter } from 'react-router-dom'
import Error403 from '@/pages/403.tsx'
import Error404 from '@/pages/404.tsx'
import { Welcome } from '@/pages/Welcome/Welcome.tsx'
import { Login } from 'src/pages/Login/Login'

const routes = [
  {
    path: '/',
    element: <Navigate to='/welcome' />
  },
  {
    path: '/welcome',
    element: <Welcome />
  },
  {
    path: '/login',
    element: <Login />
  },
  {
    path: '/403',
    element: <Error403 />
  },
  {
    path: '/404',
    element: <Error404 />
  },
  {
    path: '*',
    element: <Navigate to={'/404'} />
  }
]
export default createBrowserRouter(routes)

此时引入时报错,是因为别名@的类型没有匹配上:

// vite.config.ts
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
    // ...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
  // ...
})

// tsconfig.json
{
    // ...
    "compilerOptions": {
        /* 和vite中的alias配合,避免报类型错误 */
        "baseUrl": "./",
        "paths": {
          "@/*": ["./src/*"]
        }
    }
}

此时就正常了,有了路由配置文件之后,需要将其集成到React应用中。这是通过在 App.tsx 文件中导入RouterProvider 组件完成的。

import { RouterProvider } from 'react-router-dom'
import router from './router'

function App() {
  return <RouterProvider router={router} />
}

export default App

此时访问一下: http://localhost:5173/welcome 一切正常

截屏2024-04-09 12.04.22.png 最后,为了提高用户体验,可以使用Ant Design的React UI库中的Result组件来优化403和404页面。

import { Button, Result } from 'antd'
import { useNavigate } from 'react-router-dom'

export default function Error403() {
  const navigate = useNavigate()
  const handleClick = () => {
    navigate('/')
  }
  return (
    <Result
      status='403'
      title='403'
      subTitle='抱歉,您无权访问这个页面。'
      extra={
        <Button type='primary' onClick={handleClick}>
          回到首页
        </Button>
      }
    />
  )
}
import { Button, Result } from 'antd'
import { useNavigate } from 'react-router-dom'

export default function Error404() {
  const navigate = useNavigate()
  const handleClick = () => {
    navigate('/')
  }
  return (
    <Result
      status='404'
      title='404'
      subTitle='抱歉,您访问的页面不存在。'
      extra={
        <Button type='primary' onClick={handleClick}>
          回到首页
        </Button>
      }
    />
  )
}

截屏2024-04-09 12.07.07.png

6.引入zustand

zustand 是一个简洁的状态管理库

首先安装依赖 pnpm i zustand,当前(2024/04/09)版本为"zustand": "^4.5.2"

接着在store目录下,创建user.tsindex.ts文件,index.ts的代码只需将useUserStoreuser.ts中导出即可:

export { useUserStore } from './user'

user.ts文件中,创建用户状态管理的核心代码:

// 引入zustand库和Immer中间件
import { create } from 'zustand'

type UserInfo = {
  username: string
  avatar: string
}

type Action = {
  updateUserInfo?: (userInfo: UserInfo) => void
  updateUserName: (username: string) => void
}

interface State {
  userInfo: UserInfo
}

export const useUserStore = create<State & Action>()(
  set => ({
    userInfo: { username: '默认用户名', avatar: 'http://xxxx.com/yy.jpg' },
    updateUserName: username =>
      set(state => ({
        ...state, // 再次使用展开运算符复制所有既有状态
        userInfo: {
          ...state.userInfo, // 复制 userInfo 对象内的其他属性
          username: username // 只更新 username 属性
        }
      }))
  })
)

接着在welcome组件调用:

import { useUserStore } from '@/store'

export const Welcome = () => {
  const { userInfo, updateUserName } = useUserStore()
  const updateUserInfo = () => {
    updateUserName('修改后的用户名')
  }
  return (
    <div>
      欢迎页{userInfo.username}
      <button onClick={updateUserInfo}>更改用户信息</button>
    </div>
  )
}

屏幕录制2024-04-09 12.55.46.gif

7.区分开发环境

在dev/prod/stag环境下,配置的参数和资源不同,需要进行拆分

首先在项目根目录中创建三个文件,分别对应开发环境(.env.development)、生产环境(.env.production)和预发布环境(.env.stag)。

  • NODE_ENV:该变量用于设置当前的环境模式,例如开发模式(development)或生产模式(production)。
  • VITE_BASE_API 服务器接口地址,根据不同的环境设置不同的url。

三个文件内容如下:

# .env.production
# 设置NODE_ENV环境模式
NODE_ENV=production

# 接口API地址
VITE_BASE_API=/api
# .env.development
# 设置NODE_ENV环境模式
NODE_ENV=development

# 接口API地址
VITE_BASE_API=http://localhost:5173/api
# .env.stag
# 设置NODE_ENV环境模式
NODE_ENV=stag

# 接口API地址
VITE_BASE_API = /api

接着所有的配置内容全放在src/config文件夹,在config目录中创建3个文件:index.ts(集合了后面两个配置)axios.config.ts(axios配置)app.config.ts(应用级配置)

// index.config.ts(整合配置)
import axiosConfig from './axios.config.ts'
import appConfig from './app.config.ts'

export default {
  ...appConfig,
  ...axiosConfig
}
// app.config.ts(应用级配置)
export default {
  // 应用的路由白名单,白名单内的路由可以在未登录的情况下访问
  routeWhiteList: ['/login', '/register', '/callback', '/404', '/403']
}
// axios.config.ts(axios配置)
import { REQUEST_TIMEOUT, SUCCESSCODE } from '@/constant'

export default {
  // API接口地址
  baseURL: import.meta.env.VITE_BASE_API,

  // 网络请求的超时时间
  requestTimeout: REQUEST_TIMEOUT,

  // 请求超时时的错误信息
  timeoutErrorMessage: '请求超时,请稍后重试',

  // 请求成功时服务端返回的状态码
  successCode: SUCCESSCODE
}

现在就可以将request.ts/request.ts配置抽离出来:

// ...
import axiosConfig from '@/config/axios.config'

// 1.创建axios实例
const instance = axios.create({
  baseURL: axiosConfig.baseURL,
  timeout: axiosConfig.requestTimeout,
  timeoutErrorMessage: axiosConfig.timeoutErrorMessage,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})
// ...

同时为了验证axios请求没有问题,将原来App.tsx的请求内容写在welcome.tsx中:

import { userLoginApi } from '@/api'
import { useUserStore } from '@/store'
import { useEffect } from 'react'

export const Welcome = () => {
  const { userInfo, updateUserName } = useUserStore()
  const updateUserInfo = () => {
    updateUserName('修改后的用户名')
  }
  useEffect(() => {
    fetchPosts()
  }, [])
  const fetchPosts = async () => {
    const response = await userLoginApi({ username: 'user1', password: '111111' })
    console.log(response)
  }
  return (
    <div>
      欢迎页:{userInfo.username}
      <button onClick={updateUserInfo}>更改用户信息</button>
    </div>
  )
}

结果如下: 截屏2024-04-09 14.37.01.png