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

Posted codeMak1r.小新

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端实战|React18极客园——布局模块(useRoutes路由配置处理Token失效退出登录)相关的知识,希望对你有一定的参考价值。

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

😇本文目录😇

本文被专栏【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包

实现步骤

  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图表封装展示

需求描述:

  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–从基础到实战】

以上是关于前端实战|React18极客园——布局模块(useRoutes路由配置处理Token失效退出登录)的主要内容,如果未能解决你的问题,请参考以下文章

前端工程化实战:React 模块化开发性能优化和组件化实践

Weex实战分享|Weex在极客时间APP中的实践

2019大前端热门技术流之React服务器端渲染NextJS实战

使用React简短代码动态生成栅格布局

使用React简短代码动态生成栅格布局

使用React简短代码动态生成栅格布局