react ts

本文详细介绍了如何使用Vite构建ReactTypeScript项目,包括创建项目、配置目录结构、Sass环境、CRA插件修改、路由设置、AntD集成,以及用户登录功能和路由权限管理,涉及axios封装、Redux和RTX的使用。
摘要由CSDN通过智能技术生成

一、项目搭建

1、创建项目
  • 使用vite生成项目

npx create-react-app react-ts-project --template typescript
  • 启动项目

yarn start
  • 删除无用组件

2、设计目录结构
资源说明
http网络请求
assets公共资源
components组件
router路由配置
utils工具模块
store状态机
App.tsx应用根组件
index.tsx入口ts文件
3、配置sass环境
  • 安装sass包

yarn add sass
  • 创建全局样式文件

4、craco插件配置

如果要修改CRA的默认配置,有以下几种方案

  • 通过第三方库来修改,比如@craco/craco

  • 通过执行yarn eject命令,释放react-scripts中的所有配置到项目中

实现步骤

  • 安装修改CRA的配置的包

yarn add -D @craco/craco
  • 在项目的根目录中创建craco的配置文件(craco.config.js),并在配置中配置别名

const path=require('path')
module.exports={
    webpack:{
        alias:{
            '@':path.resolve(__dirname,'src')
        }
    }
}
  • 修改packge.json中的脚本命令

"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  }
  • 在代码中,就可以通过@来表示src目录的绝对路径

  • 重启项目,让配置生效

@别名路径提示

  • 在项目根目录创建tsconfig.json配置文件

  • 在配置文件中添加如下配置

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"]
        }
    }
  }
5、配置路由
  • 安装路由

yarn add react-router-dom
yarn add react-loadable
yarn add @types/react-loadable

下载react-loadable依赖包进行路由懒加载,如果你是typescript,你还需要额外安装@types/react-loadable这个依赖包

  • 建立一个loadable.ts,放在src/utils/loadable.ts

import Loadable from 'react-loadable';
export default function withLoadable(comp:any) {
    return Loadable({
    	//懒加载组件页面
        loader: comp,
        loading: () => null
    })
}
  • 在src下创建router目录,在该目录的index.tsx编写路由配置文件

import loadable from '@/utils/loadable'
import {RouteObject} from 'react-router-dom'
const Login=loadable(()=>import('@/views/Login'))
const Home=loadable(()=>import('@/views/Home'))
const Main=loadable(()=>import('@/views/Main'))
const Product=loadable(()=>import('@/views/Product'))
const Category=loadable(()=>import('@/views/Category'))
const routes:Array<RouteObject>=[
    {
        path:'/login',
        element:<Login/>
    },
    {
        path:'/',
        element:<Home/>,
        children:[
            {
                index:true,
                element:<Main></Main>
            },
            {
                path:'product/list',
                element:<Product></Product>
            }, 
            {
                path:'product/category',
                element:<Category></Category>
            }
        ]
    }
]
export default routes
  • 在App.tsx中通过useRoutes钩子函数来进行集中式配置

import React,{Suspense} from 'react'
import {useRoutes} from 'react-router-dom'
import routes from '@/router'
export default function App() {
  return (
   <Suspense fallback={<>loading</>}>
    {useRoutes(routes)}
   </Suspense>
  )
}
  • 在项目根目录下的index.tsx中使用<BrowserRouter>包裹<App>

root.render(
    <BrowserRouter>
        <App/>
    </BrowserRouter>
)
  • 在src/Home.tsx组件中配置二级路由出口

import React from 'react'
import {Outlet} from 'react-router-dom'
export default function Home() {
  return (
    <div>
        Home
       <Outlet></Outlet>
    </div>
  )
}
6、安装AntD
  • 安装AntD组件库

yarn add antd

二、用户登录

1、登录静态页面实现
  • 首先在utils目录下创建type.ts文件,这个文件里主要编写接口

