基于Vite4、Typescript、React18、react-router-dom6.4、Redux4、Reduxjs/toolkit、Ant Design

基于Vite、Typescript、React、react-router-dom、Redux、Reduxjs/toolkit、Ant Design从零搭建前端开发工程

vite项目初始化

本机环境

  • node v16.14.2
  • npm 8.5.0
  • yarn 1.22.18

vite 项目初始化

bash 复制代码
yarn create vite

按照步骤提示输入项目名,框架选择 React,js使用 TypeScript,就可以创建一个简单的脚手架项目。

运行项目

按照提示,使用yarn安装依赖包,之后yarn dev运行项目

项目默认包结构介绍

  • node_modules:依赖包存储位置

  • public:存储静态资源,该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下。public存放的资源一般满足下面条件:

    • 不会被源码引用(例如 robots.txt)
    • 压根不想引入该资源,只是想得到其 URL。
    • public 中的资源不应该被 JavaScript 文件引用。
    • 引入 public 中的资源永远应该使用根绝对路径 ------ 举个例子,public/icon.png 应该在源码中被引用为 /icon.png。
  • src: 存储项目源码文件和资源

  • src/assets 如果静态资源是放在src/assets目录那么会经过vite打包处理,包括压缩、重命名等

  • .eslintrc.cjs:eslint 检查规则配置

  • .gitignore:git 忽略文件配置

  • index.html:Vite 项目的入口文件。

    官方解释:Vite 将 index.html 视为源码和模块图的一部分。Vite 解析 <script type="module" src="..."> ,这个标签指向你的 JavaScript 源码。甚至内联引入 JavaScript 的 <script type="module"> 和引用 CSS 的 <link href> 也能利用 Vite 特有的功能被解析。另外,index.html 中的 URL 将被自动转换,因此不再需要 %PUBLIC_URL% 占位符了。

  • package.json:项目信息、包声明

  • tsconfig.json:项目ts配置包括编译配置等

  • tsconfig.node.json: 是专门用于 vite.config.ts 的 TypeScript 配置文件。tsconfig.json 文件通过 references 字段引入 tsconfig.node.json 中的配置。

  • vite.config.ts:vite的配置文件,可以根据项目需要进行配置

createBrowserRouter 创建路由信息

在src文件夹下,建立一个 router 文件夹,存放路由配置信息 index.tsx,路由配置方式使用 react-router-dom 6.4 引入的最新的 Data API:createBrowserRouter 或者 createHashRouter

  • createBrowserRouter:页面路由使用路径path区分
  • createHashRouter:页面路由以 # hash方式区分

安装对应包,这里使用的是6.4,可以安装6.4以上版本

sh 复制代码
 yarn add react-router-dom@^6.4.0

配置路由之前新建几个测试页面,路由信息编写如下:

js 复制代码
import About from '@/views/About'
import Home from '@/views/Home'
import Index from '@/views/Index'
import Login from '@/views/Login'
import { createBrowserRouter, Navigate } from 'react-router-dom'
// import { createHashRouter, Navigate } from 'react-router-dom'

const Router = createBrowserRouter([
    {
        path: '/home',
        element: <Home/>,
        children: [
            {
                path: 'index',
                element: <Index/>
            },
            {
                path: 'pa',
                lazy: () => import("@/views/Pa") // 懒加载
            },
            {
                path: 'pb',
                lazy: () => import("@/views/Pb") // 懒加载
            },
            {
                path: 'about',
                element: <About/>, //非懒加载
            }
        ]
    },
    {
        path: '/login',
        element: <Login/>
    },
    {
        path: '/',
        element: <Navigate to="/home/index"/>
    },
    {
        path: '*',
        lazy: () => import("@/views/404"),
        // element: <Navigate to="/home"/>
    }
])

export default Router

注意:

  • children嵌套路由,需要父页面有路由出口:<Outlet/> 可以用来占位或者定义一个路由出口
  • 路由信息可以通过接口动态加载
  • lazy 可以实现路由页面懒加载,减少打包体积和提高页面加载效率
  • Navigate 可以实现路由重定向

