基于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...

相关推荐
openKaka_1 小时前
为什么 React 18 之后使用 createRoot,而不是 ReactDOM.render
前端·javascript·react.js
老王以为2 小时前
从源码到架构:React useActionState 深度剖析
前端·javascript·react.js
萧曵 丶2 小时前
Vue3组件通信全方案
前端·javascript·vue.js·typescript·vue3
漫游的渔夫3 小时前
前端开发者做 Agent:模型说执行就执行?先加 3 道闸门再碰真实业务
前端·人工智能·typescript
天蓝色的鱼鱼3 小时前
当AI开始替我写代码,我还要纠结选Vue还是React吗?
vue.js·react.js·ai编程
空中海19 小时前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
空中海19 小时前
05 React架构设计、项目实践与专家清单
前端·react.js·前端框架
前端之虎陈随易21 小时前
2年没用Nodejs了,Bun很香
linux·前端·javascript·vue.js·typescript
空中海1 天前
04 工程化、质量体系与 React 生态
前端·ubuntu·react.js
Yue1681 天前
啥子都能看懂的TypeScript快速入门
typescript