react路由

weixin_34146805發表於2018-08-01

react-router-dom


最近開始學習reactr-router,但是看了好多文章都有問題,後來才發現是react-router 4做了很大的改動,遵循 Just Component 的 API 設計理念。reactr-router被拆分為reactr-routerreactr-router-domreactr-router-native三部分。reactr-router提供核心的API,其餘兩個則提供兩個特定的執行環境(瀏覽器和react-native)所需的元件。因此所有的路由元件匯入都改為了從react-router-dom匯入。

安裝

注意如果是用create-react-app腳手架的話,注意安裝方式(yarn或者npm),詳情參考另外一篇react腳手架搭建

npm install --save react-router-dom;
npm install --save react-router-redux;
npm install --save react-router-config;

使用

先看一下基本寫法:

一級路由,做正常的路由切換
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
class App extends Component {
  render() {
    return (
      <Router basename='/route'>
        <div>
          <Route path="/" exact component={Home}></Route>
          <Route path="/mime" component={Mime}></Route>
          <Route path="/list" component={List}></Route>
          <ul>
            <li>
                <Link to="/">home</Link>
            </li>
            <li>
                <Link to="/mime">mime</Link>
            </li>
            <li>
                <Link to="/list">list</Link>
            </li>
          </ul>
        </div>
      </Router>
    );
  }
}
二級路由

在一級路由的基礎上再做路由,通過props中的match屬性可以獲取當前的url(),然後再去拼接二級路由的path。

export default class List extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>列表頁</h1>
        <ul>
          <li>
            <Link to={`${this.props.match.url}/details`}>詳情</Link>
          </li>
          <li>
            <Link to={`${this.props.match.url}/edit`}>編輯</Link>
          </li>
        </ul>
        <Route path={`${this.props.match.url}/details`} component={Details} />
        <Route path={`${this.props.match.url}/edit`} component={Edit} />
      </div>
    );
  }
}
帶引數

如果路由後面要攜帶引數,可以這樣寫(最後括號中的值表示只能是這兩個值,如果沒有此限制則可以是任何值)

<Route
    path={`${this.props.match.url}/:id(20|100)`}
    component={Details}
/>

在子元件中通過this.props.match.params.xx獲取對應的引數

<h1>{this.props.match.params.id}</h1>
<BrowserRouter>和<HashRouter>

他們就是路由的根基,包裹在最外層。之前使用路由的肯定知道(不管是哪個版本,甚至vue的路由),路由的實現有兩種方法,HashRouter是利用URL的hash部分實現,在url中會經常有個#號,主要是針對舊版瀏覽器。我們一般都使用BrowserRouter,它是利用H5的history API實現。

還有一種實現是針對非DOM環境的(react-native),利用記憶體實現。

  • basename:如果你的檔案在伺服器的二級目錄下,就會用到它。他會在每一個路由路徑前面自動新增一級目錄。如下圖,當我們點選list時,url中會自動新增route目錄。
3473978-9340c6ca79de3424.png
basename.png

該元件下只能有一個子元件,所有元件都要包裹在一層裡。

<Route>

Route就是控制不同路徑去對應顯示內容的元件。配合Link使用。

  • exact:控制嚴格匹配。如果沒有這個屬性,當訪問list時,home元件和list元件會同時被渲染。

  • path:訪問的路徑,如果沒有該屬性,那麼將會匹配一切路徑。

  • component:path路徑對應要顯示的元件。使用最多。如果要獲取match等物件。可以使用this.props.match...

  • render:與component一樣,可以直接在後面寫要渲染的內容。可以獲取到mach物件。

<Route path="/home" render={({match}) => <div>Home</div>}/>
  • children:和render是一樣的功能,不過它不論是否匹配到都會呼叫。
<Route path="/mime" component={Mime}></Route>
<Route path="/list" children={({match})=> (
    <div>
        <h3>哈哈</h3>
        <List></List>
    </div>
)}></Route>

以上程式碼,不管當前匹配的是mime還是list或是其他,都會渲染list。

<Link>和<NavLink>

Link的api相對較少,上面我們使用的是最簡單的to,直接跳轉到一個地址。就不多說了。

  • to:還可以攜帶引數到指定頁面,後面跟一個物件就可以。如下(可以實現頁面間傳參)
<Link to={{
  pathname: '/list',
  search: '?sort=name',
  state: { id: 2 }
}} />