最后一步将路由信息引入项目中,vite建立的项目默认入口是 main.tsx ,使用 RouterProvider 加载路由配置信息

js 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import Router from './router'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
	<RouterProvider router={Router} />
  </React.StrictMode>,
)

使用最新官方建议使用的reduxjs/toolkit进行,redux状态管理

Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法,简称RTK。 它包含我们对于构建 Redux 应用程序必不可少的包和函数。 Redux Toolkit 的构建简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序变得更加容易。Redux Toolkit 就是目前 Redux 的最佳实践方式。

安装对应包

sh 复制代码
yarn add react-redux @reduxjs/toolkit

这里使用的版本为:@reduxjs/toolkit ^1.9.7,react-redux ^8.1.3

  • 第一步在src下建立一个store文件夹,新建文件 index.tsx,编写 store 配置并导出
  • 第二步在src下建立一个slice文件夹,用来配置子模块状态切片(状态管理中的一块业务状态,和对应的暴露的方法)
  • 将所有的 slice,引入到store\index.tsx
  • 使用状态管理读取和更新状态
  1. index.tsx,这里引入了三个业务模块状态,作为例子,实际根据自己业务进行增删
js 复制代码
import { configureStore } from '@reduxjs/toolkit' // 引入rtk的配置
import counterSlice from '@/slice/counterSlice' // 业务模块状态:计数器状态
import configSlice from '@/slice/configSlice' // 业务模块状态:配置中心状态
import opDrawerSlice from '@/slice/opDrawerSlice' // 业务模块状态:操作栏状态


const store = configureStore({
    reducer: {
      counter: counterSlice,
      config: configSlice,
      opDrawer: opDrawerSlice
    },
    // 配置,这里忽略了序列化失败警告
    middleware: (getDefaultMiddleware) => getDefaultMiddleware({
      serializableCheck: false
    })
  })

export default store
// 导出两个类型方便业务模块使用时候TS类型确定
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
  1. 编写业务slice,这里举一个counterSlice例子
js 复制代码
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

// 为 slice state 定义一个类型
interface CounterState {
  value: number
}

// 使用该类型定义初始 state
const initialState: CounterState = {
  value: 0
}

export const counterSlice = createSlice({
    name: 'counter',
    initialState,
    reducers: {
        increment: state => {
            // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
            // 并不是真正的改变状态值,因为它使用了 Immer 库
            // 可以检测到"草稿状态" 的变化并且基于这些变化生产全新的
            // 不可变的状态
            state.value += 1
          },
        decrement: state => {
            state.value -= 1
          },
        incrementByAmount: (state, action: PayloadAction<number>) => {
            state.value += action.payload
          }
    }
})

// 导出actions方便业务组件调用方法
export const { increment, decrement, incrementByAmount } = counterSlice.actions

// 导出reducer,被store\index.tsx引入
export default counterSlice.reducer
  1. 定义一个app.hooks.ts文件,引入store信息,方便业务组件使用,和TS类型联想
js 复制代码
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux'

import { RootState, AppDispatch } from '@/store' // store\index.tsx导出的两个类型

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
  1. 引入store配置信息
js 复制代码
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={Store}>
      <RouterProvider router={Router} />
    </Provider>
    
  </React.StrictMode>,
)
  1. 业务组件使用
js 复制代码
import { useAppDispatch, useAppSelector } from '@/hooks/app.hooks'
import { decrement, increment } from "@/slice/counterSlice"//使用切片导出的方法
import { Button, Space } from 'antd';// 这里使用了antd UI 组件


export default function Counter() {
	// 获取状态
    const count = useAppSelector(state => state.counter.value)
    // 获取状态分发器
    const dispatch = useAppDispatch()


  return (
    <Space size={"middle"}>
        <Button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
            增加
        </Button>
        <span>{count}</span>
        <Button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
            减少
        </Button>
        
    </Space>
  )
}

Ant Design 使用

参考官方组件库使用方式即可,这里给出导航侧边菜单的使用

