React+TS使用鉴权进行登录访问控制(移动端)
前言
在PC端中经常遇见的需求:
- 没有登录也能访问的页面,比如登录页,404页面
- 只有登录,有了令牌才能访问的页面,比如个人信息(只有登录了.你才能访问的页面) 因此,我们需要在项目中使用鉴权进行登录访问控制,获得令牌之后才能访问,如果没有登录就访问,让页面跳转到登录页进行登录
在vue当中使用路由守卫就可以实现,但是在React中没有导航守卫,这就需要我们自己封装了
那么我们如何实现?
首先分析一下,不管是哪个页面他们都是使用路由进行访问的,所以我们可以从路由下手,封装组件判断是否登录
登录就显示要访问的页面
没有登录就跳转到登录页面,让用户进行登录
在react-router-dom提供了鉴权示例
 那么如何在React+TS封装自己的鉴权组件?
那么如何在React+TS封装自己的鉴权组件?
React+TS封装自己的鉴权组件
实现步骤:
- 
定义私有路由组件。在 components 目录中创建 PrivateRoute路由组件:实现路由的登录访问控制逻辑 - 有token,正常访问
- 没有token,重定向到登录页面,并传递要访问的路由地址
 
- 
使用路由组件 - 将需要权限才能访问的页面,使用私有路由组件
 
分析PrivateRoute鉴权路由组件
- 
场景:限制某个页面只能在登录的情况下访问。 
- 
说明:在 React 路由中并没有直接提供该组件,需要手动封装,来实现登录访问控制(类似于 Vue 路由的导航守卫)。 
- 
如何封装?参考 react-router-dom 文档中提供的鉴权示例 。 
- 
如何使用?使用PrivateRoute 组件代替默认的 Route 组件,来配置路由规则。 
- 
PrivateRoute 组件实际上就是对原来的 Route 组件做了一次包装,来实现了一些额外的功能。 
- 
<Route path component render>render 方法,指定该路由要渲染的组件内容(类似于 component 属性)。
- 
Redirect 组件:重定向组件,通过 to 属性,指定要跳转到的路由信息。 
- 
state 属性:表示给路由附加一些额外信息,此处,用于指定登录成功后要进入的页面地址。 
使用方式:
   <PrivateRoute path="/xxx/xxx">
            <ProfileEdit />  // 登录之后才能访问的页面
   </PrivateRoute>
实现步骤:
处理Token
    // 用来对{token: string, refresh_token: string }做本地持久化
    import { Token } from '@/types/data'
    const TOKEN_KEY = 'geek-app'
    // 获取 token
    export function getToken (): Token {
      // 字符串转对象
      return JSON.parse(localStorage.getItem(TOKEN_KEY) || '{}')
    }
    // 设置 token
    export function setToken (data: Token): void {
      // 对象转字符串
      localStorage.setItem(TOKEN_KEY, JSON.stringify(data))
    }
    // 移除 token
    export function removeToken (): void {
      localStorage.removeItem(TOKEN_KEY)
    }
    // 判断是否登录(授权)
    export function hasToken (): boolean {
      return !!getToken().token
    }
权限判断(封装PrivateRoute)
import { hasToken } from '@/utils/storage'
import { Route, Redirect, RouteProps } from 'react-router-dom'
// RouteProps 特有的类型
export const PrivateRoute = ({ children, ...rest }: RouteProps) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (hasToken()) {
          return children
        }
        return (
          <Redirect
            to={{
              pathname: '/login',
              state: {
                from: props.location.pathname // 回跳地址
              }
            }}
          />
        )
      }}
    />
  )
}
使用PrivateRoute
在App.tsx
      <PrivateRoute path="/profile/edit">
        <ProfileEdit />
      </PrivateRoute>
登录成功处理
import { useHistory, useLocation } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import { Button, NavBar, Form, Input, List, Toast } from 'antd-mobile'
const dispatch = useDispatch()
//  登录收集信息
  const onFinish = async (values:loginForm) => {
    // console.log(values)
    try {
      await dispatch(getToken(values))
      // 轻提示
      Toast.show({
        icon: 'success',
        content: '登录成功',
        // 在关闭之后调用
        afterClose: () => {
        // location.state?.from 回跳
          const path = location.state?.from || '/'
          history.replace(path)
        }
      })
    } catch (e) {
      const err = e as AxiosError<{message:string}>
      const content = err.response?.data.message || '登录失败'
      Toast.show({
        icon: 'fail',
        content
      })
    }
  }
封装history
import { createBrowserHistory } from 'history'
const history = createBrowserHistory()
export default history
App.tsx 修改
import history from '@/utils/history'
<Router history={history}><Router>
修改响应拦截
对401状态的处理
  // 添加响应拦截器
instance.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response
  },
  async function (error) {
    // 对响应错误做点什么
    const er = error as AxiosError
    if (!er.response) {
      Toast.show({ content: '网络异常' })
      return Promise.reject(error)
    }
    if (er.response?.status === 401) {
      console.log(401)
      const { refresh_token } = getToken()
      // 没有 refresh_token 的情况
      if (!refresh_token) {
        Toast.show({ content: '请重新登录' })
        // 跳到登录页
        history.push({
          pathname: '/login',
          state: { from: history.location.pathname }
        })
        return Promise.reject(error)
      }
      try {
        const res = await axios.put(baseURL + 'authorizations', null, {
          headers: {
            Authorization: `Bearer ${refresh_token}`
          }
        })
        console.log(res)
        const newToken = { token: res.data.data.token, refresh_token }
        setToken(newToken)
        store.dispatch({
          type: 'login/token',
          payload: newToken
        })
        return instance(er.config)
      } catch (error) {
        Toast.show({ content: '请重新登录' })
        // 跳到登录页
        history.push({
          pathname: '/login',
          state: { from: history.location.pathname }
        })
        return Promise.reject(error)
      }
    }
    return Promise.reject(error)
  }
)
转载自:https://juejin.cn/post/7083481223384793096




