Vite创建react项目

1.背景

为什么要是用vite来创建react项目,主要是cra已经不太适配react了,react官网也放弃了cra的创建,原因是react19和cra不太兼容。所以如果大家用的是18的话,还是可以继续用cra来创建的。参考官网

另外的话用vite默认创建的话就是不支持服务端渲染(SSR),没有seo(适合toB项目),如果项目要支持seo的话,需要配置,不如建议大家使用nextjs ,或者remix来创建项目(适合toC项目)

2.创建命令

vue 复制代码
 npm create vite@latest

下面跟着选就行了,输入项目名,选择react+ts(ts是否要看大家自己的选择,建议还是选上,ts是趋势。小项目或者时间紧的话可以直接使用js也可以。)

3.生成git仓库

由于这里生成的项目没有git仓库,所以我们git init来生成一个仓库。然后git add . ,git commit- m"" 来进行初次提交。

4.规划项目目录

在src文件夹下面建立这些文件夹,详细的作用对应如下:

5.删除多余的东西

main.tsx中一些删除掉的东西就没必要引入了

6.支持scss(其他的less等也可以,看自己熟悉哪个)

安装依赖

vue 复制代码
npm i sass -D

测试一下有没有用

错误使用方式(同名类名会导致样式覆盖):

正确使用方式(改为css module方式,这样子父子组件类名相同也不会样式覆盖了):

7.加入UI组件库,安装antDesign

a.安装依赖

vue 复制代码
npm i antd

b.测试(这样子你能在页面上看到蓝色的按钮就说明导入成功了)

vue 复制代码
import { Button } from 'antd';
function App() {
  return (
    <>
    <div className="ceshi">你好</div>
    <Button type="primary">Button</Button>
    </>
  )
}

export default App

c.兼容react19,请查看官方文档(因为vite初始化的react项目目前是19版本的了,所以需要兼容)

安装依赖

vue 复制代码
npm install @ant-design/v5-patch-for-react-19 --save

在main.tsx中导入

vue 复制代码
import '@ant-design/v5-patch-for-react-19';

8.vite配置@别名

配置别名分为两种配置,1.项目支持使用@符号代表src 2.让vscode编辑器支持输入@符号时有下一个目录的提示

1.项目支持使用@符号代表src

a.先安装ts中支持path的依赖

<font style="color:rgb(6, 6, 7);">@types/node</font>(用于 TypeScript 环境中的 <font style="color:rgb(6, 6, 7);">path</font> 模块)。如果没有安装,可以通过以下命令安装:

vue 复制代码
npm install --save-dev @types/node

b.配置vite.config.ts

vue 复制代码
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path' // 导入 path 模块

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'), // 配置 '@' 别名指向项目根目录下的 src 文件夹
    },
  },
})

c.测试一下

2.让vscode编辑器支持输入@符号时有下一个目录的提示

可以看到上面截图有波浪线非常不舒服,并且输入@符号时没有下一个文件夹的提示

往这文件中(tsconfig.app.json)加入以下配置:

vue 复制代码
"compilerOptions": {
    "baseUrl": ".", // 设置基准路径
    "paths": {
      "@/*": ["src/*"] // 配置 '@' 别名指向 src 文件夹
    },
  }

说明
<font style="color:rgb(6, 6, 7);">tsconfig.json</font> 是项目的主配置文件,用于定义整个项目的 TypeScript 编译选项。:
<font style="color:rgb(6, 6, 7);">tsconfig.app.json</font> 用于专门配置客户端应用代码。它扩展或覆盖了 <font style="color:rgb(6, 6, 7);">tsconfig.json</font> 中的设置,以满足应用代码的特定需求。例如,你可以在这里设置 <font style="color:rgb(6, 6, 7);">baseUrl</font><font style="color:rgb(6, 6, 7);">paths</font> 来定义路径别名
<font style="color:rgb(6, 6, 7);">tsconfig.node.json</font> 用于配置 Node.js 环境中的 TypeScript,例如 Vite 的配置文件 <font style="color:rgb(6, 6, 7);">vite.config.ts</font>。它确保在 Node.js 环境中运行的代码(如构建脚本)使用正确的编译选项

成功的情况如下图(有路径提示了):

9.样式初始化(消灭浏览器差异以及盒子原有样式)

