React 结合 antd 入门项目,包含 token 验证、封装 hook 组件、组件通信等功能

注: 本 React 项目基于 Create React App 构建,实现了用户认证与路由鉴权、数据的增删改查,并深入介绍了 React Hook 的封装与组件间通信等核心技术。该项目内容丰富,非常适合 React 初学者进行学习和实践。以下是对项目的部分介绍,详见源码。
项目地址 gitee: https://gitee.com/zcr-1/react-jike.git
项目登录账号 :13800000002
项目登录密码 :246810

一、项目准备

1.创建项目

  • npx create-react-app my-app

2.启动项目

  • npm start

3.调整项目结构目录

-src
  -apis           项目接口函数
  -assets         项目资源文件,比如,图片等
  -components     通用组件
  -pages          页面组件
  -store          集中状态管理
  -utils          工具,比如,token、axios 的封装等
  -App.js         根组件
  -index.css      全局样式
  -index.js       项目入口

4.删除无用的文件

  • 根目录留下 App.js、index.js、index.css,其他文件删除

5.安装依赖

  • npm i sass -D
  • npm i antd
  • npm i normalize.css

6.配置别名路径

  • 安装 craco 工具包 npm i @craco/craco -D

  • 在根目录添加 craco.config.js 配置文件

const path = require('path')

module.exports = {
  // webpack 配置
  webpack: {
    // 配置别名
    alias: {
      // 约定:使用 @ 表示 src 文件所在路径
      '@': path.resolve(__dirname, 'src')
    }
  }
}
  • 修改 package.json 文件配置
"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "react-scripts eject"
}

7.VScode 配置提示

  • 在项目根目录创建 jsconfig.json 配置文件
  • 在配置文件中添加以下配置
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

二、配置路由

  • 安装依赖 npm i react-router-dom
  • 准备基础组件 Layout Login
  • 配置路由
  • 代码实现

pages/Layout/index.js pages/Login/index.js

const Layout = () => {
  return <div>this is layout</div>
}
export default Layout

router/index.js

import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/pages/Layout';
import Login from '@/pages/Login';

// 配置
const router = createBrowserRouter([
    {
        path: '/',
        element: <Layout/>,

    },
    {
        path: '/Login',
        element:<Login/>
    }
])

export default router;

index.js

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.scss'
import router from './router'
import { RouterProvider } from 'react-router-dom'

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

三、封装 request 工具

1.安装 axios 到项目 npm i axios

2.创建 utils/request.js 文件

3.创建 axios 实例,配置 baseURL,请求拦截器,响应拦截器

import axios from 'axios'

const request = axios.create({
  baseURL: 'http://geek.itheima.net/v1_0',
  timeout: 5000
})

// 添加请求拦截器
request.interceptors.request.use((config) => {
  return config
}, (error) => {
  return Promise.reject(error)
})

// 添加响应拦截器
request.interceptors.response.use((response) => {
  return response.data
}, (error) => {
  return Promise.reject(error)
})

export { request }

4.在 utils/index.js 中,统一导出request

import { request } from './request'
export { request }

四、封装请求接口 API

1.根据封装的 request.js 封装接口

2.代码演示

import { request } from "@/utils"

// POST 带参请求
export const logininApi = (data) => {
    return  request({
        url:'/authorizations',
        method:'POST',
        data
    })
 }

//  GET 带参请求
export const getArticleAPI = (data) => {
    return request({
        url: '/mp/articles',
        method: 'GET',
        params: data
    })
}

// GET 无参请求 参数可省略
export const getArticleById = (id) => {
    return request({
        url: `/mp/articles/${id}`,
    })
}

// DELETE 请求
export const delArticleAPI = (id) => {
    return request({
        url: `/mp/articles/${id}`,
        method: 'DELETE',
    })
}

// PUT 请求
export const updateArticleAPI = (data) => {
    return request({
        url: `/mp/articles/${data.id}?draft_false`,
        method: 'PUT',
        data
    })
}

五、使用 Redux 管理 token