state可以傳遞任何型別的引數,而且類似於post方式,可以不以明文傳輸。在跳轉的頁面中實用this.props.location.state獲取資料。

  • replace:基本不用,為true時會替換掉上次訪問的地址,也就是使用瀏覽器的回退按鈕無法返回上一個頁面。

NavLink很明顯就是為頁面導航準備的,因為導航是要有啟用狀態的。

  • activeClassName:啟用時要渲染的樣式名
<NavLink to="/mime" activeClassName='actvived'>mime</NavLink>
  • activeStyle:直接把啟用的樣式寫在後面
<NavLink to="/mime" activeStyle={{color: red,fontSize: 20px}}>mime</NavLink>
  • exact:如果為true,只有當訪問地址嚴格匹配時才會使用啟用樣式。

  • strict:如果為true,只有擋訪問地址後的斜槓嚴格匹配(有或沒有)才會使用啟用樣式。

  • isActive:跟一個函式,當導航啟用時要做的事情。

<StaticRouter>

api中介紹說這是一個從來不會改變位置的Router。當使用者沒有實際點選時,因此位置也從未發生變化。這在服務端渲染很有用,因為在後臺上, 我們只會渲染一次,而且不會直接地對使用者的互動操作做出反應。

看下API中的案例:

import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'

createServer((req, res) => {
  // 這個context包含渲染的結果
  const context = {}
  const html = ReactDOMServer.renderToString(
    <StaticRouter location={req.url} context={context}>
      <App/>
    </StaticRouter>
  )

  // 如果使用<Redirect>,context.url將包含要重定向到的URL
  if (context.url) {
    res.writeHead(302, {
      Location: context.url
    })
    res.end()
  } else {
    res.write(html)
    res.end()
  }
}).listen(3000)
  • basename:所有位置的基本url,正確的格式應該是前面有/後面沒有/
<StaticRouter basename="/main">
  <Link to="/list"/> <!--渲染出來應該是<a href="/main/list">-->
</StaticRouter>
  • location:如果是個字串就應該是伺服器上收到的url(req.url)。如果是個物件應該是一個類似{pathname, search, hash, state}的位置物件。
<StaticRouter location={{ pathname: '/home' }}>
  <App/>
</StaticRouter>
  • context:一個普通js物件,在渲染過程中元件可以向物件裡添屬性以儲存有關渲染的資訊。

我們使用它來找出渲染結果,context上下文物件是自己的,我們可以改變它。

//app上有個404元件,把status改成404
const NotFound = () => (
  <Route
    render={({ staticContext }) => {
      if (staticContext) {
        staticContext.status = 404;
      }
      return (
        <div>
          <h1>你輸入的地址不正確喲!</h1>
        </div>
      );
    }}
  />
);

// 在服務端我們就可以通過判斷,對這個404介面傳送404響應
const context = {};
const content = renderToString(
    <Provider store={store}>
        <StaticRouter location={req.url} context={context}>
            <App />
        </StaticRouter>
    </Provider>
);
if (context.status === 404) {
    res.status(404);
}

或者判斷重定向,如果有context.url就說明應用被重定向,這允許我們從伺服器傳送適當的重定向。

if (context.url) {
  // can use the `context.status` that
  // we added in RedirectWithStatus
  redirect(context.status, context.url)
}
<Switch>

用來包裹Route或者Redirect元件,而且不能放其他元素,用來渲染第一個匹配到的路由,不會向下繼續匹配。如果不用Switch包裹的話,訪問/about時下面三個元件都會被渲染。

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

將匹配到的路徑重定向到一個新的地址。新的地址應該覆蓋掉訪問的地址。

<Route path="/game" component={Game}></Route>
<Redirect from="/home" to="game" push={true}></Redirect>
  • from:匹配到的訪問的地址

  • to:重定向到的地址

  • push:為true時,重定向到的地址會新增到歷史訪問記錄,並且無法回到之前訪問的地址。

<withRouter>

withRouter可以用來包裝任何自定義元件,並將historylocationmatch三個物件傳入。這樣元件就可以拿到路由資訊。

import {withRouter} from 'react-router-dom';
const Home = withRouter(({history,location,match})=>{
  return <h1>{location.pathname}</h1>
})
history

