react-router原理之路徑匹配

首先看一下react-router官網示例react

const BasicExample = () => (
  <Router>
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/topics" component={Topics} />
    </div>
  </Router>
);
複製代碼

上面代碼的運行效果點擊這裏web

本文的目的是講清楚react-router如何根據瀏覽器中的url來渲染不一樣的組件的,至於url是如何改變的(Link組件)請參見下一篇react-router原理之Link跳轉正則表達式

基礎依賴path-to-regexp

react-router提供了專門的路由匹配方法matchPath(位於packages/react-router/modules/matchPath.js),該方法背後依賴的實際上是path-to-regexp包。數組

path-to-regexp輸入是路徑字符串(也就是Route中定義的path的值),輸出包含兩部分瀏覽器

  • 正則表達式(re)
  • 一個數組(keys)(用於記錄param的key信息)

針對path中的參數(下例中的:bar)path-to-regexp在生成正則的時候會把它做爲一個捕獲組進行定義,同時把參數的名字(bar)記錄到數組keys中bash

var pathToRegexp = require('path-to-regexp')
var keys = []
var re = pathToRegexp('/foo/:bar', keys)
console.log(re);
console.log(keys);

// 輸出
/^\/foo\/([^\/]+?)(?:\/)?$/i
[ { name: 'bar',
    prefix: '/',
    delimiter: '/',
    optional: false,
    repeat: false,
    partial: false,
    pattern: '[^\\/]+?' } ]
複製代碼

matchPath核心

matchPath方法首先經過path-to-regexp的方法來獲取Route上定義的path對應的正則,再將生成的正則表達式與url中的pathname作正則匹配判斷是否匹配。markdown

console.log(re.exec('/foo/randal'));   
console.log(re.exec('/foos/randal'));

// 輸出
[ '/foo/randal', 'randal', index: 0, input: '/foo/randal' ]
null
複製代碼

因爲path-to-regexp建立的正則中對param部分建立了捕獲組,同時把param的key記錄在了單獨的數組keys中,所以經過遍歷正則匹配的結果和keys數組便可將param的key和value進行關聯,以下所示:react-router

const match = re.exec('/foo/randal');
const [url, ...values] = match;

const params = keys.reduce((memo, key, index) => {
  memo[key.name] = values[index];
  return memo;
}, {})

console.log(params) // {"bar": "randal"}
複製代碼

最終matchPath針對未匹配的返回null,匹配成功的則返回一個object函數

return {
    path,    //  /foo/:bar
    url:     //  /foo/randal
    isExact, //  false
    params:  //  {"bar": "randal"}
  };
複製代碼

Route渲染

Route組件維護一個state(match),match的值來自於matchPath的執行結果,以下所示oop

state = {
    match: this.computeMatch(this.props, this.context.router)
  };
  computeMatch({ computedMatch, location, path, strict, exact, sensitive }, router) {
  	 if (computedMatch) return computedMatch; // computedMatch留給Switch使用
    const { route } = router;
    const pathname = (location || route.location).pathname;

    return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
  }
複製代碼

當state.match不爲null的時候Route纔會建立關聯的component。

Route關聯component有多種形式(render、component、children) children定義形式與render和component的不一樣在於,children的執行與match無關,即便match爲null,children函數也是會執行的,至於爲何會有children這樣的設計呢,在接下來的一篇關於Link組件的文章中會提到。

render() {
    const { match } = this.state;
    const { children, component, render } = this.props;
    const props = { match, ...};

    if (component) return match ? React.createElement(component, props) : null;

    if (render) return match ? render(props) : null;

    if (typeof children === "function") return children(props);

    return null;
  }
複製代碼

至此關於react-router如何根據url渲染不一樣Route的組件都講解完了,不過有時候只用Route的話仍是會產生問題,好比:

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

若是當前訪問的url是/about的話,上面的寫法會在頁面上渲染About、User、NoMatch三個組件,其實咱們但願的是隻渲染About組件。

Switch 路徑匹配前置

針對上面的問題,能夠用Switch組件包裹一下

<Switch>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={NoMatch}/>
</Switch>
複製代碼

通過Switch包裹後, 若是訪問url是/about的話則只會渲染About組件了,若是url是/abouts的話,則只會渲染User組件。

Switch組件的特色是隻會從子children裏挑選一個Route渲染,爲了實現只渲染一個的目的,Switch採用的是Route路徑匹配前置,不依賴Route的render方法來渲染組件,而是在Switch中就開始Route的路徑匹配,一旦發現一個匹配的路徑,則將其挑選出來進行渲染。Switch的關鍵代碼以下

render() {
    const { route } = this.context.router;
    const { children } = this.props;
    const location = this.props.location || route.location;

    let match, child;
    // 子children至關於只是選項,Switch負責從中挑選與當前url匹配的Route,被選中的子Route纔會觸發render方法
    React.Children.forEach(children, element => {
      if (match == null && React.isValidElement(element)) {
        const {
          path: pathProp,
          exact,
          strict,
          sensitive,
          from
        } = element.props;
        const path = pathProp || from;

        child = element;
        match = matchPath(
          location.pathname,
          { path, exact, strict, sensitive },
          route.match
        );
      }
    });

    return match
      ? React.cloneElement(child, { location, computedMatch: match })
      : null;
  }
複製代碼

上面代碼把matchPath的執行結果match以computedMatch爲key傳入到Route中了,這樣就避免了重複匹配,Route的computeMatch方法就能夠直接複用了,computeMatch代碼參見前面的Route渲染章節。

進入下一篇react-router原理之Link跳轉

相關文章
相關標籤/搜索