1.安装 redux 相关工具包 npm i react-redux @reduxjs/toolkit

2.配置 Redux

  • 创建 user.js 文件
import { createSlice } from "@reduxjs/toolkit";
import { logininApi,fetchUserInfoApi } from "@/apis/user"
import {
    request,
    getToken,
    setToken as _setToken,
    removeToken,
} from "@/utils";

const userStore = createSlice({
    name: 'user',
    initialState: {
        token: getToken() || '',
        userInfo: {}
    },
    // 同步修改方法
    reducers: {
        setToken(state, action) {
            state.token = action.payload
            _setToken(action.payload)
        },
        setUserInfo(state, action) {
            state.userInfo = action.payload
        },
        clearnUserInfo(state) {
            state.token = ''
            state.userInfo = {}
            removeToken()
        }
    }
})

// 解构
const { setToken, setUserInfo ,clearnUserInfo} = userStore.actions

// 异步登录方法
const fetchLogin = (loginForm) => {
    return async (dispatch) => {
        const res = await logininApi(loginForm)
        dispatch(setToken(res.data.token))
    }
}

// 获取用户信息异步方法
const fetchUserInfo = () => {
    return async (dispatch) => {
        const res = await fetchUserInfoApi()
        dispatch(setUserInfo(res.data))
    }
}

const userReducer = userStore.reducer

export { setToken, fetchLogin, fetchUserInfo ,clearnUserInfo}

export default userReducer

注: 为了使 token 持久化,需要将 token 存储在 localStorage 中,utils 中写了详细代码

  • 在 store/index.js 中引入
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./moudles/user";

const store =  configureStore({
    reducer:{
        user: userReducer
    }
})

export default store
  • 在 index.js 中注册
import { Provider } from 'react-redux';
import store from './store';

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

3.请求拦截器注入 token

  • utils/rquest.js
// 添加请求拦截器
request.interceptors.request.use(config => {
  // if not login add token
  const token = getToken()
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

4.路由鉴权实现

  • components/AuthRoute/index.jsx
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'

const AuthRoute = ({ children }) => {
  const isToken = getToken()
  if (isToken) {
    return <>{children}</>
  } else {
    return <Navigate to="/login" replace />
  }
}

export default AuthRoute
  • src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'

import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'


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

export default router

5.处理 token 失效

request.interceptors.response.use((response) => {
  return response.data
}, (error) => {
  // 对响应错误做点什么
  console.dir(error)
  if (error.response.status === 401) {
    clearToken()
    router.navigate('/login')
    window.location.reload()
  }

  return Promise.reject(error)
})

六、引入 Menu 点击跳转,并反向高亮

1.登录页面后,左侧菜单栏点击跳转,并在地址栏输入路由,菜单栏会自动高亮

2.代码演示

  • 点击菜单跳转路由
import { Outlet, useNavigate } from 'react-router-dom'

const items = [
  {
    label: '首页',
    key: '/',
    icon: <HomeOutlined />,
  },
  {
    label: '文章管理',
    key: '/article',
    icon: <DiffOutlined />,
  },
  {
    label: '创建文章',
    key: '/publish',
    icon: <EditOutlined />,
  },
]

const GeekLayout = () => {
  const navigate = useNavigate()
  const menuClick = (route) => {
    navigate(route.key)
  }
  return (
      <Menu
        mode="inline"
        theme="dark"
        selectedKeys={selectedKey}
        items={items}
        style={{ height: '100%', borderRight: 0 }}
        onClick={menuClick}
      /> 
  )
}
export default GeekLayout
  • 菜单反向高亮
import { useLocation } from 'react-router-dom'
const GeekLayout = () => {
  // 省略部分代码
  const location = useLocation()
  const selectedKey = location.pathname
  
  return (
        <Sider width={200} className="site-layout-background">
          <Menu
            mode="inline"
            theme="dark"
            selectedKeys={selectedKey}
            items={items}
            style={{ height: '100%', borderRight: 0 }}
            onClick={menuClickHandler}></Menu>
        </Sider>
  )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值