前端实战|React18极客园——布局模块(useRoutes路由配置、处理Token失效、退出登录)

欢迎来到我的博客
博主是一名大学在读本科生,主要学习方向是前端。
目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
目前正在学习的是 R e a c t / 小程序 React/小程序 React/小程序,中间穿插了一些基础知识的回顾
博客主页codeMak1r.小新的博客

本文目录

  • Layout模块
    • 1. 基本结构搭建
    • 2. 二级路由配置
    • 3. 菜单高亮显示
    • 4. 展示个人信息
    • 5. 退出登录实现
    • 6. 处理Token失效
    • 7. 首页Home图表展示

本文被专栏【React–从基础到实战】收录
坚持创作✏️,一起学习,码出未来‍!

最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
gitee完整项目地址:极客园完整代码

Layout模块

1. 基本结构搭建

本节目标: 能够使用antd搭建基础布局

实现步骤

  1. 打开 antd/Layout 布局组件文档,找到示例:顶部-侧边布局-通栏
  2. 拷贝示例代码到我们的 Layout 页面中
  3. 分析并调整页面布局

代码实现

pages/Layout/index.js

import { Layout, Menu, Popconfirm } from 'antd'
import {
  HomeOutlined,
  DiffOutlined,
  EditOutlined,
  LogoutOutlined
} from '@ant-design/icons'
import './index.scss'

const { Header, Sider } = Layout

const GeekLayout = () => {
  return (
    <Layout>
      <Header className="header">
        <div className="logo" />
        <div className="user-info">
          <span className="user-name">user.name</span>
          <span className="user-logout">
            <Popconfirm title="是否确认退出?" okText="退出" cancelText="取消">
              <LogoutOutlined /> 退出
            </Popconfirm>
          </span>
        </div>
      </Header>
      <Layout>
        <Sider width={200} className="site-layout-background">
          <Menu
            mode="inline"
            theme="dark"
            defaultSelectedKeys={['1']}
            style={{ height: '100%', borderRight: 0 }}
          >
            <Menu.Item icon={<HomeOutlined />} key="1">
              数据概览
            </Menu.Item>
            <Menu.Item icon={<DiffOutlined />} key="2">
              内容管理
            </Menu.Item>
            <Menu.Item icon={<EditOutlined />} key="3">
              发布文章
            </Menu.Item>
          </Menu>
        </Sider>
        <Layout className="layout-content" style={{ padding: 20 }}>内容</Layout>
      </Layout>
    </Layout>
  )
}

export default GeekLayout

pages/Layout/index.scss

.ant-layout {
  height: 100%;
}

.header {
  padding: 0;
}

.logo {
  width: 200px;
  height: 60px;
  background: url('~@/assets/logo.png') no-repeat center / 160px auto;
}

.layout-content {
  overflow-y: auto;
}

.user-info {
  position: absolute;
  right: 0;
  top: 0;
  padding-right: 20px;
  color: #fff;
  
  .user-name {
    margin-right: 20px;
  }
  
  .user-logout {
    display: inline-block;
    cursor: pointer;
  }
}
.ant-layout-header {
  padding: 0 !important;
}

2. 二级路由配置

本节目标: 能够在右侧内容区域展示左侧菜单对应的页面内容

使用步骤

  1. 在 pages 目录中,分别创建:Home(数据概览)/Article(内容管理)/Publish(发布文章)页面文件夹
  2. 分别在三个文件夹中创建 index.js 并创建基础组件后导出
  3. 在app.js中配置嵌套子路由,在layout.js中配置二级路由出口
  4. 使用 Link 修改左侧菜单内容,与子路由规则匹配实现路由切换

代码实现

pages/Home/index.js

const Home = () => {
  return <div>Home</div>
}
export default Home

pages/Article/index.js

const Article = () => {
  return <div>Article</div>
}
export default Article

pages/Publish/index.js

const Publish = () => {
  return <div>Publish</div>
}
export default Publish

src/routes/index.js

