react-router v4的入門學習

react-router

本來想給大家教學react-router2.0的版本。但是考慮到4.0的版本已經出現了。本著學新不學舊的原則。今天來帶大家踩坑react-router4.0,react-routerV2,v3。V3相對V2其實沒有什么改變,V2是一種面向切面的編程思想(AOP),而V4是一種萬物皆組件的思想(just component)。V4和V2也大相徑庭。因此學了V2的同學可能要在思想有所轉變。

1.準備

  • 創建項目
//創建項目
create-react-app react4demo
  • 引入react-router
//引入react-router
npm install react-router --save
package.json

項目中的目錄是4.2.0

2.開始

先讓我們看一個小demo。可能開始有些地方不理解,但是之后我會慢慢講解每個用到的知識點。讓我們從整體開始認識react-routerV4

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

const BasicExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr/>

      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
      <Route path="/topics" component={Topics}/>
    </div>
  </Router>
)

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)

const About = () => (
  <div>
    <h2>About</h2>
  </div>
)

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>

    <Route path={`${match.url}/:topicId`} component={Topic}/>
    <Route exact path={match.url} render={() => (
      <h3>Please select a topic.</h3>
    )}/>
  </div>
)

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)


export default BasicExample;

Demo1-截圖

2.1包的選擇

react router v4 是對v3的重寫。現在分為三個包:

  • react-router :只提供核心的路由和函數。一般的應用不會直接使用
  • react-router-dom :供瀏覽器/Web應用使用的API。依賴于react-router, 同時將react-router的API重新暴露(export)出來;
  • react-router-native:供 React Native 應用使用的API。同時將react-router的API重新暴露(export)出來;

因此對于一般項目來說,我們其實只需要引入react-router-dom就好了。如果你項目中存在老版本的v2,v3,需要你先刪除

npm uninstall react-router --save
npm install --save react-router-dom

2.2<Router>

和之前的Router不一樣,這里<Router>組件下只允許存在一個子元素,如存在多個則會報錯。

/*錯誤的實例*/
<Router>
      <ul>
        <li><Link to="/">首頁</Link></li>
        <li><Link to="/about">關于</Link></li>
        <li><Link to="/topics">主題列表</Link></li>
      </ul>

      <hr/>

      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
      <Route path="/topics" component={Topics}/>
 </Router>

同上面的例子不同的是,沒有了div的庇護,這里就會報錯。

image.png

2.3<Route>

Route組件主要的作用就是當一個location匹配路由的path時,渲染某些UI。示例如下:


<Router>
  <div>
    <Route exact path="/" component={Home}/>
    <Route path="/ttt" component={Topic}/>
  </div>
</Router>

// 如果應用的地址是/,那么相應的UI會類似這個樣子:
<div>
  <Home/>
</div>

// 如果應用的地址是/ttt,那么相應的UI就會成為這個樣子:
<div>
  <Topic/>
</div>

Route屬性

path(string): 路由匹配路徑。(沒有path屬性的Route 總是會 匹配);
exact(bool):為true時,則要求路徑與location.pathname必須完全匹配;
strict(bool):true的時候,有結尾斜線的路徑只能匹配有斜線的location.pathname;

同時,新版的路由為<Route>提供了三種渲染內容的方法:

  • <Route component>:在地址匹配的時候React的組件才會被渲染,route props也會隨著一起被渲染;
  • <Route render>:這種方式對于內聯渲染和包裝組件卻不引起意料之外的重新掛載特別方便;
  • <Route children>:與render屬性的工作方式基本一樣,除了它是不管地址匹配與否都會被調用;

上面的例子是講的<Route component>,現在我們來說<Route render>


//就在源代碼中渲染。

<Route path="/home" render={() => <div>Home</div>}/>


// 包裝/合成
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    <FadeIn>
      <Component {...props}/>
    </FadeIn>
  )}/>
)

<FadingRoute path="/cool" component={Something}/>

<Route component>的優先級要比<Route render>高,所以不要在同一個<Route>中同時使用這兩個屬性。

2.4<Link>

