【场景优化】提供一个用hooks编写的简单登录登出功能的方案(react)

文章目录

  • 前言
  • 用户信息存储在redux中
  • useLoadUserData 获取接口用户信息
    • 在layout中调用
  • useGetUserInfo 获取本地用户信息
  • useNavPage 路由跳转判断
  • 登录与登出的逻辑梳理
    • 登录
    • 登出
  • 尾巴

前言

如果你是一个vue工程师,第一次使用react(hooks)去编写,恭喜你,看这篇文章就对了。我将用react-hook的写法去实现一个简单的登录登出功能,让你体会react代码编写思维和vue是有一些不同的。

还需要借助第三方hooks库ahooks,用户数据暂不考虑权限

代码学习来源:双越老师的视频,感谢!


用户信息存储在redux中

用户信息我认为存在redux中是最好的:

  • 安全,如果存在本地缓存中是可以被看到的,也不用考虑加密
  • 刷新数据就没,重新拉取最新数据
  • 能够集中化在redux对应用户模块中处理,各个地方调取方便

下面代码里涉及到redux使用的就不细说里面的代码了,看变量名称见词答意就好。


useLoadUserData 获取接口用户信息

第一个hook功能为获取用户信息,就是页面第一次加载的时候,判断redux中是否存在用户名称,没有就去调用接口,获取后设置在redux中;有就什么都不处理。最终返回一个是否正在获取用户信息的变量。

import { useEffect, useState } from 'react'
import { useRequest } from 'ahooks'
import { useDispatch } from 'react-redux'
import useGetUserInfo from './useGetUserInfo'
import { getUserInfoService } from '../services/user'
import { loginReducer } from '../store/userReducer'
function useLoadUserData() {
  const dispatch = useDispatch()
  const [waitingUserData, setWaitingUserData] = useState(true) // 用来记录获取用户信息是否还在请求中

  // 接口获取用户信息
  const { run } = useRequest(getUserInfoService, {
    manual: true, // 手动触发
    onSuccess(result) {
      const { username, nickname } = result
      dispatch(loginReducer({ username, nickname })) // 调用action存储用户信息到 redux
    },
    onFinally() {
      setWaitingUserData(false)
    },
  })

  // 判断当前 redux 是否已经存在用户信息
  const { username } = useGetUserInfo() // 一般用一个用户名判断即可
  useEffect(() => {
    if (username) {
      setWaitingUserData(false) // 如果 redux 已经存在用户信息,就不用重新加载了
      return
    }
    run() // 如果 redux 中没有用户信息,就要去调接口
  }, [username])

  return { waitingUserData }
}

export default useLoadUserData

getUserInfoService这个接口只是通过token去拿用户数据,与登录表单页面直接解耦了,很纯粹。

在layout中调用

那么这个hook那里调用好呢?我们可以放在最外层的layout组件中:

const MainLayout: FC = () => {
    const { waitingUserData } = useLoadUserData()
	
	return <Layout></Layout>
}

有什么好处呢?我们知道基本所有的页面(包括首页、登录页、404页等)都在layout组件中,咱们的layout组件一般是这样设计的layout嵌套layout。

举例说明一个两层layout的设计,第一层layout是决定整个页面的整体布局,例如头部、尾部、内容部分就可以嵌套第二层layout路由组件:

import React, { FC } from 'react'
import { Outlet } from 'react-router-dom'
import { Layout, Spin } from 'antd'
import useLoadUserData from '../hooks/useLoadUserData'

const { Header, Content, Footer } = Layout

const MainLayout: FC = () => {
  const { waitingUserData } = useLoadUserData()

  return (
    <Layout>
      <Header>
      </Header>
      <Layout>
        <Content>
          {waitingUserData ? (
            <div style={{ textAlign: 'center', marginTop: '60px' }}>
              <Spin />
            </div>
          ) : (
            <Outlet /> // 路由组件
          )}
        </Content>
      </Layout>
      <Footer></Footer>
    </Layout>
  )
}

第二层layout:

import React, { FC } from 'react'
import { Outlet } from 'react-router-dom'