(1)normalize.css保证我们的代码在各浏览器的一致性

安装依赖

vue 复制代码
npm i normalize.css

main.ts引入

vue 复制代码
import 'normalize.css'

(2)消除盒子原有样式(简化版) 1.建立reset.scss,写入以下样式:

vue 复制代码
* {
    padding: 0;
    margin: 0;
    list-style: none;
    box-sizing: border-box;
  }
  html,
  body {
    height: 100%;
  }
  #root{
    height: 100%;
  }

2.在main.ts中引入:import '@/assets/css/reset.scss'

vue 复制代码
import '@/assets/css/reset.scss'

10.router-配置基础路由页面

安装依赖

vue 复制代码
npm install react-router-dom

在pages页面下建立这三个文件夹(Home,Layout,Login ,记得是index.tsx文件)并写入最简单的代码

vue 复制代码
function Layout(){
    return (
        <div>Layout页面</div>
    )
}
export default Layout

在router页面下建立index.tsx文件写入以下代码

vue 复制代码
import { createBrowserRouter } from 'react-router-dom'

import Home from '@/pages/Home'
import Layout from '@/pages/Layout'
import Login from '@/pages/Login'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout></Layout>,
  },
  {
    path: '/home',
    element: <Home></Home>,
  },
  {
    path: '/login',
    element: <Login></Login>,
  },
])

export default router

在main.tsx的导入并使用(可以把app.tsx和index.scss删掉了,用不到了)

vue 复制代码
import { createRoot } from 'react-dom/client'
import 'normalize.css'
import '@/assets/css/reset.scss'
import '@ant-design/v5-patch-for-react-19'
import router from '@/router'
import { RouterProvider } from 'react-router-dom'

createRoot(document.getElementById('root')!).render(
  <RouterProvider router={router}></RouterProvider>
)

11.加入状态管理工具(这里以rtk+redux+react-redux为例)

a.安装依赖

vue 复制代码
npm install @reduxjs/toolkit react-redux

b.在store文件夹下面建立modules文件夹(用于区分仓库)和index.tsx(仓库的统一出口),文件夹如下

c.如上图,在modules下建立user.tsx,写入以下代码

vue 复制代码
import { createSlice } from '@reduxjs/toolkit'

export const userSlice = createSlice({
  name: 'user',
  initialState: {
    value: 0,
    userInfo: {},
  },
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    setUserInfo: (state, action) => {
      state.userInfo = action.payload
    },
  },
})

export const { increment, decrement, setUserInfo } = userSlice.actions

// 异步写法
const fetchUser = () => {
  return async (dispatch: any) => {
    const res = await Promise.resolve()  // 假设这里去发了后台接口获取用户信息
    dispatch(setUserInfo(res))
  }
}

export { fetchUser }

export default userSlice.reducer

d.在router中的index.tsx中写入以下代码:

vue 复制代码
// src/store.js
import { configureStore } from '@reduxjs/toolkit'
import userSlice from './modules/user'

const store = configureStore({
  reducer: {
    user: userSlice,
  },
})
export default store
// 推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

e.在主应用中使用 Redux Store

<font style="color:rgba(0, 0, 0, 0.9);">src/main.tsx</font> 文件d中,使用 <font style="color:rgba(0, 0, 0, 0.9);">Provider</font> 组件将 Redux Store 传递给 React 组件树

vue 复制代码
import { createRoot } from 'react-dom/client'
import 'normalize.css'
import '@/assets/css/reset.scss'
import '@ant-design/v5-patch-for-react-19'
import router from '@/router'
import { RouterProvider } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from '@/store'

createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <RouterProvider router={router}></RouterProvider>
  </Provider>
)

f.在login页面中测试使用(异步和同步在react中调用时写法是一样的)

vue 复制代码
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement, fetchUser } from '@/store/modules/user'
import { RootState, AppDispatch } from '@/store'

function Login() {
  // 使用 RootState 类型推断 state 的类型
  const count = useSelector((state: RootState) => state.user.value)
  // 使用 AppDispatch 类型推断 dispatch 的类型
  const dispatch = useDispatch<AppDispatch>()
  return (
    <div>
      Login页面
      {count}
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(fetchUser())}>获取用户数据</button>
    </div>
  )
}
export default Login