也就是我們的跳轉屬性啦。現在我們看看Link的屬性

  • to(string / object):要跳轉的路徑或地址;
  • replace :為 true 時,點擊鏈接后將使用新地址替換掉訪問歷史記錄里面的原地址;為 false 時,點擊鏈接后將在原有訪問歷史記錄的基礎上添加一個新的紀錄。默認為 false;
/*這里一些關于Link的例子*/


//當to為string類型的時候
<Link to="/about">關于</Link>

//to為obj
<Link to={{
  pathname: '/courses',
  search: '?sort=name',
  hash: '#the-hash',
  state: { fromDashboard: true }
}}/>

// replace 
<Link to="/courses" replace />

2.5<NavLink>

<NavLink>是<Link>的一個特定版本,會在匹配上當前URL的時候會給已經渲染的元素添加樣式參數。

我們自己如果手寫一個NavLink,應該可以這樣去完成:只是封裝這層<NavLink>花了更多的心思去完成他的樣式封裝和功能封裝。


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

export default React.createClass({
  render() {
    return <Link {...this.props} activeClassName="active"/>
  }
})

//index.css

.active {
  color: green;
}

讓我們來看下<NavLink>有什么屬性吧

  • activeClassName(string):設置選中樣式,默認值為 active;
  • activeStyle(object):當元素被選中時, 為此元素添加樣式;
  • exact(bool):為 true 時, 只有當地址完全匹配 class 和 style 才會應用;
  • strict(bool):為 true 時,在確定位置是否與當前 URL 匹配時,將考慮位置 pathname 后的斜線;
    isActive(func):判斷鏈接是否激活的額外邏輯的功能;

來看幾個簡單的demo

// activeClassName選中時樣式為selected
<NavLink
  to="/faq"
  activeClassName="selected"
 >FAQs</NavLink>

// 選中時樣式為activeStyle的樣式設置
<NavLink
  to="/faq"
  activeStyle={{
    fontWeight: 'bold',
    color: 'red'
   }}
 >FAQs</NavLink>
 
 
// 當event id為奇數的時候,激活鏈接
const oddEvent = (match, location) => {
  if (!match) {
    return false
  }
  const eventID = parseInt(match.params.eventID)
  return !isNaN(eventID) && eventID % 2 === 1
}

<NavLink
  to="/events/123"
  isActive={oddEvent}
>Event 123</NavLink>



2.6<Switch>

該組件用來渲染匹配地址的第一個<Route>或者<Redirect>。那么它與使用一堆route又有什么區別呢?

<Switch>的獨特之處是獨它僅僅渲染一個路由。相反地,每一個包含匹配地址(location)的<Route>都會被渲染。思考下面的代碼:

<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>

