React18 (六) Router5

Router5安裝:

npm

npm install react-router-dom@5 -S

yarn

yarn add react-router-dom@5

App.js

import './App.css';
import React, { useReducer, useState } from 'react'
import CartContext from './store/CartContext';
import { Route } from 'react-router-dom';
import Home from './component/Home/Home';
import Shopping from './component/Shopping/Shopping';
import Member from './component/Member/Member';
import Bonus from './component/Bonus/Bonus';
import BottomNavigation from '@mui/material/BottomNavigation';
import BottomNavigationAction from '@mui/material/BottomNavigationAction';
import RestoreIcon from '@mui/icons-material/Restore';
import FavoriteIcon from '@mui/icons-material/Favorite';
import ArchiveIcon from '@mui/icons-material/Archive';
import Paper from '@mui/material/Paper';
import { Link, Redirect, Switch, useHistory, useLocation } from 'react-router-dom/cjs/react-router-dom.min';
import Wechat from './component/Bonus/Wechat/Wechat';

const cartsReducer = (params, action) => {
  const newCarts = { ...params };
  switch (action.type) {

    case 'ADD':
      if (newCarts.items.indexOf(action.meal) === -1) {
        newCarts.items.push(action.meal);
        action.meal.attributes.amount = 1;
      } else {
        action.meal.attributes.amount += 1;
      }
      newCarts.totalAmount += 1;
      newCarts.totalPrice += action.meal.attributes.price;
      return newCarts;

    case 'REMOVE':
      if (--action.meal.attributes.amount <= 0) {
        newCarts.items.splice(newCarts.items.indexOf(action.meal), 1);
      }
      newCarts.totalAmount -= 1;
      newCarts.totalPrice -= action.meal.attributes.price;
      return newCarts;

    case 'CLEAR':
      newCarts.items.forEach(item => delete item.attributes.amount);
      newCarts.items = [];
      newCarts.totalAmount = 0;
      newCarts.totalPrice = 0;
      return newCarts;

    default:
      return params;
  }
}

function App() {

  const [carts, cartsDispatch] = useReducer(cartsReducer, {
    items: [],
    totalAmount: 0,
    totalPrice: 0
  });

  const [value, setValue] = useState('/');

  const _history = useHistory();

  const gotoHander = (event, newValue) => {
    setValue(newValue);
    _history.push(newValue);
  }

  const homeProps = {
    name: 'hanbao'
  }

  const memberProps = {
    name: 'member',
    params: {}

  }

  return (
    <CartContext.Provider value={{ ...carts, cartsDispatch }}>

      {/* <Route exact path = '/' component ={Home}/> */}
      <Route exact path='/' children={<Home {...homeProps} />} />
      <Route path='/bonus' component={Bonus} />
      <Switch>
        <Route exact path='/bonus/wechat' component={Wechat} />
      </Switch>
      <Route exact path='/shopping' component={Shopping} />
      <Route exact path='/home' component={Home} />
      <Route path='/member' render={() => <Member {...memberProps} />} />


      <Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={1}>
        <BottomNavigation
          showLabels
          value={value}
          onChange={gotoHander}
        >
          <BottomNavigationAction label="Home" value="/" icon={<RestoreIcon />} />
          <BottomNavigationAction label="Bonus Plan" value="/bonus" icon={<FavoriteIcon />} />
          <BottomNavigationAction label="Store" value="/shopping" icon={<ArchiveIcon />} />
          <BottomNavigationAction label="Member" value={`/member/${homeProps.name}`} icon={<ArchiveIcon />} />
        </BottomNavigation>
      </Paper>
    </CartContext.Provider>

  );
}

export default App;

1. React-Router-Dom包

react router適用於web和原生項目,我們在web項目中使用,所以需要引入的包是react-router-dom。

2. BrowserRouter組件

和Redux類似,要使得路由生效,需要使用Router組件將App組件包裹起來。這裏我們選擇的是BrowserRouter,除了BrowserRouter外還有其他的Router,暫時我們只介紹BrowserRouter。

下面樣例中,爲BrowserRouter起一個別名Router,這樣一來我們在切換Router時,只需要修改引用位置,而不需要修改其他代碼,像是這樣:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { HashRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store/index';

//
document.documentElement.style.fontSize = 100 / 750 + 'vw';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <Router>
      <App />
    </Router>
  </Provider>
);

3. Route組件

route組件是路由的映射組件,通過該組件將url地址和React組件進行映射,映射後當url地址變爲指定地址時指定的組件就會顯示,否則不顯示。

<Route exact path='/' children={<Home {...homeProps} />} />
<Route path='/bonus' component={Bonus} />

上例中,路徑/<Home/>組件進行了映射,路徑/bonus<Bonus/>組件進行了映射。當訪問http://localhost:3000/bonus時,Bonus組件會自動渲染顯示,默認是渲染Home組件