export default [
  // 不需要鉴权的组件Login
  {
    path: "/login",
    element: <Login />
  },
  // 需要鉴权的组件Layout
  {
    path: "/",
    element: <AuthRoute>
      <Layout />
    </AuthRoute>,
    children: [
      {
        path: "home",
        element: <AuthRoute>
          <Home />
        </AuthRoute>
      },
      {
        path: "article",
        element: <AuthRoute>
          <Article />
        </AuthRoute>
      },
      {
        path: "publish",
        element: <AuthRoute>
          <Publish />
        </AuthRoute>
      },
      {
        path: "",
        element: <Navigate to="home" replace />
      }
    ]
  }
]

pages/Layout/index.js

// 配置Link组件
<Menu
 mode="inline"
 theme="dark"
 defaultSelectedKeys={['1']}
 style={{ height: '100%', borderRight: 0 }}
>
  <Menu.Item icon={<HomeOutlined />} key="1" onClick={() => navigate('home')}>
    数据概览
	</Menu.Item>
	<Menu.Item icon={<DiffOutlined />} key="2" onClick={() => navigate('article')}>
    内容管理
  </Menu.Item>
  <Menu.Item icon={<EditOutlined />} key="3" onClick={() => navigate('publish')}>
		发布文章
  </Menu.Item>
</Menu>
<Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout>

3. 菜单高亮显示

本节目标: 能够在页面刷新的时候保持对应菜单高亮

思路

  1. Menu组件的selectedKeys属性与Menu.Item组件的key属性发生匹配的时候,Item组件即可高亮
  2. 页面刷新时,将当前访问页面的路由地址作为 Menu 选中项的值(selectedKeys)即可

实现步骤

  1. 将 Menu 的key 属性修改为与其对应的路由地址
  2. 获取到当前正在访问页面的路由地址
  3. 将当前路由地址设置为 selectedKeys 属性的值

代码实现

pages/Layout/index.js

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

const GeekLayout = () => {
  const { pathname: selectedKey } = useLocation()
  console.log(selectedKey)

  return (
    // ...
    <Menu
      mode="inline"
      theme="dark"
      selectedKeys={[selectedKey]}
      style={{ height: '100%', borderRight: 0 }}
    >
      <Menu.Item icon={<HomeOutlined />} key="/home" onClick={() => navigate('home')}>
				  数据概览
			</Menu.Item>
			<Menu.Item icon={<DiffOutlined />} key="/article" onClick={() => navigate('article')}>
				  内容管理
			</Menu.Item>
			<Menu.Item icon={<EditOutlined />} key="/publish" onClick={() => navigate('publish')}>
				  发布文章
			</Menu.Item>
    </Menu>
  )
}

4. 展示个人信息

本节目标: 能够在页面右上角展示登录用户名

实现步骤

  1. 在store中新增user.Store.js模块,在其中定义获取用户信息的mobx代码
  2. 在store的入口文件中组合新增的userStore模块
  3. 在Layout组件中调用action函数获取用户数据
  4. 在Layout组件中获取个人信息并展示

代码实现

store/user.Store.js

// 用户模块
import { makeAutoObservable } from "mobx"
import { http } from '@/utils'

class UserStore {
  userInfo = {}
  constructor() {
    makeAutoObservable(this)
  }
  async getUserInfo() {
    const res = await http.get('/user/profile')
    this.userInfo = res.data
  }
}

export default UserStore

store/index.js

import React from "react"
import LoginStore from './login.Store'
import UserStore from './user.Store'

class RootStore {
  // 组合模块
  constructor() {
    this.loginStore = new LoginStore()
    this.userStore = new UserStore()
  }
}

const StoresContext = React.createContext(new RootStore())
export const useStore = () => React.useContext(StoresContext)

pages/Layout/index.js

import { useEffect } from 'react'
import { observer } from 'mobx-react-lite'

const GeekLayout = () => {
  const { userStore } = useStore()
  // 获取用户数据
  useEffect(() => {
    try {
      userStore.getUserInfo()
    } catch { }
  }, [userStore])
    
  return (
    <Layout>
      <Header className="header">
        <div className="logo" />
        <div className="user-info">
          <span className="user-name">{userStore.userInfo.name}</span>
        </div>
      </Header>
      {/* 省略无关代码 */}
    </Layout>
  )
}

export default observer(GeekLayout)

5. 退出登录实现

本节目标: 能够实现退出登录功能

