react快速开始项目模板

代码仓库

gitee

创建项目

首先保证安装了node, 然后使用vite创建项目

vite

shell 复制代码
npm create vite react-learn
cd react-learn
npm i 

目录结构

一个完整的前端项目需要:

  • 状态管理
    在全局维护共有的状态(数据), 让页面组件之间共享数据, 我们使用pinia
  • 路由
    路由让页面之间可以进行跳转, 我们使用vue-router
  • 样式
    样式让页面更美观, 我们使用tailwindcss
  • 网络请求
    前端需要通过网络请求的方式和后端进行数据交互, 从而实现功能, 我们使用axios
text 复制代码
├── dev-dist/               # 开发环境构建输出目录
├── node_modules/           # Node.js依赖包目录
├── public/                 # 静态资源目录,不会被构建工具处理
├── src/
    ├── admin/                  # 存放后台管理页面
    ├── api/                    # 接口请求逻辑
    ├── assets/                 # 静态资源(图片、字体等)
    ├── components/             # 公共组件
    ├── includes/               # 包含文件,存放外部库
    ├── lib/                    # 存放项目内的一些公共资源
    ├── locales/                # 国际化语言包
    ├── mocks/                  # 模拟数据
    ├── pages/                  # 存放普通的页面组件
    ├── router/                 # 路由配置
    ├── store/                  # 状态管理(Pinia)
    ├── styles/                 # 全局样式或CSS文件
    ├── utils/                  # 工具函数
    ├── App.jsx                 # 根组件
    ├── main.js                 # 项目入口文件
    ├── middleware.js           # 中间件逻辑(如路由守卫)
    └── settings.js             # 项目设置或配置文件
├── .gitignore              # Git忽略文件配置
├── index.html              # 项目入口HTML文件
├── package.json            # 项目配置及依赖声明
├── postcss.config.js       # PostCSS配置文件
├── README.md               # 项目说明文档
├── tailwind.config.js      # Tailwind CSS配置文件
├── vite.config.js          # Vite构建工具配置文件

配置

路径别名

配置一下路径别名, 用@/表示src/目录

shell 复制代码
# node的类型声明, 防止使用node的依赖后报错
pnpm i @types/node --save-dev
js 复制代码
import {defineConfig} from 'vite'
import {join} from 'path';
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
    plugins: [vue()],
    // 路径别名
    resolve: {
        alias: {
            '@':
                join(__dirname, 'src'),
        }
    }
})

PWA配置

shell 复制代码
# v1
pnpm i vite-plugin-pwa --save-dev
js 复制代码
import {defineConfig} from 'vite'
import {join} from 'path';
import react from '@vitejs/plugin-react'
import {VitePWA} from "vite-plugin-pwa";

// https://vite.dev/config/
export default defineConfig({
    plugins: [
        react(),
        VitePWA({
            registerType: 'autoUpdate',
            devOptions: {
                // 生成清单文件
                enabled: true
            },
            manifest: {
                name: "vue-quick-start",
                theme_color: '#ff5e3a',
                icons: [
                    {
                        src: 'assets/logo.png',
                        size: '192x192',
                        type: 'image/png'
                    }
                ]
            },
            workbox: {
                globPatterns: ['**/*.{js,css,html,png,jpg}']
            }
        })
    ],
    resolve: {
        alias: {
            '@':
                join(__dirname, 'src'),
        }
    }
})

项目的设置

js 复制代码
// settings.js
export default {
    routeMode: 'history', // 路由模式
    BaseURL: 'http://localhost:4000', // 后端请求地址
    timeout: 5000, // 请求超时时间
}

tailwind

使用tailwind3(如果你要用4的也可以, 不过两个安装有点区别)

shell 复制代码
pnpm install -D tailwindcss@3 postcss autoprefixer
pnpm dlx tailwindcss@3 init -p

tailwind.config.js

js 复制代码
/** @type {import('tailwindcss').Config} */
export default {
    content: [
        "./index.html",
        "./src/**/*.{vue,js,ts,jsx,tsx}",
    ],
    theme: {
        extend: {},
    },
    plugins: [],
}

styles/base.css

css 复制代码
@tailwind base;

@layer base {
    h1 {
        @apply text-2xl;
    }

    h2 {
        @apply text-xl;
    }

    h3 {
        @apply text-lg;
    }

    h4 {
        @apply text-base;
    }

    h5 {
        @apply text-sm;
    }

    h6 {
        @apply text-xs;
    }
}

@tailwind components;
@tailwind utilities;

body {
    @apply h-full w-full p-0 m-0;
}

/* 覆盖默认的最大宽度限制 */
@media (min-width: 1536px) {
    .container {
        max-width: 100%;
    }
}

