React-router 5

彭宜人
2023-12-01

React-router5

什么是前端路由?

把不同路由对应不同的内容或页面的任务交给前端来做。

前端路由原理及实现方式

本质是监听URL变化,然后匹配路由规则,显示相应的页面,并且无需刷新页面。

目前前端使用的路由有2种实现方式:Hash模式,History模式

Hash模式

像www.testpage.com/#/ 这样的就是一个hash URL,当#后面的哈希值发生变化时,可以通过hashChange事件来监听URL变化,从而跳转页面。无论hash值怎么变化,服务端接收到的URL请求永远是www.testpage.com。Hash模式相对来说简单,兼容性也更好。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <a href="#view1">view1</a>
  <a href="#view2">view2</a>
  <a href="#view3">view3</a>
  <div id="app"></div>
</body>
<script>
  function hashChangeHandler() {
    console.log(window.location.hash); // 获取#hash
    document.getElementById('app').innerHTML = window.location.hash
  }
  window.addEventListener('hashchange',hashChangeHandler)
</script>
</html>

History模式

History模式是HTML5新推出的功能,主要使用history.pushState和history.replaceState改变URL

通过History模式改变URL同样不会引起页面的刷新,只会更新浏览器的历史纪录。

当用户做出浏览器动作时(如点击后退按钮),会触发popState事件.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <ul id="nav">
    <li class="js-news">news</li>
    <li class="js-shopping">shopping</li>
    <li class="js-music">music</li>
  </ul>
  <div id="app"></div>
</body>
<script>
  const nav = document.getElementById('nav')
  nav.addEventListener('click',function(e) {
    const target = e.target
    switch (target.className) {
      case 'js-news':
        history.pushState("新闻","",location.href+"/news")
        break;
      case 'js-shopping':
      history.pushState("购物","",location.href+"/shopping")
        break
      default:
      history.pushState("音乐","",location.href+"/music")
        break;
    }
  })
  // 回退或者前进时触发
  function popStateEvent(e) {
    console.log('popStateEvent');
    console.log(e);
  }
  window.addEventListener('popstate',popStateEvent)
</script>
</html>

安装

npm install --save react-router-dom (npm i -S react-router-dom)

React-router官方文档

核心API

react-router中奉行一切皆组件的思想,路由器Router,链接Link,路由Route,独占Switch,重定向Redirect都以组件形式存在。

Browser Router

基于HTML5提供的history API(pushState,replaceState和popstate事件)来保持UI和URL同步。

History Router

使用URL的hash部分(window.location.hash)来保持UI和URL同步

Link

提供声明式的,可访问的导航。

Redirect

渲染<Redirect>使导航到一个新的地址。这个新的地址会覆盖history栈中当前的地址。类似服务端(HTTP 3XX) 的重定向。

Route

Route的path属性与某个location匹配时呈现一些UI

Switch

渲染与该地址匹配的第一个子节点<Route>或者<Redirect>

withRouter

通过withRouter高阶组件访问history对象的属性和最近的<Route>的match.

当路由渲染时,withRouter会将已更新的match,location ,history 属性传递给被包裹的组件。

基本使用

1、Route渲染内容的三种方式

Route渲染优先级: children > component > render

三者能接收到同样的route props,包含match,location,history。当不匹配的时候,children的match为null。

这三种方式互斥,只能选用一种。

children:func . 不管location是否匹配都会被渲染。

render:func   只有当location匹配时才会渲染,以函数形式调用。

注意:要把route props传递下去,否则在组件中要用withRouter高阶组件包裹才能获取到。

component:component  只有当location匹配时才会渲染。

注意:渲染component的时候会调用React.createElement.如果使用匿名函数调用,每次都会生成一个新的匿名函数,导致组件重复卸载/挂载

      <Router>
        <ul>
            <li><Link to="/">home</Link></li>
            <li><Link to="/list">list</Link></li>
            <li><Link to="/detail">detail</Link></li>    
        </ul>
        <Route path="/" component={Home} />
        <Route
          exact
          path="/list"
          // 三种渲染方式互斥,只能选择其一
          children={(props) => <List {...props}/>}
          component={Home}
          render={(props) => <List {...props} />}
          // 以下是错误示范,会导致组件不断卸载、挂载
          // component={() => <Home /> }         
        />
        <Route path="/detail" component={ Detail}/>
        <Route component={ NotFound}/>
      </Router>