实现步骤

  1. 为气泡确认框添加确认回调事件
  2. store/login.Store.js 中新增退出登录的action函数,在其中删除token
  3. 在回调事件中,调用loginStore中的退出action
  4. 退出后,返回登录页面

代码实现

store/login.Store.js

class LoginStore {
  // 退出登录
  loginOut = () => {
    this.token = ''
    clearToken()
  }
}

export default LoginStore

clearToken()是utils/token.js中定义好的清除token的工具函数。

pages/Layout/index.js

// login out
const navigate = useNavigate()
const onLogout = () => {
    loginStore.loginOut()
    navigate('/login')
}

<span className="user-logout">
    <Popconfirm title="是否确认退出?" okText="退出" cancelText="取消" onConfirm={onLogout}>
      <LogoutOutlined /> 退出
    </Popconfirm>
</span>

6. 处理Token失效

本节目标: 能够在响应拦截器中处理token失效

说明:为了能够在非组件环境下拿到路由信息,需要我们安装一个history包

前端实战|React18极客园——布局模块(useRoutes路由配置、处理Token失效、退出登录)_第1张图片

实现步骤

  1. 安装history包:yarn add history
  2. 创建 utils/history.js文件
  3. 在app.js中使用我们新建的路由并配置history参数
  4. 通过响应拦截器处理 token 失效,如果发现是401跳回到登录页

代码实现

utils/history.js

// https://github.com/remix-run/react-router/issues/8264
import { createBrowserHistory } from 'history'
const history = createBrowserHistory()
export { history }

index.js入口文件

...省略无关代码
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { history } from "./utils/history";

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <HistoryRouter history={history}>
    <App />
  </HistoryRouter>
)

utils/http.js

import { history } from './history'

http.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    if (error.response.status === 401) {
      // 清除失效的token
      removeToken()
      // 跳转到登录页
      history.push('/login')
    }
    return Promise.reject(error)
  }
)

7. 首页Home图表展示

本节目标: 实现首页echart图表封装展示

前端实战|React18极客园——布局模块(useRoutes路由配置、处理Token失效、退出登录)_第2张图片

需求描述:

  1. 使用eharts配合react封装柱状图组件Bar
  2. 要求组件的标题title,横向数据xData,纵向数据yData,样式style可定制

代码实现

components/Bar/index.js

// 封装图表bar组件
// 思路:
// 1. 看官方文档 把echarts加入项目
// 如何在react中获取dom => useRef
// 在什么地方获取dom节点  => useEffect
// 2. 不抽离定制化参数 先把最小化的demo跑起来
// 3. 按照需求:哪些参数需要自定义 抽象出来
import { useRef, useEffect } from 'react';
import * as echarts from 'echarts'

export default function Bar({ title, xData, yData, style }) {
  const domRef = useRef()
  // 执行这个初始化的函数
  useEffect(() => {
    const chartInit = () => {
      // 基于准备好的dom,初始化echarts实例
      const myChart = echarts.init(domRef.current);
      // 绘制图表
      myChart.setOption({
        title: {
          text: title
        },
        tooltip: {},
        xAxis: {
          data: xData
        },
        yAxis: {},
        series: [
          {
            name: '框架',
            type: 'bar',
            data: yData
          }
        ]
      });
    }
    chartInit()
  }, [title, xData, yData])
  return (
    <div>
      {/* 为echart准备一个dom节点 */}
      <div ref={domRef} style={style}></div>
    </div>
  )
}

pages/Home/index.js

import React from 'react'
import Bar from '@/components/Bar'

export default function Home() {
  return (
    <div>
      <Bar
        title='主流框架使用满意度'
        xData={['React', 'Vue', 'Angular']}
        yData={[40, 50, 30]}
        style={{ width: '500px', height: '400px' }}
      />
      <Bar
        title='主流框架使用满意度2'
        xData={['React', 'Vue', 'Angular']}
        yData={[70, 80, 40]}
        style={{ width: '300px', height: '200px' }}
      />
    </div>
  )
}

pages/Home/index.scss

.home {
  width: 100%;
  height: 100%;
  align-items: center;
}

下篇文章:内容管理模块的实现
专栏订阅入口【React–从基础到实战】

你可能感兴趣的:(React--从基础到实战,前端,javascript,react.js,antd)