/* 页面主内容 */
.container {
    width: 100%;
    height: 100%;
    background-color: #f5f7fb;
    padding: 20px;

    .container-wrapper {
        width: 100%;
        height: 100%;
        padding: 15px 30px 0;
        background-color: #fff;
        border-radius: 8px;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
}

/* 设置打印控件样式 */
.plugin-download {
    width: 500px !important;

    a:hover {
        text-decoration: underline;
    }
}

记得在src/main.js引入

js 复制代码
import {StrictMode} from 'react'
import {createRoot} from 'react-dom/client'
import App from './App.jsx'
import '@/styles/base.css'

createRoot(document.getElementById('root')).render(
    <StrictMode>
        <App/>
    </StrictMode>,
)

路由 router

使用react-router-dom进行路由跳转, 并且我们会实现文件路由, 自动扫描目录下的page.jsx文件, 然后注册为路由,

有两个页面目录, 一个是admin, 一个是pages, admin目录是后台管理页面, pages目录是普通的页面组件

shell 复制代码
# v7
pnpm i react-router-dom@7

文件路由

原生方案

文件路由就是根据目录结构, 自动扫描并注册路由, 不需要我们一个一个手动声明注册

实现的关键是

js 复制代码
import.meta.glob("xxx")

这是由vite提供的方法, 可以扫描获取文件, webpack也有类似的方法

js 复制代码
require.context()

我们使用的是vite, 使用import.meta.glob就行, 现在来实现文件扫描功能

插件方案

使用vite-plugin-pages, 也可以实现文件路由注册

shell 复制代码
npm install -D vite-plugin-pages
npm install react-router react-router-dom

vite.config

js 复制代码
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import {VitePWA} from "vite-plugin-pwa";
import {join} from 'path';
import pages from 'vite-plugin-pages'

// https://vite.dev/config/
export default defineConfig({
    plugins: [
        react(),
        ///
        /*pages({
            // 自定义配置
            // dir: 'src/pages',          // 路由组件目录
            extensions: ['jsx', 'tsx'], // 支持文件后缀
            // exclude: ['components'],  // 排除组件目录
            base: process.env.VITE_APP_BASE_URL
        }),*/
        pages({
            // 注册多个目录, 有不同的路由前缀
            dirs: [
                // basic
                {dir: 'src/pages', baseRoute: ''},
                // with custom file pattern
                {dir: 'src/admin/', baseRoute: 'admin'},
            ],
        }),
    ],
    resolve: {
        alias: {
            '@':
                join(__dirname, 'src'),
        }
    },
})

src/components/router-guard.jsx

js 复制代码
import {matchPath, Route, Routes, useLocation, useNavigate, useRoutes} from "react-router-dom";
import {Suspense, useEffect} from "react";
import routes from "~react-pages";
import Login from "../pages/login.jsx";
import NotFount from "../pages/not-fount.jsx";

const isAuthenticated = () => {
    return localStorage.getItem("token") !== null; // 示例逻辑
};

// 全局路由组件
function RouterGuard() {
    const navigate = useNavigate();
    const location = useLocation();

    // 添加 requiresAuth 属性
    const toNeedAuth = (r) => {
        const route = {...r, requiresAuth: true};
        if (route.children) {
            route.children = route.children.map(c => toNeedAuth(c));
        }
        return route;
    }
    const authRoutes = routes.map(toNeedAuth)

    useEffect(() => {
        /*
        // 无法匹配嵌套的路由
        const currentRoute = authRoutes.find(route =>
            matchPath({path: route.path, end: true}, location.pathname)
        );
        console.log(currentRoute)
        */
        console.log(location.pathname)

        // 全局导航守卫逻辑
        /*
        if (currentRoute?.requiresAuth && !isAuthenticated()) {
            navigate("/login", {replace: true});
        }
        */
        // 后续添加更多不需要身份验证的页面
        if (location.pathname !== '/login' && !isAuthenticated()) {
            navigate("/login", {replace: true});
        }
    }, [location, navigate]); // 监听路由变化

    return (
        <Suspense fallback={<p>Loading...</p>}>
            {useRoutes(
                [
                    ...authRoutes,
                    {
                        path: 'login',
                        element: <Login/>
                    },
                    {
                        path: "*",
                        element: <NotFount/>
                    }
                ]
            )}
        </Suspense>
    );
}

export default RouterGuard;

app.jsx

js 复制代码
import RouterGuard from "./components/router-guard.jsx";

function App() {

    return (
        <RouterGuard/>
    )
}

export default App

main.jsx

js 复制代码
import {StrictMode} from 'react'
import {createRoot} from 'react-dom/client'
import App from './App.jsx'
import '@/styles/base.css'
import {BrowserRouter} from "react-router-dom";

createRoot(document.getElementById('root')).render(
    <StrictMode>
        <BrowserRouter>
            <App/>
        </BrowserRouter>
    </StrictMode>,
)

状态管理 store

之前学习react的时候会发现原生的组件传值会遇到很麻烦的情况, 比如兄弟/跨级传值, 而我们可以使用全局状态管理的方案了解决

这里我们使用zustand

shell 复制代码
pnpm i zustand

src/store/count/index.js

js 复制代码
import {create} from 'zustand'

export const useCountStore = create((set) => ({
    count: 0,
    increment: () => set((state) => ({count: state.count + 1})),
    reset: () => set({count: 0}),
}))

测试 /pages/index.jsx

js 复制代码
import React from 'react';
import {useCountStore} from "../store/count/index.js";

const Index = () => {
    const {count, increment, reset} = useCountStore()
    localStorage.setItem("token", "123")

    return (
        <div className={`flex items-center flex-col`}>
            <div className={``}>
                {count}
            </div>
            <div className={`flex gap-x-4`}>
                <button
                    className={`bg-blue-400 py-1 px-2 rounded-lg`}
                    onClick={() => increment()}>+1
                </button>
                <button
                    className={`bg-blue-400 py-1 px-2 rounded-lg`}
                    onClick={() => reset()}>reset
                </button>
            </div>
        </div>
    );
};

export default Index;

网络请求 api

使用axios进行网络请求, 一般情况下, 前后端是分人分组开发的, 前端请求接口地址是后端提供的

如果你此时后端还没有接口, 就需要自己用假数据或者用json-server来模拟测试

shell 复制代码
# v1
pnpm i axios

封装

js 复制代码
// request.js
import axios from 'axios'
import settings from "../settings.js";

const request = axios.create({
    baseURL: settings.BaseURL, // 设置基础URL
    timeout: settings.timeout, // 请求超时时间
})

// 请求拦截器
request.interceptors.request.use(
    (config) => {
        // 在发送请求之前做些什么,例如添加 token
        const token = localStorage.getItem('token')
        if (token) {
            config.headers['Authorization'] = `Bearer ${token}`
        }
        return config
    },
    (error) => {
        // 对请求错误做些什么
        return Promise.reject(error)
    }
)

// 响应拦截器
request.interceptors.response.use(
    (response) => {
        // 对响应数据做处理
        return response.data
    },
    (error) => {
        // 对响应错误做处理
        return Promise.reject(error)
    }
)

export default request

测试

js 复制代码
/*
 * @Author Malred · Wang
 * @Date 2025-06-23 16:42:29
 * @Description 
 * @Path src/pages/login.jsx
 */
import React, {useState} from 'react';
import auth from '@/api/auth/index.js'
import {useNavigate} from "react-router-dom";

const Login = () => {
    const navigate = useNavigate();

    const [username, setUsername] = useState('')
    const [password, setPassword] = useState('')

    return (
        <div className={`flex items-center flex-col`}>
            <h1>login</h1>
            <form
                onSubmit={async (e) => {
                    e.preventDefault(); // 阻止默认提交行为
                    const res = await auth.login({username, password})
                    localStorage.setItem('token', res.token)
                    navigate('/')
                }}
                className={`flex flex-col gap-y-2`}
            >
                账号
                <input className={`px-2 py-1 border rounded-md`} type="text" value={username}
                       onChange={(e) => setUsername(e.target.value)}/>
                密码
                <input className={`px-2 py-1 border rounded-md`} type="password" value={password}
                       onChange={(e) => setPassword(e.target.value)}/>
                <button
                    type={"submit"}
                    className={`text-white bg-blue-400 py-1 px-2 rounded-lg`}
                >
                    登录
                </button>
            </form>
        </div>
    );
};

export default Login;

后端服务可以参考我之前的后端web单体教学代码模板:
rust-web-starter
go-web-starter

下一步

  • json-server模拟接口
  • 代码生成器, 批量生成重复页面和代码

社群

你可以在这些平台联系我:

相关推荐
hackchen15 分钟前
从0到1解锁Element-Plus组件二次封装El-Dialog动态调用
前端·vue.js·elementui
君子宜耘心15 分钟前
el-table虚拟列表封装
前端
黄瓜沾糖吃18 分钟前
大佬们指点一下倒计时有什么问题吗?
前端·javascript
温轻舟19 分钟前
3D词云图
前端·javascript·3d·交互·词云图·温轻舟
buibui19 分钟前
打包一个工具类
前端
巴别塔的饿灵21 分钟前
Webpack[TBC]
前端
LinHan21 分钟前
我的浏览器插件 Horizon-Hop 又又又更新啦!
前端
一个小潘桃鸭22 分钟前
需求:el-upload实现上传/粘贴图片功能
前端
胡清波23 分钟前
# vue 的 Diff 算法
前端·面试
浩龙不eMo24 分钟前
✅ Lodash 常用函数精选(按用途分类)
前端·javascript