12.加入axios进行发送请求

a.建立request.ts

具体的封装可看:https://blog.csdn.net/weixin_43239880/article/details/130143352?spm=1001.2014.3001.5501

javascript 复制代码
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { message, Modal } from 'antd'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 扩展 axios 的配置类型,添加自定义属性
declare module 'axios' {
  export interface AxiosRequestConfig {
    meta?: {
      responseAll?: boolean
    }
  }
}

// 定义后端返回的数据结构
interface ApiResponse<T = unknown> {
  code: number
  message: string
  data: T
}

// 创建 axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // api 的 base_url,Vite 使用 import.meta.env
  timeout: 5000, // 请求超时时间
})

// request 拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 如果登录了,有 token,则请求携带 token
    const state = store.getState()
    const userInfo = state.user?.userInfo as { token?: string } | undefined
    const token = userInfo?.token || getToken()
    
    if (token) {
      // 让每个请求携带 token--['X-Token'] 为自定义 key 请根据实际情况自行修改
      config.headers['X-Token'] = token
    }
    return config
  },
  (error: AxiosError) => {
    // Do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  /**
   * 下面的注释为通过 response 自定义 code 来标示请求状态,当 code 返回如下情况为权限有问题,登出并返回到登录页
   * 如通过 xmlhttprequest 状态码标识 逻辑可写在下面 error 中
   */
  (response: AxiosResponse<ApiResponse>) => {
    const res = response.data
    
    // 处理异常的情况
    if (res.code !== 200) {
      message.error({
        content: res.message || '请求失败',
        duration: 5,
      })
      
      // 403:非法的 token; 50012:其他客户端登录了; 401:Token 过期了;
      if (res.code === 403 || res.code === 50012 || res.code === 401) {
        Modal.confirm({
          title: '确定登出',
          content: '你已被登出,可以取消继续留在该页面,或者重新登录',
          okText: '重新登录',
          cancelText: '取消',
          onOk: () => {
            // 清除用户信息和 token
            // 这里需要根据你的实际 Redux action 来调整
            // store.dispatch(logout()) 
            localStorage.removeItem('token')
            location.reload() // 为了重新实例化 react-router 对象 避免 bug
          },
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      // 默认只返回 data,不返回状态码和 message
      // 通过 meta 中的 responseAll 配置来决定后台是否返回所有数据(包括状态码,message 和 data)
      const isBackAll = response.config.meta?.responseAll
      if (isBackAll) {
        return res as unknown as AxiosResponse
      } else {
        return res.data as unknown as AxiosResponse
      }
    }
  },
  (error: AxiosError) => {
    console.log('err' + error) // for debug
    message.error({
      content: error.message || '网络请求失败',
      duration: 5,
    })
    return Promise.reject(error)
  }
)

export default service

b.定义api文件user.ts:

javascript 复制代码
import request from '@/utils/request'
import { SuccessResponse, PageParams, PageData } from './common-types'

// 定义用户相关的接口类型
export interface LoginParams {
  username: string
  password: string
}

export interface LoginResponse {
  token: string
  userInfo: {
    id: number
    username: string
    avatar?: string
  }
}

export interface UserInfo {
  id: number
  username: string
  email?: string
  avatar?: string
}

// ========== 场景1: 复杂返回数据,必须定义类型(推荐) ==========
/**
 * 用户登录
 * ✅ 返回数据复杂,定义类型可以获得完整的智能提示
 */
export function login(data: LoginParams) {
  return request<LoginResponse>({
    url: '/user/login',
    method: 'post',
    data,
  })
}

/**
 * 获取用户信息
 * ✅ 返回数据有固定结构,定义类型
 */
export function getUserInfo() {
  return request<UserInfo>({
    url: '/user/info',
    method: 'get',
  })
}

// ========== 场景2: 简单返回数据,可以内联类型 ==========
/**
 * 修改密码
 * ✅ 返回数据简单,可以直接内联类型,不用单独定义
 */
export function changePassword(oldPwd: string, newPwd: string) {
  return request<{ success: boolean }>({
    url: '/user/password',
    method: 'put',
    data: { oldPwd, newPwd },
  })
}

/**
 * 检查用户名是否存在
 * ✅ 返回单个布尔值,内联类型即可
 */
export function checkUsername(username: string) {
  return request<{ exists: boolean }>({
    url: '/user/check',
    method: 'get',
    params: { username },
  })
}

// ========== 场景3: 使用通用类型 ==========
/**
 * 删除用户
 * ✅ 使用通用的 SuccessResponse 类型
 */
export function deleteUser(id: number) {
  return request<SuccessResponse>({
    url: `/user/${id}`,
    method: 'delete',
  })
}

/**
 * 获取用户列表(分页)
 * ✅ 使用通用的 PageData 泛型类型
 */
export function getUserList(params: PageParams) {
  return request<PageData<UserInfo>>({
    url: '/user/list',
    method: 'get',
    params,
  })
}

// ========== 场景4: 不关心返回值,可以省略类型 ==========
/**
 * 用户登出
 * ⚠️ 不关心返回值,可以不定义类型(返回 unknown)
 */
export function logout() {
  return request({
    url: '/user/logout',
    method: 'post',
  })
}

/**
 * 上报用户行为日志
 * ⚠️ 不需要处理返回值,可以省略类型
 */
export function reportLog(action: string) {
  return request({
    url: '/log/report',
    method: 'post',
    data: { action, timestamp: Date.now() },
  })
}

// ========== 场景5: 返回完整响应(包括 code 和 message) ==========
/**
 * 获取用户信息(返回完整响应,包括 code 和 message)
 * 💡 当需要获取后端返回的状态码和提示信息时使用
 */
export function getUserInfoWithFullResponse() {
  return request<UserInfo>({
    url: '/user/info',
    method: 'get',
    meta: {
      responseAll: true, // 返回完整的响应数据
    },
  })
}

这是关于axios和ts使用的一些方案对比

最好的就是使用工具自动生成api的ts的类型,这样子就不用手动维护了。

现在我使用的是混合策略,关键是因为有时候后端没有swagger文档,或者很多项目不太规范

次优方案:混合策略

● 如果后端没有文档 → 使用这个

● 核心功能定义类型(登录、用户信息等)

● 次要功能简化处理(日志上报、简单操作等)

● 通用结构复用类型(分页、成功响应等)

13.加入代码检测工具lint-staged+husky

参考:https://blog.csdn.net/weixin_43239880/article/details/130263271?spm=1001.2014.3001.5501

1.安装依赖包:

javascript 复制代码
npm i lint-staged --save-dev

2.在 package.json 中配置 lint-staged,利用它来调用 eslint 和 stylelint 去检查暂存区内的代码

javascript 复制代码
{
  // ...
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix"
    ]
  },
}

3.安装配置 husky

javascript 复制代码
npm i husky --save-dev

4.在package.json中配置快捷命令,用来在安装项目依赖时生成 husky 相关文件

javascript 复制代码
{
  // ...
  "scripts": {
    // ...
    "prepare": "husky && echo 'pnpm lint-staged' > .husky/pre-commit && chmod +x .husky/pre-commit"
  },
}

5.有时候配置的命令可能随着版本升级会变,但是在git commit时出现这个检测就说明弄成功了

14.template仓库

仓库地址:https://gitee.com/rui-rui-an/react_vite_template.git

相关推荐
IT_陈寒1 小时前
Redis性能提升40%!我用这5个冷门但高效的配置优化了千万级QPS应用
前端·人工智能·后端
GISer_Jing1 小时前
SSE Conf 大会分享——AI Native 3D开发革命,让创意不再被技术门槛阻挡(推荐!!!)
前端·人工智能·3d·信息可视化
克喵的水银蛇1 小时前
Flutter 通用网络图片封装实战:带占位 / 错误 / 缓存的 CachedImageWidget
开发语言·前端·javascript
kong@react1 小时前
springbpoot项目,富文本,xss脚本攻击防护,jsoup
java·前端·spring boot·xss
涵涵(互关)1 小时前
后端返回的id到前端接收时,id改变了
前端·状态模式
码上成长1 小时前
从零实现:react&Ts--批量导入 & Excel 模版下载功能
javascript·react.js·excel
拾忆,想起1 小时前
Dubbo灰度发布完全指南:从精准引流到全链路灰度
前端·微服务·架构·dubbo·safari
liudongyang1231 小时前
EasyExcel使用模版填充的方式,导致单元格边框消失
前端·html