Route組件可以設置以下幾個屬性

path,exact,strict,component,render,children,location,sensitive

3.1 path

用來設置要映射的路徑,可以是一個字符串或字符串數組。字符串用來匹配單個路徑,數組可以匹配多個路徑。看一個數組的例子:

<Route path={["/", "/member"]}>
    <Member/>
</Route>

使用數組映射後,當我們訪問數組中的路徑時都會使組件掛載。設置路徑時也可以在路徑中設置參數,比如:/member/:id其中id就是一個參數,可以動態的傳遞:id的值,換句話說/member/1/member/2,都會觸發組件掛載。

設置動態參數後,在組件的內部可以使用useParams()鉤子來讀取參數:

const Member = () => {
    const {id} = useParams();
    return <div>userId:{id}</div>
};

...略...
<Route path="/member/:id">
    <Member/>
</Route>
...略...

3.2 exact

路由的匹配默認並不是完整匹配,這意味着路由映射的地址是/home時,只要我們訪問的路徑是以/home開頭就會觸發組件的掛載,默認情況下是不會檢查路由的子路徑的。比如:/home/123/home/456等都會導致home組件掛載。

exact屬性用來設置路由地址是否完整匹配,它需要一個布爾值,默認爲false,就像上邊的情況。如果設置爲true,那麼只有地址和path完全一致時,組件纔會掛載。

<Route path="/home" exact>
    <Home/>
</Route>

3.3 strict

布爾值,默認值爲false。false時,會匹配到以/結尾的路徑。比如:path設置爲/home默認情況下/home/也會導致組件掛載。設置爲true時,以/結尾的路徑不會被匹配。

3.4 component

設置路徑匹配後需要掛載的組件。作用和Route的標籤體類似。

<Route path="/home" component={Home}/>

和標籤體指定組件不同,如果通過component屬性指定組件,React Router會自動向組件中傳遞三個參數matchlocationhistory

3.4.1 match

對象,表示請求匹配的路徑信息,其中包含四個屬性:

param —— 請求參數
isExact —— 布爾值,請求路徑是否完整匹配
path —— 請求路徑的規則
url —— 匹配到的url地址
3.4.2 location

對象,表示瀏覽器地址欄的信息,請求完整路徑、查詢字符串等,可能具有的屬性:

pathname —— 請求的路徑
search —— 查詢字符串
hash —— hash字符串
state —— 歷史記錄中的狀態對象,可以用來在跳轉時傳遞數據
3.4.3 history

對象,用來讀取和操作瀏覽器的歷史記錄(頁面跳轉)等功能,屬性:

length —— 歷史記錄的數量
action —— 當前歷史記錄的狀態,pop(前進、後退、新記錄創建、索引發生變化);push(新記錄添加);replace(歷史記錄被替換)
location —— location對象
push() —— 添加新的歷史記錄
replace() —— 替換歷史記錄
go() —— 跳轉到指定記錄
goBack() —— 回退
goForward() —— 前進
block() —— 用來阻止用戶跳轉行爲,可以用Prompt組件代替

3.5 render

render也是Route組件中的屬性,和component類似,也用來指定路徑匹配後需要掛載的組件。只是render需要的是一個回調函數作爲參數,組件掛載時,render對應的回調函數會被調用,且函數的返回值會成爲被掛載的組件。render的回調函數中會接收到一個對象作爲參數,對象中包含三個屬性,即matchlocationhistory,我們可以根據需要選擇是否將其傳遞給組件。

<Route path="/member/:id" render={routeProps => <Member {...routeProps}/>} />

3.6 children

children實際上就是組件的組件體,設置方式有兩種一個是通過組件體設置,一個是通過children屬性設置。它的值也有兩種方式,一種直接傳遞組件,這樣當路徑匹配時組件會自動掛載。一種是傳遞一個回調函數,這樣它和render的特點是一樣的。

直接設置組件:

<Route path="/mrmber/:id" children={<Member/>} />
<Route path="/member/:id"> <Member/> </Route>

傳遞迴調函數:

<Route path="/member/:id" children={routeProps => <Member {...routeProps}/>} />
<Route path="/member/:id"> {routeProps => <Member {...routeProps}/>} </Route>

需要注意的時,當children接收到的是一個回調函數時,即使路徑沒有匹配組件也會被掛載到頁面中(沒有使用Switch標籤的情況下),這一特性可以在一些特殊應用場景下發揮作用。如果不希望出現路徑不匹配時組件被掛載的情況,最好選擇使用render來代替。

4. Switch組件

Switch組件是Route組件的外部容器,可以將Route組件放入到Switch組件中。放入Switch組件中後,匹配路徑時會自動自上向下對Route進行匹配,如果匹配到則掛載組件,並且一個Switch中只會有一個Route被掛載。如果將Route組件單獨使用,那麼所有的路徑匹配的Route中的組件都會被掛載。

5. Link組件

Link組件作用類似於a標籤(超鏈接),並且Link組件在瀏覽器中也會被渲染爲超鏈接。但是Link組件生成的鏈接點擊後只會修改瀏覽器地址欄的url,並不會真的向服務器發送請求。這種方式有利於組件的渲染,所以在開發中應該使用Link組件而不是超鏈接。

其他組件

1. HashRouter組件

當我們使用BrowserRouter時,路徑會直接根據url地址進行跳轉,也就是我們在使用應用時在瀏覽器的地址欄看到的地址就和我們正常去訪問網頁一樣。但是,HashRouter不是這樣,使用HashRouter時,組件的跳轉不再是以完整的url形式,而是通過url地址中的hash值進行跳轉(url地址中#後的內容爲hash值)。

BrowserRouter的地址欄

http://localhost:3000/home

HashRouter的地址欄

http://localhost:3000/#/home

爲什麼會有這兩種Router呢?首先,我們的項目在開發完成後需要進行構建,構建後的代碼需要放到服務器中供用戶訪問。服務器無非就是Nginx,Apache或者NodeJS這些東西,服務器的主要功能是將url地址和網頁進行映射。傳統web項目中,每一個頁面都對應一個文件,當用戶訪問/index.html時,服務器會自動返回根目錄下的index.html。當用戶訪問/home.html時,服務器會返回根目錄下home.html。換句話說url和文件的映射都是由服務器來完成的。但是React項目不同,React項目所有的頁面都是通過React進行渲染構建的。項目中只存在一個index.html沒有那麼多的頁面(所以才叫單頁應用)。當瀏覽器地址發生變化時,比如用戶訪問/about時,此時是不需要服務器介入的,react router會自動掛載對應的組件。當我們將React項目部署到服務器時,如果直接訪問根目錄,請求會直接發送給index.html。這個頁面我們是有的,所以此時不會有任何問題。用戶訪問頁面後,點擊頁面後的連接切換到不同的組件也沒有問題,因爲頁面並沒有真的發生跳轉,而是通過react router在內存中完成了模擬跳轉。但是,當我們刷新某個路由或直接通過瀏覽器地址欄訪問某個路由時,比如:http://localhost:3000/home,此時請求會發送給服務器,服務器會尋找名爲home的資源(此時並沒有經過React)。顯然找不到這個資源,於是返回404。

怎麼辦呢?使用HashRouter,HashRouter通過hash地址跳轉,而服務器不會處理hash地址,這樣地址就會交由React處理,路由便可正常跳轉。缺點是url地址上總會多出一個#,但不妨礙使用。

2. NavLink組件

特殊版本的Link,可以根據不同的情況設置不同的樣式。屬性:

activeClassName —— 字符串 鏈接激活時的class
activeStyle —— 對象 鏈接激活時的樣式
isActive —— 函數,可動態判斷鏈接是否激活
style —— 函數,動態設置樣式
className —— 函數,動態設置class值

3. Prompt組件

prompt組件可以在用戶離開頁面前彈出提示。屬性:

message -- 字符串/函數,設置離開前顯示的提示信息
when --  布爾值,設置是否顯示提示

4. Redirect組件

將請求重定向到一個新的位置,經常用來進行權限的處理。例如:當用戶已經登錄時則正常顯示組件,用戶沒有登錄時則跳轉到登錄頁面。

        <Route
            {...rest}
            render={({ location }) =>
                isLogged ? (
                    children
                ) : (
                    <Redirect
                        to={{
                            pathname: "/login-out",
                            state: { from: location }
                        }}
                    />
                )
            }
        />

上例中,如果isLogged的值爲true,表示用戶已經登錄,若用戶登錄,則掛載對應組件。若isLogged值爲false,則掛載Redirect組件觸發重定向,重定向會使得路徑跳轉到/login-out頁面。屬性:

to —— 重定向的目標地址,可以是一個字符串也可以是一個對象
from —— 需要重定向的地址
push —— 布爾值,是否使用push方式對請求進行重定向

5. 鉤子函數

5.1 useHistory

useHistory鉤子使您可以訪問可用於導航的history實例。

5.2 useLocation

useLocation鉤子返回代表當前 URL 的location對象。你可以把它想象成一個 useState,只要 URL 發生變化,它就會返回一個新的location。

5.3 useParams

useParams返回URL參數的鍵/值對對象。使用它來訪問當前 <Route> 的 match.params。

5.4 useRouteMatch

useRouteMatch 鉤子嘗試以與 <Route> 相同的方式匹配當前 URL。它主要用於在不實際渲染 <Route> 的情況下訪問匹配數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章