如果現在的URL是/about,那么<About>, <User>, 還有<NoMatch>都會被渲染,因為它們都與路徑(path)匹配。這種設計,允許我們以多種方式將多個<Route>組合到我們的應用程序中,例如側欄(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶爾我們只想選擇一個<Route>來渲染。如果我們現在處于/about,我們也不希望匹配/:user(或者顯示我們的 “404” 頁面 )。以下是使用 Switch 的方法來實現

<Switch>
  <Route exact path="/" component={Home}/>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={NoMatch}/>
</Switch>

現在,如果我們處于/about,<Switch>將開始尋找匹配的<Route>。<Route path="/about"/> 將被匹配, <Switch>將停止尋找匹配并渲染<About>。同樣,如果我們處于/michael,<User>將被渲染

以上是react-router的基礎。


3.URL參數

同樣的,我們先來看一個demo

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

const ParamsExample = () => (
  <Router>
    <div>
      <h2>Accounts</h2>
      <ul>
        <li><Link to="/netflix">Netflix</Link></li>
        <li><Link to="/zillow-group">Zillow Group</Link></li>
        <li><Link to="/yahoo">Yahoo</Link></li>
        <li><Link to="/modus-create">Modus Create</Link></li>
      </ul>

      <Route path="/:id" component={Child}/>
    </div>
  </Router>
)

const Child = ({ match }) => (
  <div>
    <h3>ID: {match.params.id}</h3>
  </div>
)

export default ParamsExample

4.重定向

<Redirect>
組件用于路由的跳轉,即用戶訪問一個路由,會自動跳轉到另一個路由。

這里原本我們需要訪問protected,由于沒登錄被跳轉登錄到login頁面。

看效果圖:

image.png

這是一個需要你登錄才能查看隱私內容。那么如何去實現呢?


  const fakeAuth = {
    isAuthenticated: false,
    authenticate(cb) {
      this.isAuthenticated = true
      setTimeout(cb, 100) // fake async
    },
    signout(cb) {
      this.isAuthenticated = false
      setTimeout(cb, 100)
    }
  } 
  
  
  const PrivateRoute = ({ component: Component, ...rest }) => (
    <Route {...rest} render={props => (
      fakeAuth.isAuthenticated ? (
        <Component {...props}/>
      ) : (
        <Redirect to={{
          pathname: '/login',
          state: { from: props.location }
        }}/>
      )
    )}/>
  )
  
  const AuthButton = withRouter(({ history }) => (
    fakeAuth.isAuthenticated ? (
      <p>
        Welcome! <button onClick={() => {
          fakeAuth.signout(() => history.push('/'))
        }}>Sign out</button>
      </p>
    ) : (
      <p>You are not logged in.</p>
    )
  ))

代碼中出現了withRouter這個又是什么呢?

首先withRouter是一個組件,withRouter可以包裝任何自定義組件,將react-router 的 history,location,match 三個對象傳入。 無需一級級傳遞react-router 的屬性,當需要用的router 屬性的時候,將組件包一層withRouter,就可以拿到需要的路由信息

ok,得到了history(統一的API管理歷史堆棧、導航、確認跳轉、以及sessions間的持續狀態)。在v3的時候,我們想跳轉路徑,一般會這樣處理。

  1. 我們從react-router導出browserHistory。
  2. 我們使用browserHistory.push()等等方法操作路由跳轉。

例如:

import browserHistory from 'react-router';

export function addProduct(props) {
  return dispatch =>
    axios.post(`xxx`, props, config)
      .then(response => {
        browserHistory.push('/cart'); //這里
      });
}

在v4我們直接操作history來進行路由棧的管理。history.push('/'))默認調到該路由的主頁

ok,現在我們來看Login里的代碼

class Login extends React.Component {
    state = {
      redirectToReferrer: false
    }
  
    login = () => {
      fakeAuth.authenticate(() => {
        this.setState({ redirectToReferrer: true })
      })
    }
  
    render() {
      const { from } = this.props.location.state || { from: { pathname: '/' } }
      const { redirectToReferrer } = this.state

      if (redirectToReferrer) {
        return (
          <Redirect to={from}/>
        )
      }
      
      return (
        <div>
          <p>You must log in to view the page at {from.pathname}</p>
          <button onClick={this.login}>Log in</button>
        </div>
      )
    }
  }

這里肯定有人會有疑問,這里的from 是什么?打印出來是什么?

回答這個問題,我們先來看一下location

首先location是一個Object。當前訪問地址信息組成的對象,具有如下屬性:

  • pathname: string URL路徑
  • search: string URL中的查詢字符串
  • hash: string URL的 hash 片段
  • state: string 例如執行 push(path, state) 操作時,location 的 state 將被提供到堆棧信息里。

打印出來剛剛代碼里的location

location

這里的from.pathname是 /protected 意思是什么?其實本應該跳轉到 ‘/protected’,但是redirect 因為你沒登錄攔截下來了。因此我們可以通過 this.props.location.state.from 來查看是否跳轉到成功的頁面。講到這里相信大家對重定向有了自己的理解。手敲一遍代碼是最方便也是最好的理解。

5.自定義鏈接

自定義截圖

自定義鏈接的思路,其實就是對<Route><Link> 進行封裝一層。

  const OldSchoolMenuLink = ({ label, to, activeOnlyWhenExact }) => (
    <Route path={to} exact={activeOnlyWhenExact} children={({ match }) => (
      <div className={match ? 'active' : ''}>
        {match ? '> ' : ''}<Link to={to}>{label}</Link>
      </div>
    )}/>
  )
  

