如果你是一个vue工程师,第一次使用react(hooks)去编写,恭喜你,看这篇文章就对了。我将用react-hook的写法去实现一个简单的登录登出功能,让你体会react代码编写思维和vue是有一些不同的。
还需要借助第三方hooks库ahooks,用户数据暂不考虑权限
代码学习来源:双越老师的视频,感谢!
用户信息我认为存在redux中是最好的:
下面代码里涉及到redux使用的就不细说里面的代码了,看变量名称见词答意就好。
第一个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去拿用户数据,与登录表单页面直接解耦了,很纯粹。
那么这个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每次刷新页面,都会重新调用接口拿到最新的用户信息。
当我们的用户信息从后端拉取到浏览器本地时,就会存放在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
主要是通过用户的登录状态来判断是否要自动做路由跳转,是一个实时监听的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的写法我觉得难点有如下:
还有就是这次的登录登出hook的写法只是给你提供一个例子让你体会下用hooks编写时候的编码思路,并不是所有业务都适合于这套,主要是抛砖引玉。