export interface IUser{
    account:string,
    password:string
  }
  • 在views目录下的Login.tsx文件中编写登录的静态页面

import {Button,Form,Input,message} from 'antd'
import api from '@/api'
import {useNavigate} from 'react-router-dom'

export default ()=>{
    const nav=useNavigate()
    const onFinish=async(values:any)=>{
       console.log(values)
    }
    return (
        <Form onFinish={onFinish} style={{ maxWidth: 600 }}>
            <Form.Item label='用户名' name="username">
                <Input/>
            </Form.Item>
            <Form.Item label='密码' name="password">
                <Input.Password/>
            </Form.Item>
            <Form.Item>
                <Button type="primary" htmlType='submit'>登录</Button>
            </Form.Item>
        </Form>
    )
}
2、axios的二次封装
import axios,{ InternalAxiosRequestConfig,AxiosResponse,AxiosError} from "axios";
import {message} from 'antd'
//设置根路径
axios.defaults.baseURL="http://www.zhaijizhe.cn:3005"
//设置请求拦截器
axios.interceptors.request.use((config: InternalAxiosRequestConfig)=>{
    const token=localStorage.getItem('token')
    if(token){
        config.headers.Authorization=token
    }
    return config
})
//设置响应拦截器
axios.interceptors.response.use((response:AxiosResponse)=>{
    return response.data
},(error:AxiosError)=>{
    switch(error.response?.status){
        case 500:
            message.error('服务端出现500错误')
            break
        case 401:
            message.error('服务端出现400错误')
            break;
        case 404:
            message.error("没有找到服务端相应资源");
            break;
    }
    return Promise.reject(error)
})
export default axios
3、登录功能的实现
  • 在src/types文件夹下编写接口

export default interface IUser{
    username:string
    password:string
}
  • 编写登录的后端请求API

import request from '@/utils/request'
import IUser from '@/types/IUser'

export default{
    login:(user:IUser)=>request.post('/users/login',user)
}
  • 将用户模块汇总到api.tsx中

import users from "./modules/users"
export default{
   users
}
  • 在登录组件中调用登录API,完成登录功能

import {Button,Form,Input,message} from 'antd'
import api from '@/api'
import {useNavigate} from 'react-router-dom'

export default ()=>{
    const nav=useNavigate()
    const onFinish=async(values:any)=>{
        const result=await api.users.login(values)
        if(result.data.code){
            localStorage.setItem('token',result.data.token)
            nav('/')
        }else{
            message.warning('登录失败')
        }
    }
    return (
        <Form onFinish={onFinish} style={{ maxWidth: 600 }}>
            <Form.Item label='用户名' name="username">
                <Input/>
            </Form.Item>
            <Form.Item label='密码' name="password">
                <Input.Password/>
            </Form.Item>
            <Form.Item>
                <Button type="primary" htmlType='submit'>登录</Button>
            </Form.Item>
        </Form>
    )
}

三、路由权限