const ManageLayout: FC = () => {

  return (
    <div> // 结构具体怎么写看自己,Outlet组件是一定要写的
       <Outlet /> // 路由组件
    </div>
  )
}

路由表可以这样设计:

const router = createBrowserRouter([
  // 一级layout
  {
    path: '/',
    element: <MainLayout />, // 里面调用useLoadUserData
    children: [
      {
        path: '/',
        element: <Home />,
      },
      {
        path: 'login',
        element: <Login />,
      },
      // ...
      // 二级layout
      {
        path: 'manage',
        element: <ManageLayout />,
        children: [
          {
            path: 'list',
            element: <List />,
          },
          // ...
        ],
      },
      {
        path: '*', // 404 路由配置,都写在最后(兜底)
        element: <NotFound />,
      },
    ],
  },
  // 一级layout
  {
    path: 'question',
    element: <QuestionLayout />, // 里面调用useLoadUserData
    children: [
      {
        path: 'edit/:id',
        element: <Edit />,
      },
      // ...
    ],
  },
])

hook放在最外层的layout每次刷新页面,都会重新调用接口拿到最新的用户信息。


useGetUserInfo 获取本地用户信息

当我们的用户信息从后端拉取到浏览器本地时,就会存放在redux中,redux通过action获取用户信息的写法还是挺麻烦的,可以通过hook再封装一层:

import { useSelector } from 'react-redux'
import { StateType } from '../store' // 整个store数据的类型
import { UserStateType } from '../store/userReducer' // store中userInfo数据的类型

function useGetUserInfo() {
  const { username, nickname } = useSelector<StateType>(state => state.user) as UserStateType
  return { username, nickname }
}

export default useGetUserInfo

useNavPage 路由跳转判断

主要是通过用户的登录状态来判断是否要自动做路由跳转,是一个实时监听的hook

import { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import useGetUserInfo from './useGetUserInfo'
import {
  isLoginOrRegister,
  isNoNeedUserInfo,
  MANAGE_INDEX_PATHNAME,
  LOGIN_PATHNAME,
} from '../router/index'

function useNavPage(waitingUserData: boolean) {
  const { username } = useGetUserInfo()
  const { pathname } = useLocation()
  const nav = useNavigate()

  useEffect(() => {
    // 1 还在获取用户信息
    if (waitingUserData) return

    // 2 已经登录了
    if (username) {
      // 如果在登录页或者注册页,直接跳到功能使用页,否则停留在本页面
      if (isLoginOrRegister(pathname)) {
        nav(MANAGE_INDEX_PATHNAME)
      }
      return
    }

    // 3 未登录
    // 如果当前页面不需要用户登录,就停留在本页面
    if (isNoNeedUserInfo(pathname)) {
      return
    } else {
      nav(LOGIN_PATHNAME) // 否则跳转到登录页面
    }
  }, [waitingUserData, username, pathname])
}

export default useNavPage

这个hook通过监听路由地址也充当了路由守卫了。同样的这个hook也是推荐放在最外层的layout组件中执行:

const MainLayout: FC = () => {
    const { waitingUserData } = useLoadUserData()
	useNavPage(waitingUserData)
	
	return <Layout></Layout>
}

登录与登出的逻辑梳理

登录

当用户未登录(此时没token或者token过期),来到登录页面或其他的时候,会执行layout组件的useLoadUserData 获取接口用户信息,返回错误码,然后提示用户未登录(在其他页面会自动跳到登录页)

当登录页表单提交后,调取登录接口,拿到token,通过redux修改username为null触发useLoadUserData 后,登录页再主动跳到功能页面去。

登出

点击退出的时候,只需要清除redux用户数据、token数据再给跳转到登录页即可(虽然useLoadUserData会再执行一次但无伤大雅)。


尾巴

hook的写法我觉得难点有如下:

  • 怎么让useEffect的依赖项能够适配各种情况,怎么去完美的控制里面的那些异步操作。
  • 怎么去设计避免hook只能使用在最外层作用域的局限性。

还有就是这次的登录登出hook的写法只是给你提供一个例子让你体会下用hooks编写时候的编码思路,并不是所有业务都适合于这套,主要是抛砖引玉。

你可能感兴趣的:(场景方案,react.js,javascript,前端)