要想理解這段代碼,我們先了解match

match 對象包含了 <Route path> 如何與 URL 匹配的信息,具有以下屬性:

  • params: object 路徑參數,通過解析 URL 中的動態部分獲得鍵值對
  • isExact: bool 為 true 時,整個 URL 都需要匹配
  • path: string 用來匹配的路徑模式,用于創建嵌套的 <Route>
  • url: string URL 匹配的部分,用于嵌套的 <Link>

在以下情境中可以獲取 match 對象

  • 在 Route component 中,以 this.props.match獲取
  • 在 Route render 中,以 ({match}) => () 方式獲取
  • 在 Route children 中,以 ({match}) => () 方式獲取
  • 在 withRouter 中,以 this.props.match的方式獲取
  • matchPath 的返回值
打印match內容

當該鏈接被點擊的時候,match就有了值,未被點擊的就是null。這時候可以根據match是否存在來進行判斷。當有的時候,className為active,并且,在Link之前加上 > 符號

理解了match對于這里的理解就會顯得方便了多。在項目中我們可以根據自己的需求封裝不同樣式不同外觀的造型。

6.防止轉換

首先我們來看一下這里的效果圖


QQ20180131-162002.gif

代碼

        <Prompt
          when={isBlocking}
          message={history => (
            `Are you sure you want to go to ${history.pathname}`
          )}
        />
        
        //這里history 同樣可以 使用location.pathname 來獲得

其實這里的關鍵就 一個組件 Prompt ,我們來看一下它的屬性:

  • message :提示用戶的一種方式。(可以得到 history ,match ,location)
  • when:作為一種觸發的方式 bool 類型

7.404 No match

之前上面是switch講的有點干癟,現在配合著具體的代碼來說

const NoMatchExample = () => (
    <Router>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/old-match">Old Match, to be redirected</Link></li>
          <li><Link to="/will-match">Will Match</Link></li>
          <li><Link to="/will-not-match">Will Not Match</Link></li>
          <li><Link to="/also/will/not/match">Also Will Not Match</Link></li>
        </ul>
        <Switch>
          <Route path="/" exact component={Home}/>
          <Redirect from="/old-match" to="/will-match"/>
          <Route path="/will-match" component={WillMatch}/>
          <Route component={NoMatch}/>
        </Switch>
      </div>
    </Router>
  )
  
  const Home = () => (
    <p>
      A <code>&lt;Switch></code> renders the
      first child <code>&lt;Route></code> that
      matches. A <code>&lt;Route></code> with
      no <code>path</code> always matches.
    </p>
  )
  
  const WillMatch = () => <h3>Matched!</h3>
  
  const NoMatch = ({ location }) => (
    <div>
      <h3>No match for <code>{location.pathname}</code></h3>
    </div>
  )
  
  export default NoMatchExample

我們把switch 理解為 代碼中的 switch case 有匹配的則跳轉。<Route component={NoMatch}/> 放在最后的意義是,如果上訴都沒有匹配的,那么就跳轉到404頁面。

8.遞歸路徑

通常來說,遞歸路徑適用于項目中需要用到分級的地方,比如,一級目錄,二級目錄,三級目錄這樣子。我們先來看一下項目的效果圖。

遞歸路徑.gif

接下來,我們來解析源碼。