1、静态菜单
import {Outlet,NavLink,useNavigate} from 'react-router-dom'
import React,{useState,useEffect} from 'react'
import '@/assets/css/home.scss'
import {Layout,Menu} from 'antd'
import {WindowsOutlined,TrademarkCircleOutlined,UserOutlined} from '@ant-design/icons'
const {Header,Sider,Content,Footer}=Layout
export default ()=>{
    const[menuList,setMenuList]=useState<any>([])
    const nav=useNavigate()
    useEffect(()=>{
        const list=[
            {
              key:'sub1',
              label:'日常业务',
              icon:<WindowsOutlined />,
              children:[
                {
                  label:'学员管理',
                  key:'/students'
                },
                {
                  label:'班级管理',
                  key:'/classes'
                }
              ]
            },
            {
              key:'sub2',
              label:'校区管理',
              icon:<TrademarkCircleOutlined />,
              children:[
                {
                    label:'教师管理',
                    key:'/teachers'
                },
                {
                  label:'班主任管理',
                  key:'/directors'
                },
                {
                  label:'专业管理',
                  key:'/subjects'
                }
              ]
            },
            {
              key:'sub3',
              label:'系统管理',
              icon:<UserOutlined />,
              children:[
                {
                  key:'/users',
                  label:'用户管理'
                }
              ]
            }
          ]
          setMenuList(list)
    },[])

    const go=(item:any)=>{
       nav(item.key)
    }
    return (<>
         <Layout>
          <Header>
            <div>蜗牛BOSS管理系统</div>
          </Header>  
          <Layout style={{height:'750px'}}>
              <Sider>
                  <Menu 
                    items={menuList} 
                    onClick={go}
                    mode="inline"
                    theme="dark"
                    defaultOpenKeys={['sub1','sub2']}
                    defaultSelectedKeys={['/home/students']}></Menu>
              </Sider>
              <Content>
                  {/* 设置子路由出口 */}
                  <Outlet></Outlet>
              </Content>
          </Layout>
          <Footer style={{ textAlign: 'center'}}>Ant Design ©2023 Created by Ant UED</Footer>
      </Layout> 
    </>
    )
}
2、动态菜单
  • 在api/modules/users下编写获取权限菜单的接口

import request from '@/utils/request'
export default{
    getAuthMenus:()=>request.get('/menus/getAuthMenus')
}
  • 在Home.tsx中中调用getAuthMenus接口来完成权限菜单数据的获取

 useEffect(() => {
    getAuthMenus()
  }, [])
 const getAuthMenus = async () => {
    const result = await api.users.getAuthMenus()
    console.log(result.data.data)
    const rList = transformDataToMenus(result.data.data)
    setMenuList(rList)
  }
  • 将后台的权限菜单数据转成antd格式的菜单数据

/**
 * 将后台的权限菜单数据转成antd格式的菜单数据
 */
  interface IMenuData {
    title: string
    path: string
    icon: string
    children?: Array<IMenuData>
  }
  interface IMenu {
    label: string
    key: string
    icon: ReactElement
    children?: Array<IMenu>
  }
  const transformDataToMenus = (list: Array<IMenuData>) => {
    return list.map((item: IMenuData) => {
      let name = item.icon as string
      let iis: any = icons[name as keyof typeof icons]
      let menuItem: IMenu = { label: item.title, key: item.path, icon: React.createElement(iis) }
      if (item.children) {
        menuItem.children = transformDataToMenus(item.children)
      }
      return menuItem
    })
  }
  • 渲染导航列表

<Menu
     items={menuList}
     mode="inline"
     theme="dark"
     onClick={goNav}>
 </Menu>
3、动态添加路由表
1、安装和配置RTX

在终端执行

yarn add redux
yarn add react-redux
yarn add @reduxjs/toolkit

在src目录下创建store文件夹,目录结构如下

src
 |-store
    |-moudles
    	|-xx.tsx
    	|-yy.tsx
    |-index
2、创建slice模块

在src/store/reducers目录下创建routeReducer.tsx

import {createSlice,PayloadAction,Dispatch} from '@reduxjs/toolkit'
import {RouteObject} from 'react-router-dom'
import api from '@/api'
import {withLoadable} from '@/utils/loadable'
interface IAuthMenu{
    rows:Array<any>
}
const initialState:IAuthMenu={
    rows:[]
}
export const routesSlice=createSlice({
    name:'getAuthMenu',
    initialState,
    reducers:{
        setAuthRoutes(state,{payload}:PayloadAction<any[]>){
            state.rows=payload
        }
    }
})
export const getAuthRoutesAsync=(routes:any)=>{
    return async(dispath:Dispatch)=>{
        console.log('----网络请求-----------');
        let result=await api.users.getAuthMenus()
        console.log('aaaa',result.data.data)
        result.data.data.forEach((item:RouteObject)=>{
            if(item.children){
                item.children.forEach((subItem:RouteObject)=>{
                    console.log('ss',subItem)
                    const Module=withLoadable(()=>import(`@/views${subItem.path}`))
                    routes[1].children.push({
                        path:subItem.path,
                        element:<Module/>
                    })
                })
            }
        })
       dispath(setAuthRoutes(routes))
    }
}
export const {setAuthRoutes}=routesSlice.actions
export default routesSlice.reducer
3、创建store对象
import {configureStore} from '@reduxjs/toolkit'