js 复制代码
import React, { useState } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom'
import {
  DesktopOutlined,
  FileOutlined,
  PieChartOutlined,
  TeamOutlined,
  UserOutlined,
  BulbFilled
} from '@ant-design/icons';
import style from './index.module.css'
import type { MenuProps } from 'antd'
import type { MenuClickEventHandler  } from 'rc-menu/lib/interface'
import { Layout, Menu, theme, Switch } from 'antd';
import { useAppDispatch, useAppSelector } from '@/hooks/app.hooks'
import { updateTheme } from '@/slice/configSlice';


const { Header, Content, Footer, Sider } = Layout;

type MenuItem = Required<MenuProps>['items'][number];

// function getItem(
//     label: React.ReactNode,
//     key: React.Key,
//     icon?: React.ReactNode,
//     children?: MenuItem[],
//   ): MenuItem {
//     return {
//       key,
//       icon,
//       children,
//       label,
//     } as MenuItem;
//   }

// 定义菜单
  const items: MenuItem[] = [
    {
      label: '常用工具',
      key: '/home',
      icon: <PieChartOutlined />,
      
    },
    {
      label: '开发工具',
      key: '/about',
      icon: <DesktopOutlined />
    },
    {
      label: '格式转换',
      key: '/home/pa',
      icon: <TeamOutlined />
    },
    {
      label: '生活日常',
      key: '/home/pb',
      icon: <FileOutlined />
    },
    {
      label: '热站导航',
      key: '/sub1',
      icon: <UserOutlined />,
      // children: [
      //   {
      //     label: 'Tom',
      //     key: '/tom',
      //     icon: <UserOutlined />,
      //   },
      //   {
      //     label: 'Bill',
      //     key: '/bill',
      //     icon: <UserOutlined />,
      //   }
      // ]
    },
  ];




  const Home: React.FC = () => {
    const [collapsed, setCollapsed] = useState(false);
    const { token: { colorBgContainer } } = theme.useToken();
    // const headerHeight = theme.useToken().token.Layout?.headerHeight
    const navigate = useNavigate();
    const location = useLocation()
    const menuClick: MenuClickEventHandler = (e) => {
      console.log(e)
      navigate(e.key)
    }
    const myTheme = useAppSelector(state => state.config.theme)
    const dispatch = useAppDispatch()

    return (
      <Layout hasSider>
        <Sider
            theme={myTheme}
            collapsedWidth={50}
            width={150}
            collapsed={collapsed}
            onCollapse={(value) => setCollapsed(value)}
            style={{
              overflow: 'auto',
              height: '100vh',
              position: 'fixed',
              left: 0,
              top: 0,
              bottom: 0,
            }}>
          <div className="demo-logo-vertical" />
          <Menu
            theme={myTheme}
            defaultSelectedKeys={[location.pathname]}
            mode="inline"
            items={items}
            onClick={menuClick}/>
        </Sider>
        <Layout style={{marginLeft: '150px'}}>
          <Header style={{ padding: 0, margin: '16px', background: colorBgContainer }} >
            <div className={style.headerDiv}>
              <Switch
                  checkedChildren={<BulbFilled />}
                  unCheckedChildren={<BulbFilled />}
                  defaultChecked
                  onChange={checked => {dispatch(updateTheme(checked ? 'light': 'dark'))}}
                />
            </div>
          </Header>
          <Content style={{ margin: '0 16px' }}>
            <div style={{ padding: 24, minHeight: 360, background: colorBgContainer }}>
              <Outlet/>
            </div>
          </Content>
          <Footer style={{ textAlign: 'center' }}>Ant Design ©2023 Created by Ant UED</Footer>
        </Layout>
      </Layout>
    );
  };
export default Home

项目源码地址

项目源码地址:gitee.com/li_zheng/re...

相关推荐
清灵xmf11 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
PleaSure乐事12 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
getaxiosluo12 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
新星_13 小时前
函数组件 hook--useContext
react.js
阿伟来咯~14 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端14 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱14 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
bysking15 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
Amd79418 小时前
Nuxt.js 应用中的 prepare:types 事件钩子详解
typescript·自定义·配置·nuxt·构建·钩子·类型
September_ning20 小时前
React.lazy() 懒加载
前端·react.js·前端框架