const PEEPS = [
    { id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
    { id: 1, name: 'Sean', friends: [ 0, 3 ] },
    { id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
    { id: 3, name: 'David', friends: [ 1, 2 ] }
  ]
  
  const find = (id) => PEEPS.find(p => p.id == id)
  
  const RecursiveExample = () => (
    <Router>
      <Person match={{ params: { id: 0 }, url: '' }}/>
    </Router>
  )
  
  const Person = ({ match }) => {
    
    //console.log(match);
    const person = find(match.params.id)
    console.log(person);
    return (
      <div>
        <h3>{person.name}’s Friends</h3>
        <ul>
          {person.friends.map(id => (
            <li key={id}>
              <Link to={`${match.url}/${id}`}>
                {find(id).name}
              </Link>
            </li>
          ))}
        </ul>
        <Route path={`${match.url}/:id`} component={Person}/>
      </div>
    )
  }

find 是ES6中的方法。用于找到第一個符合條件的數組成員并返回。

每次點擊的時候,我們會傳遞id過去。因此可以使用“match” 查看我們的params 中的參數屬性。通過id 再繼而判斷 找出是 find 的數據。

這里的知識點是告訴我們如何進行遞歸路徑的排序。

9.邊欄

邊欄的代碼相對較簡單,這里我就不多過述

10.動畫轉化

首先來看效果圖


QQ20180201-101027.gif

這里需要我們在項目導入

npm install react-transition-group --save

我們用到了CSSTransitionGroup 這個組件。它有這么些個屬性

transitionName="fade"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}

更多的大家可以上github上查看 具體的文檔。

 <div style={styles.content}>
            <CSSTransitionGroup
              transitionName="fade"
              transitionEnterTimeout={300}
              transitionLeaveTimeout={300}
            >
              {/* no different than other usage of
                  CSSTransitionGroup, just make
                  sure to pass `location` to `Route`
                  so it can match the old location
                  as it animates out
              */}
              <Route
                location={location}
                key={location.key}
                path="/:h/:s/:l"
                component={HSL}
              />
            </CSSTransitionGroup>
          </div>

11.不明確匹配

知識點也同switch,這里就不過多述

12.路由配置

官方文檔里寫的路由配置,可以實際配置到項目中的。它把我們所有的路由情況加載到一個數組中,通過數組去配置整個路由。

const routes = [
  { path: '/sandwiches',
    component: Sandwiches
  },
  { path: '/tacos',
    component: Tacos,
    routes: [
      { path: '/tacos/bus',
        component: Bus
      },
      { path: '/tacos/cart',
        component: Cart
      }
    ]
  }
]

如果有多個路由,routes繼續遞增。我們可以看到routes[1]的這個路由對象,它的路由一級目錄是'/tacos' 在‘/tacos‘下還有二級目錄。分別是'/tacos/bus'和'/tacos/cart'。因此每次我們需要增加路由的時候,只需要在routes這個數組中完成配置,并不需要添加額外的組件,同時也減少了代碼的耦合,增加了可讀性,這是非常關鍵的一點。

const RouteWithSubRoutes = (route) => 
{   
    console.log(route);
    return(
  <Route path={route.path} render={props => (
    // pass the sub-routes down to keep nesting
    <route.component {...props} routes={route.routes}/>
  )}/>
)}

上面這個是對路由的一次封裝。

const RouteConfigExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/tacos">Tacos</Link></li>
        <li><Link to="/sandwiches">Sandwiches</Link></li>
      </ul>

      {routes.map((route, i) => (
        <RouteWithSubRoutes key={i} {...route}/>
      ))}
    </div>
  </Router>
)

使用的時候,我們只需要對數組進行一次map遍歷。按照上面封裝的方法依次加載所需展示的路由。

與此同時我也在思考一個問題?

我們使用標簽的方式去展示路由,自然是沒有問題的,但是?假設項目很大。我們使用webpack對項目進行打包,webpack是把所有的文件都打包在一個main.***.js的文件中,那么加載首頁的時候的,就需要花費大量的的時間去加載這么大的文件,豈不是很耗時間?

如何改善?

按需加載。。也可以叫延遲加載。 這里有一份簡書的文檔可以參考(作者:zhangpei),里面的內容值得深入琢磨,在這里不做討論,有興趣的可以研究研究。

demo地址-github

參考

作者:阮一峰
鏈接:http://www.ruanyifeng.com/blog/2016/05/react_router.html?utm_source=tool.lu
react-router 官網
鏈接 :https://reacttraining.com/react-router/web/example/url-params

作者:桂圓_noble
鏈接:http://www.lxweimin.com/p/6a45e2dfc9d9
來源:簡書
參考博文鏈接:http://blog.csdn.net/sinat_17775997/article/details/69218382
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容