2、Route props

①、history对象,通常具有以下属性和方法。

  • length-(number):history堆栈的条目数
  • action-(string):当前的操作(PUSH,REPLACE,POP)
  • push(path,[state])-(function):在history堆栈添加一个新的条目
  • replace(path,[state]) -(function):替换在history 堆栈中的当前条目。
  • go(n)-(function):将history堆栈中的指针调整n
  • goBack()-(function):等同于go(-1)
  • goForward()-(function):等同于go(1)
  • location-(object):当前的位置。包含以下属性:(*history对象是可变的,不要从这里获取location对象)
  1. pathname(string):URL路径
  2. search(string):URL中哈希片段
  3. state(object):提供给例如使用push操作将location放入堆栈时的特定location状态

可以通过如下方式获取history对象:

  • Route component: props.history
  • Route render:({history}) => ()
  • Route children:({history}) => ()
  • withRouter: props.history

②、location对象:代表应用程序现在在哪,它曾经在哪,你想让它去哪。可以通过如下方式获取

  • Route component: props.location
  • Route render:({location}) => ()
  • Route children:({location}) => ()
  • withRouter: props.location

③、match对象:包含有关如何匹配URL的信息,match对象包含以下属性。

  • params-(object): key/value与动态路径的URL对应的解析内容 
  • isExact-(boolean):  是否精准匹配

 可以通过如下方式获取match对象

  • Route component: props.match
  • Route render:({match}) => ()
  • Route children:({match}) => ()
  • withRouter: props.match

3、hooks API(react-router-dom 5*版本后添加 且 仅在函数组件中使用)

useHistory:获取history对象

useLocation:获取location对象

useRouteMatch:获取match对象

useParams:获取动态路由参数

动态路由

1) 定义路由

 <Route path="/detail/:id" component={ Detail}/>

2)定义导航

<ul>
      <li><Link to="/detail/123">123</Link></li>
      <li><Link to="/detail/456">456</Link></li>
      <li><Link to="/detail/789">789</Link></li>
</ul>

3)创建Detail组件接收参数

import React from 'react'
import { useHistory, useLocation, useParams, useRouteMatch } from 'react-router'

const Detail = () => {
  const { goBack} = useHistory() // 获取history对象
  // const location = useLocation()  // 获取location对象
  // const match = useRouteMatch()  // 获取match对象
  const params = useParams()  // 获取路由参数

  return <div>
    <p>detail page</p>
    <p>id is { params.id}</p>
    <button onClick={() => {goBack() }}>返回上一页</button>
   </div>
}
 
export default Detail

嵌套路由

(1)定义路由和导航 app.js (主要用到render渲染)

 <Router>
        <ul>
          <li><Link to="/">home</Link></li>
          <li><Link to="/list">list</Link></li>
          <li><Link to="/music">music</Link></li>
        </ul>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route
            exact
            path="/list"         
            component={List}             
          />
          {/* 动态路由 */}
          <Route exact path="/detail/:id" component={Detail} />
          {/* 路由嵌套 */}
          <Route path="/music" render={(props) => <Music>           
            <Route {...props} exact path="/music" component={Classic} />
            <Route {...props} path="/music/pop" component={ PopMusic} />         
          </Music>} />
          {/* 404 */}
          <Route component={NotFound} />
        </Switch>
      </Router>

 (2)嵌套路由的页面 music.js (props.children接收)

import React from 'react';
import { Link} from 'react-router-dom'

const Music = props => {
 
  return (
    <div>
      <p>This is Music Page</p>
      <ul>
        <li><Link to="/music">jump to classcic</Link></li>      
        <li><Link to="/music/pop">jump to pop</Link></li>
      </ul>
      {props.children}
    </div>
  );
};

export default Music;

路由守卫

思路:使用高阶组件包装Route使其具有权限判断功能。

做个小例子:假设有首页,用户中心,登录 3个页面;用户中心页面只有登录了才能查看。没登录的要重定向至登录页。登录态保存在store中。

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import store from './store'
import { Provider} from 'react-redux'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={ store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);