import menuReducer from './reducers/menus'
const store=configureStore({
    reducer:{
        menuReducer
    }
})
export default store
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
4、创建hooks文件
​
在src/store目录下新建hooks文件

import { useDispatch, useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './index'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
Usage With TypeScript | Redux 中文官网

​
5、全局注册store
import ReactDOM from 'react-dom/client';
import App from '@/App'
import {BrowserRouter} from 'react-router-dom'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <BrowserRouter><App /></BrowserRouter>
);
6、在组件中使用
import { Outlet, NavLink } from 'react-router-dom'
import '@/assets/css/home.scss'
import { Layout,Menu } from 'antd'
import React,{ useEffect, useState } from 'react'
import {WindowsOutlined,TrademarkCircleOutlined,UserOutlined} from '@ant-design/icons'
//导入api
import api from '@/api'
//导入接口
import IMenuData from '@/types/IMenuData'
import IMenu from '@/types/IMenu'
import IMenuFun from '@/types/IMenuFun'
//导入图标
import * as icons from '@ant-design/icons'
//导入useSelector和useDispatch
// import {useSelector,useDispatch} from 'react-redux'
//使用ts的话,hooks使用的是useAppSelector来代替useSelector,使用useAppDispatch来替换useDispatch
import {useAppSelector,useAppDispatch} from '@/store/hooks'
//导入通知对象
import {getRoutesAsync} from '@/store/modules/routesReducer'
//导入routes
import routes from '@/router'

import {useNavigate} from 'react-router-dom'


const { Header, Sider, Content, Footer } = Layout
export default () => {
    const[menuList,setMenuList]=useState<Array<any>>([])
    useEffect(()=>{
        getAuthMenus()
    },[])
    
    const dispatch=useAppDispatch()
    const nav=useNavigate()
    /**
     * 通过token向后端服务端获取权限数据
     */
    const getAuthMenus=async()=>{
      const result=await api.users.getAuthMenus()
      //进行转换
      const list:Array<IMenu>=transformDataToMenu(result.data.data)
      setMenuList(list)
      //向状态机发送通知操作路由表,关键代码
      dispatch(getRoutesAsync(routes))

    }
    //代码略
    const transformDataToMenu:IMenuFun=(list:Array<IMenuData>)=>{
    }
	//进行路由跳转的方法
    const go=(values:any)=>{
      nav(values.key)
    }
    return (
        <Layout>
            <Header style={{ color: '#fff' }}>蜗牛BOSS系统</Header>
            <Layout style={{ height: '750px' }}>
                <Sider>
                    <Menu
                        onClick={go}
                        items={menuList}
                        theme="dark"
                        mode="inline">

                    </Menu>
                </Sider>
                <Content>
                    <Outlet></Outlet>
                </Content>
            </Layout>
            <Footer>&copy;版权前端10期班所有</Footer>
        </Layout>
    )
}
7、App.tsx
import {useRoutes} from 'react-router-dom'
import routes from '@/router'
import {useAppSelector} from '@/store/hooks'
import {useEffect,useState} from 'react'
export default ()=>{
    const rows=useAppSelector((state)=>{
        return state.routesReducer.rows
    })
    const[rout,setRout]=useState(routes)
    useEffect(()=>{
        console.log('********')
        console.log('rows',rows)
        setRout(rows)
    },[rows])
    
    return (<>
        {useRoutes(rows.length==0?routes:rout)}
     </>)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值