表示瀏覽器歷史記錄的物件,可以對它進行一系列操作,有以下屬性和方法:

  • length:獲取歷史堆疊中的條目數

  • action:獲取當前操作(push/pop/replace)

  • location:獲取當前位置,包括(pathname、search、hash、state、)

  • push(''):新增一個新的條目到歷史堆疊(一般會用來跳轉到一個新頁面)

  • replace(''):替換掉當前堆疊上的條目

  • go(n):通過n個條目移動歷史堆疊中的指標,向前或向後n個

  • goBack():後退一個歷史條目,相當於go(-1)。

  • goForward():前進一個歷史條目,相當於go(1)

  • block:防止導航

match

<Route path>如何與url匹配的資訊,包含以下內容:

  • params:URL動態段解析對應的鍵值對

  • isExact:如果匹配整個URL則為true

  • path:<Route>匹配的路徑

  • url:當前真正訪問的路徑

location

程式當前所在的位置。。。

react-router-redux


如果使用的react-router-redux(把react-router url的變化、引數等資訊作為狀態,交給redux的store管理),則可以直接從state中獲取路由資訊。

import {routeMiddleware} from 'react-router-redux';
const store = createStore(combineReducers, applyMiddleware(routeMiddleware));
const mapStateToProps = (router) => ({
    pathname: rourter.location.pathname
})
export default connect(mapStateToProps)(MyControl)

具體請看API

react-router-config


react-router-config是幫助我們配置靜態路由的。算是比較好理解的,API只有兩個方法:

matchRoutes(routes, pathname)

該方法接收兩個引數(routes配置,請求路徑),返回一個陣列。

import { matchRoutes } from 'react-router-config'
const branch = matchRoutes(routes, '/child/23')

這個返回的陣列的每一個元素包含兩個屬性routes和match。結構如下:

[
  {
    route: {
      path: '/app',
      component: [(Function: component)],
      routes: [Array]
    },
    match: { path: '/app', url: '/app', isExact: false, params: {} }
  },
  {
    route: { path: '/app/resources', component: [Object] },
    match: {
      path: '/app/resources',
      url: '/app/resources',
      isExact: true,
      params: {}
    }
  }
];

這個方法在服務端貌似用的多一些,自己沒有用過不說太多。。。

renderRoutes(routes, extraProps = {})

該方法接受兩個引數:配置的路由和要傳的值。
寫這篇文章的時候,react-router-config的renderRoutes方法並沒有更新的npm上,其實已經實現了,不知道為什麼沒有上npm。看下原始碼:

import React from 'react';
import Switch from 'react-router/Switch';
import Route from 'react-router/Route';

const renderRoutes = (routes, extraProps = {}, switchProps = {}) =>
  (routes ? (
    <Switch {...switchProps}>
      {routes.map((route, i) => (
        <Route
          exact={route.exact}
          key={route.key || i}
          path={route.path}
          render={props =>
            (route.render ? (
              route.render(props)
            ) : (
              <route.component {...props} {...extraProps} route={route} />
            ))
          }
          strict={route.strict}
        />
      ))}
    </Switch>
  ) : null);

實際上就是在一個<Switch>中建立了多個<Route>

看一下API中給出的程式碼示例,路由可以進行統一的配置了,不過有個缺陷就是:在元件的render方法中還需要呼叫renderRoutes方法。

import { renderRoutes } from 'react-router-config'
// 配置路由
const routes = [
  { component: Root,
    routes: [
      { path: '/',
        exact: true,
        component: Home
      },
      { path: '/child/:id',
        component: Child,
        routes: [
          { path: '/child/:id/grand-child',
            component: GrandChild
          }
        ]
      }
    ]
  }
]
// 設定路由
ReactDOM.render((
  <BrowserRouter>
    {renderRoutes(routes)}
  </BrowserRouter>
), document.getElementById('root'))
// 在元件中呼叫方法
const Root = ({ route }) => (
  <div>
    <h1>Root</h1>
    {renderRoutes(route.routes)}
  </div>
)
const Home = ({ route }) => (
  <div>
    <h2>Home</h2>
  </div>
)
const Child = ({ route }) => (
  <div>
    <h2>Child</h2>
    {renderRoutes(route.routes, { someProp: 'these extra props are optional' })}
  </div>
)
const GrandChild = ({ someProp }) => (
  <div>
    <h3>Grand Child</h3>
    <div>{someProp}</div>
  </div>
)

相關文章