app.js

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
  Switch,
  Redirect
} from 'react-router-dom'

import Home from './pages/Home'
import UseCenter from './pages/Usercenter'
import Login from './pages/Login'
import NotFound from './pages/NotFound'
import PriviateRoute from './router/PriviateRoute'
import { useSelector} from 'react-redux'

function App () {
  const { isLogin } = useSelector(({ loginReducer}) => loginReducer)
   return  <div className="App">    
   <Router>
     <ul>
       <li><Link to="/">首页</Link></li>
       <li><Link to="/userCenter">用户中心</Link></li>
       <li><Link to="/login">登录</Link></li>
     </ul>
     <Switch>
       <Route path="/" exact component={Home} />
         {/* <Route path="/userCenter" component={UseCenter} /> */}
         <PriviateRoute isLogin={ isLogin} path="/userCenter" component={UseCenter} />
       <Route path="/login" component={ Login}/>        
       {/* 404 */}
       <Route component={NotFound} />
     </Switch>
   </Router>     
 </div>
}

export default App;

router/priviateRoute.js (路由守卫)

import React from 'react'
import { Redirect, Route} from 'react-router-dom'

/**
 * 路由守卫
 已登录:该去哪去哪
 未登录:重定向至登录页,并且携带参数,以便登录后自动跳转到原来的页面
 */
function PriviateRoute ({ isLogin,component:Component,...rest}) {
  return <Route
    {...rest}
    render={(props) =>
      isLogin ? <Component {...props}/>
              : <Redirect to={{ pathname: '/login', state: {from:props.location.pathname}}
            } />
    } />
}

export default PriviateRoute

store/index.js

import { createStore,combineReducers} from 'redux'
import { loginReducer } from './reducer'

const store = createStore(
  combineReducers({loginReducer})
)

export default store

store/action.js

export const IS_LOADING = "IS_LOADING"

export const LOGIN_SUCCESS = "LOGIN_SUCCESS"
export const LOGIN_FAILED = "LOGIN_FAILED"

export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS"
export const LOGOUT_FAILED = "LOGOUT_FAILED"

store/reducer.js

import { IS_LOADING, LOGIN_FAILED, LOGIN_SUCCESS, LOGOUT_SUCCESS } from './action'

const defaultLoginState = {
  isLogin: false,
  userInfo: { id: null, name: '' },
  loading: false,
  err: {msg:''}
}
export const loginReducer = (state = defaultLoginState, { type, payload }) => {
  switch (type) {
    case IS_LOADING:
      return {...state,loading:true}
    case LOGIN_SUCCESS:     
      return { ...state, isLogin: true,loading:false, userInfo: {...payload}}
    case LOGIN_FAILED:
      return { ...state, ...defaultLoginState,...payload}
    case LOGOUT_SUCCESS:
      return {...state,isLogin:false,loading:false}
    default:
      return state
  }
}

Login.jsx

import React from 'react'
import { useSelector,useDispatch} from 'react-redux'
import { Redirect, useLocation } from 'react-router'

const Login = (props) => {
  const dispatch = useDispatch()
  const { isLogin, loading, err, userInfo } = useSelector(({ loginReducer }) => loginReducer)
  const location = useLocation()
  const { from = "/" } = location.state || {}

  const login = () => {
    dispatch({ type: 'LOGIN_SUCCESS', payload: { id:1,name:'Amy'}})
  }
  if (isLogin) {
    return <Redirect to={from}/>
   }

  return <div>
    <p>登录页</p>
    <button onClick={ login}>{ loading?"...loading":"登录"}</button>
  </div>
}

export default Login

UserCenter.jsx

import React from 'react'
import { useSelector} from 'react-redux'

const Usercenter = (props) => {
  const { userInfo} = useSelector(({ loginReducer }) => loginReducer)
  return <div>
    <p>用户中心</p>
    <p>{ userInfo.name},欢迎您</p>
  </div>
}

export default Usercenter

当然,实际项目中登录态肯定都是通过接口获取的,上例主要是为了演示路由守卫。

对于处理异步请求,推荐使用react-redux中间件。

预计下一篇文章比较redux-thunk,redux-saga.

 类似资料: