React Router 4.0 ---- 嵌套路由和動態路由

  嵌套路由,從廣義上來講,分爲兩種狀況:一種是每一個路由到的組件都有共有的內容,這時把共有的內容抽離成一個組件,變化的內容也是一個組件,兩種組件組合嵌套,造成一個新的組件。另外一種是子路由,路由到的組件內部還有路由。css

  對於共有的內容,典型的表明就是網頁的側邊欄,假設側邊欄在左邊,咱們點擊其中的按鈕時,右側的內容會變化,但無論右側的內容怎麼變化,左側的側邊欄始終存在。這個側邊欄就是共有內容,以下圖所示html

  這個共有內容要怎麼處理? 首先想到的就是把這個功能提取出來,寫成一個組件,而後再把這個組件依次應用到其它路由組件,如about, products 等。導航欄確定是一個導航,不過這裏咱們要使用navLink 組件,由於從圖片中能夠看到它有高亮顯示,咱們要設計一個高亮顯示的樣式。新建一個menus.js 文件,來寫這個導航組件react

import React from 'react'
// 引入NavLink 組件
import { NavLink } from "react-router-dom";
import './menus.css'

// 高亮的樣式,表示咱們在哪一個導航下
const selectedStyle = {
  backgroundColor: 'white',
  color: 'slategray'
}

// navLink, activeStyle 點擊高亮顯示當前標籤。
export const MainMenu = () => (
  <nav className='main-menu'>
    <NavLink to='/'>首頁</NavLink>
    <NavLink to='/about' activeStyle = {selectedStyle}>關於咱們</NavLink>
    <NavLink to='/events' activeStyle = {selectedStyle}>企業事件</NavLink>
    <NavLink to='/products' activeStyle = {selectedStyle}>公司產品</NavLink>
    <NavLink to='/contact' activeStyle = {selectedStyle}>聯繫咱們</NavLink>
  </nav>
)

  能夠看到導航的高亮樣式能夠在NavLink 組件中直接設置,這也是Link 和NavLink的區別。這裏還寫了一點樣式,在menus.css 文件中。瀏覽器

/* 頁面左側主要導航 */
.main-menu {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 20%;
  min-width: 20%;
  background-color: slategray;
  color: ghostwhite;
}

.main-menu  a {
  color: ghostwhite;
  text-align: center;
  padding: 1em;
  font-size: 1.5em;
}

  按照上面的說法,咱們把這個組件應用到其它組件中,在pages.js中引入該組件,並修改Events,Products, react-router

Contact和 About 組件中,引入的組件命名爲MainMenu
// 企業事件內容
export const Events = () => (
    <div>
      <MainMenu></MainMenu>
      <section className="events">
        <h1>企業大事件</h1>
      </section>
    </div>
)

// 公司產品 
export const Products = () => (
  <div>
    <MainMenu></MainMenu>
    <section className="products">
      <h1>公司產品:主要經營 手機、電腦</h1>
    </section>
  </div>
)

// 聯繫咱們
export const Contact = () => (
  <div>
    <MainMenu></MainMenu>
    <section className="contact">
      <h1>聯繫咱們</h1>
      <p>公司電話:0755 - 12345678</p>
    </section>
  </div>
)

  這時你會發現,相同的代碼複製了4遍,還能夠接受,畢竟只有一個共有組件。但若是about, home 這些組件有好多共有的部分,咱們這樣一遍一遍的複製就有點麻煩了。因此還要對page.js文件這些組件相同的內容進行進一步的抽取。抽取的形式應該是app

<div>
    <MainMenu></MainMenu>
    // 變化的部分
  </div>

  如今最主要的部分就是這些變化的部分要怎麼處理,想到了其實也很簡單,由於React 有一個children 屬性, 直接把這些變化的部分寫成props.childern 就能夠了。這時你會發現,這個抽取的組件像一個佈局模板, 好比單頁面應用時的頁眉和頁腳這些共有的部分,也能夠放到這個模版中。新建一個template.js 文件dom

import React from 'react'
import { MainMenu } from "./menus";

export const Template = (props) => (
  <div className = 'page'>
    <MainMenu></MainMenu>
    {props.children}
  </div>
)

  上面的代碼中加了一個樣式類page, 在pages.css 中添加一個 page樣式,函數

.page {
  display: flex;
  justify-content: space-between;
  height: 100%;
  margin-top: 20px;
}

  如今再在pages.js中的相應組件中應用Template組件oop

import { Template } from "./template";// 企業事件內容
export const Events = () => (
  <Template>
    <section className="events">
      <h1>企業大事件</h1>
    </section>
  </Template>

)

// 公司產品 
export const Products = () => (
  <Template>
    <section className="products">
      <h1>公司產品:主要經營 手機、電腦</h1>
    </section>
  </Template>
)

// 聯繫咱們
export const Contact = () => (
  <Template>
    <section className="contact">
      <h1>聯繫咱們</h1>
      <p>公司電話:0755 - 12345678</p>
    </section>
  </Template>
)

  這時效果就達到了,左側的側邊欄始終存在。如今 咱們再來實現一會兒路由。佈局

  子路由: 就是當路由匹配成功後,它會渲染出一個組件。這個組件中還有路由,這個路由就是子路由。好比: 我進入到about頁面,about 組件渲染出來,而about 組件中,還有history, service 要展現,它還須要路由,about 組件中的路由就是子路由,以下圖所示

  about組件上面的導航條,咱們仍是使用NavLink實現,導航條下面的內容確定是路由系統。對於這裏的路由來講,咱們要注意的就是它的path屬性,由於它是子路由,咱們首先要進入到父組件about下面,才能顯示這個路由系統,因此它前面的路徑都要加about, 匹配公司服務, 它的path 確定是’/about/service’; 其它三個對應的則是 about/company,  about/location, about/history, 但若是四個路由都這麼匹配,就會出現一個問題,首次進入到about 組件時,它什麼都不會顯示。就是說,咱們點擊左側的側邊欄中的 ‘關於咱們’,它不會顯示任何內容。這裏能夠這麼處理,點擊 ‘關於咱們’,它確定會顯示四個子路由中的 一個,好比顯示公司簡介, 咱們就能夠把公司簡介的路由直接設置成about, 對於子路由來講,父路由進來顯示的默認頁面,就能夠設置和父路由同樣。

  導航條,咱們寫到menus.js 文件裏面,命名爲AboutMenu

export const AboutMenu = () => (
  <ul className="about-menu">
    <li>
      <NavLink to='/about' exact activeStyle ={selectedStyle}>公司簡介</NavLink>
    </li>
    <li>
      <NavLink to='/about/history' activeStyle ={selectedStyle}>公司歷史</NavLink>
    </li>
    <li>
      <NavLink to='/about/services' activeStyle ={selectedStyle}>公司服務</NavLink>
    </li>
    <li>
      <NavLink to='/about/location' activeStyle ={selectedStyle}>企業位置</NavLink>
    </li>
  </ul>
)

  咱們再在pages.js中修改about組件的內容,使其包含路由組件

export const About = () => (
  <Template>
    <section className="about">
      <AboutMenu></AboutMenu>
      <Route path='/about' exact component={Company}/>
      <Route path='/about/history' component={History}/>
      <Route path='/about/services' component={Services}/>
      <Route path='/about/location' component={Location}/>
    </section>
  </Template>
)

  同時再簡單地定義Company, History 等4個顯示組件。

export const Services = () => (
  <section>
    <p>公司服務</p>
  </section>
)

export const Location  = () => (
  <section>
    <p>公司位置</p>
  </section>
)

export const Company = () => (
  <section>
    <p>公司簡介</p>
  </section>
)

export const History = () => (
  <section>
    <p>公司歷史</p>
  </section>
)

  這時就實現了子路由的功能。

  動態路由:就是在匹配路徑path 的後面加上冒號 + 參數, 如path ="products/:id". 好比咱們進入產品頁面,它有手機,電腦等產品,咱們點擊單個產品,確定進入到產品詳情頁面,咱們爲每個產品都建一個產品詳情組件,有點不太現實,由於產品太多,寫的大累,再說,產品種類是動態增長的,增長一個產品,就增長一個組件,維護起來也太累。最好的辦法就是,它們都跳轉到一個詳情組件,根據不一樣的產品渲染動態渲染不一樣的內容, 那麼產品詳情組件就要接受一個參數表示產品類型,這個類型是動態的,因此要用一個變量,路由的格式就是details/:type, 那組件內部怎麼獲得這個參數呢,咱們以前說過,當渲染組件時,路由會給咱們組件注入3個參數,這裏使用match 就能夠了,它有一個params屬性,就是專門獲取動態路由參數的。

  咱們在products組件內部,寫兩個link, 用於導航到產品詳情,同時寫一個產品詳情頁組件。

// 公司產品 
export const Products = () => (
  <Template>
    <section className="products">
        <Link to='/details/telphone'>手機</Link>
        &nbsp;
        <Link to='/details/computer'>電腦</Link> 
    </section>
  </Template>
)

// 產品詳情組件
export const Details = (props) => {
  console.log(props.match.params);
  return <p>這是 {props.match.params.type}詳情內容</p>
}

  但產品詳情的路由的定義要放到什麼地方呢?因爲產品詳情要佔據整個頁面,它和 Events, Products 組件是一個級別的,因此要放到App組件 中, 在app.js文件中路由下面增長一條路由,<Route path='/details/:type' component={Details}></Route>     

<Switch>
          <Route path='/' exact component={Home}/>
          <Route path='/about' component={About}/>
          <Route path='/contact' component={Contact}/>
          <Route path='/products' component={Products}/>
          <Route path='/events' component={Events}/>   
          <Route path='/details/:type' component={Details}></Route>     
          <Route component={NotFound404}/>     
        </Switch>    

  如今能夠實現動態路由了。

  最後一點是重定向路由組件<Redirect>, 它最基本使用就是一個to 屬性,和link的to 屬性一個意思,到什麼地方去。<Redirect to=’/about’>, 就是重定向到about組件。當在Switch 組件下面,它還有一個from屬性,它的接受的參數和to 同樣,是一個路徑,表示從哪裏來。在這裏,假設用戶在瀏覽器地址欄中輸入history, 咱們把它重定向about/history, 就能夠這麼寫

<Redirect from='/history' to='about/history'></Redirect>

  它還有一種使用場景就是,已有路徑的重定向,好比,當用戶訪問首頁的時候,把它重定向到產品頁面。這裏要知道的是,Route 組件的除了接受component,還能夠接受一個render 函數,render函數渲染出一個組件。

<Route path='/' exact render={() => <Redirect to='/products' />} />

  整個項目內容以下,就是在src目錄下新建了 menus.js, pages.js, template.js,pages.css, menus.css 文件,修改了App.js 文件

   App.js 

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

// 引入展現組件
import { About, Contact, Home, Products, Events, NotFound404, Details } from './pages';

function App() {
  return (
    <HashRouter>
      <div>
        <Switch>
          <Route path='/' exact component={Home}/>
          <Route path='/about' component={About}/>
          <Route path='/contact' component={Contact}/>
          <Route path='/products' component={Products}/>
          <Route path='/events' component={Events}/>  
          <Redirect from='/history' to='about/history'></Redirect>
          <Route path='/details/:type' component={Details}></Route>     
          <Route component={NotFound404}/>     
        </Switch>     
      </div>
    </HashRouter>
  )
}

export default App

  menus.js

import React from 'react'
// 引入NavLink 組件
import { NavLink } from "react-router-dom";
import './menus.css'

// 高亮的樣式,表示咱們在哪一個導航下
const selectedStyle = {
  backgroundColor: 'white',
  color: 'slategray'
}

// navLink, activeStyle 點擊高亮顯示當前標籤。
export const MainMenu = () => (
  <nav className='main-menu'>
    <NavLink to='/'>首頁</NavLink>
    <NavLink to='/about' activeStyle = {selectedStyle}>關於咱們</NavLink>
    <NavLink to='/events' activeStyle = {selectedStyle}>企業事件</NavLink>
    <NavLink to='/products' activeStyle = {selectedStyle}>公司產品</NavLink>
    <NavLink to='/contact' activeStyle = {selectedStyle}>聯繫咱們</NavLink>
  </nav>
)

export const AboutMenu = () => (
  <ul className="about-menu">
    <li>
      <NavLink to='/about' exact activeStyle ={selectedStyle}>公司簡介</NavLink>
    </li>
    <li>
      <NavLink to='/about/history' activeStyle ={selectedStyle}>公司歷史</NavLink>
    </li>
    <li>
      <NavLink to='/about/services' activeStyle ={selectedStyle}>公司服務</NavLink>
    </li>
    <li>
      <NavLink to='/about/location' activeStyle ={selectedStyle}>企業位置</NavLink>
    </li>
  </ul>
)

  pages.js

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

import './pages.css';
import { Template } from "./template";
import { AboutMenu } from "./menus";
// 首頁內容
export const Home = () => (
  <section className="home">
    <h1>企業網站</h1>
    <nav>
      {/* 添加了四個導航組件Link */}
      <Link to='/about'>關於咱們</Link>
      <Link to='/events'>企業事件</Link>
      <Link to='/products'>公司產品</Link>
      <Link to='/contact'>聯繫咱們</Link>
    </nav>
  </section>
)

// 企業事件內容
export const Events = () => (
  <Template>
    <section className="events">
      <h1>企業大事件</h1>
    </section>
  </Template>

)

// 公司產品 
export const Products = () => (
  <Template>
    <section className="products">
        <Link to='/details/telphone'>手機</Link>
        &nbsp;
        <Link to='/details/computer'>電腦</Link> 
    </section>
  </Template>
)

// 產品詳情組件
export const Details = (props) => {
  console.log(props.match.params);
  return <p>這是 {props.match.params.type}詳情內容</p>
}

// 聯繫咱們
export const Contact = () => (
  <Template>
    <section className="contact">
      <h1>聯繫咱們</h1>
      <p>公司電話:0755 - 12345678</p>
    </section>
  </Template>
)

// 關於咱們
export const About = () => (
  <Template>
    <section className="about">
      <AboutMenu></AboutMenu>
      <Route path='/about' exact component={Company}/>
      <Route path='/about/history' component={History}/>
      <Route path='/about/services' component={Services}/>
      <Route path='/about/location' component={Location}/>
    </section>
  </Template>
)
// 沒有匹配成功的404組件
export const NotFound404 = (props) => (
  <div className="whoops-404">
    <h1>沒有頁面能夠匹配</h1>
  </div>
)

// 4個子路由對應的顯示組件
const Services = () => (
  <section>
    <p>公司服務</p>
  </section>
)

const Location  = () => (
  <section>
    <p>公司位置</p>
  </section>
)

const Company = () => (
  <section>
    <p>公司簡介</p>
  </section>
)

const History = () => (
  <section>
    <p>公司歷史</p>
  </section>
)

  template.js

import React from 'react'
import { MainMenu } from "./menus";

export const Template = (props) => (
  <div className = 'page'>
    <MainMenu></MainMenu>
    {props.children}
  </div>
)

  menus.css

/* 頁面左側主要導航 */
.main-menu {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 20%;
  min-width: 20%;
  background-color: slategray;
  color: ghostwhite;
}

.main-menu  a {
  color: ghostwhite;
  text-align: center;
  padding: 1em;
  font-size: 1.5em;
}

.about-menu {
  width: 100%;
  margin: 0;
  padding: 0;
  list-style-type: none;
  display: flex;
  background-color: slategray;
}

.about-menu > li {
  flex-grow: 1;
}

.about-menu > li > a {
  display: block;
  width: calc(100% - 1em);
  text-align: center;
  text-decoration: none;
  color: ghostwhite;
  padding: 0.5em;
}

  pages.css

html, body, #root {
  height: 100%;
}
h1 {
  font-size: 3em;
  color: slategray;
}
/* home 組件 */
.home {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.home > nav {
  display: flex;
  justify-content: space-around;
  padding: 1em;
  width: calc(100% - 2em);
  border-top: dashed 0.5em ghostwhite;
  border-bottom: dashed 0.5em ghostwhite;
  background-color: slategray;
}

.home > nav a {
  font-size: 2em;
  color: ghostwhite;
  flex-basis: 200px;
}

/* 其它組件 */
section.events,
section.products,
section.contact {
  flex-grow: 1;
  margin: 1em;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* 404頁面 */
.whoops-404 {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99;
  display: flex;
  width: 100%;
  height: 100%;
  margin: 0;
  justify-content: center;
  align-items: center;
  background-color: darkred;
  color: ghostwhite;
  font-size: 1.5em;
}

.page {
  display: flex;
  justify-content: space-between;
  height: 100%;
  padding-top: 20px;
  background: #f3f7ff;
}
/* about 組件 */
.about {
  flex-grow: 1;
  margin: 1em;
  display: flex;
  flex-direction: column;
}
.about > section {
  text-align: center;
}
相關文章
相關標籤/搜索