1. 程式人生 > >React Router 用法

React Router 用法

React Router 用法

一、DEMO

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

const First = () => <div>第一個示例的第【1】個路由,第一個路由在第一個和第二個url裡都會顯示,但不在第三個顯示</div>
const Second = () => <div>第一個示例的第【2】個路由,只在第二個url裡顯示</div>
const Third = () => <div>第三個示例</div>

class BaseDemo extends React.Component {
    render() {
        
return <div> <h3>React-router基礎示例</h3> <h3>路由資料被儲存在 this.props.match 裡,這是其中的值{JSON.stringify(this.props.match)}</h3> <Router> <div> {/* this.props.match.url 表示當前url */} <li><Link to={`${this
.props.match.url}/1`}>示例1</Link></li> <li><Link to={`${this.props.match.url}/2`}>示例2</Link></li> <li><Link to={`${this.props.match.url}/3`}>示例3</Link></li> <Route path={`${this.props.match.url}/1`} component={First}/> <Route path={`${this
.props.match.url}/2`} component={First}/> <Route path={`${this.props.match.url}/2`} component={Second}/> <Route path={`${this.props.match.url}/3`} component={Third}/> </div> </Router> </div> } }

 

二、路由巢狀

在頂級路由匹配到元件後,子元件裡面也可能有一個次級路由。

假如頂級路由的url為:/1,那麼次級路由匹配後的路徑一般來說是 /1/2;

但是假如當前路徑是 /1,然後次級路由裡有這樣一個標籤 <Link to="/2"}>示例2</Link>。

當我們點選這個標籤時,跳轉的 url 是 /2,而不是我們期望的 /1/2。因此我們需要拿到之前的 url /1,具體方法就是通過路由的 match 屬性來拿,於是就有了這種寫法:

<li><Link to={`${this.props.match.url}/2`}>示例2</Link></li>

意思就是跳轉到當前路徑,後面再拼接 /2這個路徑。

相對應的,我們在 Route 標籤裡也要新增相同的內容:

<Route path={`${this.props.match.url}/2`} component={second}/>

示例(子路由是 ChildRouter):

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

const First = () => <div>第一個示例的第【1】個路由,第一個路由在第一個和第二個url裡都會顯示,但不在第三個顯示</div>
const Second = () => <div>第一個示例的第【2】個路由,只在第二個url裡顯示</div>
const ChildRouter = (route) => <div>第一個示例的第【3】個路由,只在第三個url裡顯示
    <Router>
        <div>
            <h3>以下是子路由的屬性</h3>
            <p>{JSON.stringify(route)}</p>
            <li><Link to={`${route.match.url}/1`}>跳轉子1</Link></li>
            <li><Link to={`${route.match.url}/2`}>跳轉子2</Link></li>
            <hr/>
            {/* component 是一個React元件。
              * 注意,元件是放在這個屬性裡,而不是 Route 包裹的裡面
              * */}
            <Route path={`${route.match.url}/1`} component={() => <h3>這裡是子1</h3>}/>
            <Route path={`${route.match.url}/2`} component={() => <h3>這裡是子2</h3>}/>
        </div>
    </Router>
</div>

class RoutingNested extends React.Component {
    render() {
        return <div>
            <h3>React-router 路由巢狀</h3>
            <h3>路由資料被儲存在 this.props.match 裡,這是其中的值{JSON.stringify(this.props.match)}</h3>
            <Router>
                <div>
                    {/* this.props.match.url 表示當前url */}
                    <li><Link to={`${this.props.match.url}/1`}>示例1</Link></li>
                    <li><Link to={`${this.props.match.url}/2`}>示例2</Link></li>
                    <li><Link to={`${this.props.match.url}/3`}>示例3</Link></li>
                    <hr/>

                    <Route path={`${this.props.match.url}/1`} component={First}/>
                    <Route path={`${this.props.match.url}/2`} component={Second}/>
                    <Route path={`${this.props.match.url}/3`} component={ChildRouter}/>
                </div>
            </Router>
        </div>
    }
}

 

三、props

react-router 的路由資訊,都儲存在元件的 props 裡。

之所以是存在 props 裡,是因為我們寫在父元件裡的,是 Route 標籤,我們需要顯示的元件,是作為 Route 標籤的屬性而傳進去的。

而我們的元件,作為 Route 標籤的子元件而存在,因此,路由資料通過 props 傳給我們的元件。

因此:

1、只有 Route 標籤裡傳入的元件,才能通過 props 屬性讀取路由屬性(除非你自己手動傳給子元件);

2、每個能讀取路由屬性的元件,其 match 屬性,獲得的是當前級別的路由的屬性(例如本級路由的 match.url = '/Params/2',那麼上級路由的 match.url = '/Params'

3、match.isExact:假如當前路徑和 route 標籤裡的 path 完全相同,該值為 true,否則為 false(例如當匹配到次級路由時,那麼上級路由的這個屬性則為 false,次級當前的為 true)(當 url 為 / 時顯示該元件,/a 不顯示元件,需要使用這個);

4、match 屬性的值,是根據當前路由(元件所在的 route 標籤)的層級而決定的;

5、location 屬性的值,在每個能讀取到這個屬性的路由元件,都是相同的;

6、類似 /1?a=1 這樣的路徑,其中 ?a=1,是通過 location.search 來獲取;

7、路由資訊,當路由變化時,是會跟著一起更新的,但並不是實時更新的;

假如我通過點選 <Link> 標籤,讓路由從 /a 跳轉到 /b ,也就是說,從顯示 A 元件到顯示 B 元件。會發生以下事情:

【1】如果 Link 標籤裡有一個 onClick 事件,那麼顯然可以拿到 location 屬性的值。

在該事件執行的這段時間,props.location 的值,是 url 更新之前的。

並且,window.location(也就是原生的),其 url 也是更新之前的;

【2】那什麼時候可以獲取到更新之後的 url 呢?

答案是路由更新後,所對應的那個元件,在掛載的時候,生命週期處於 componentWillMount 時,可以獲取到最新的 url。

因此如果需要第一時間在父元件內拿到更新後的值,那麼需要在父元件,將回調函式傳給子元件才可以實現。

實現原理:可以參考元件通訊,父元件將回調函式傳給表單元件,然後表單元件負責執行這個回撥函式,並將修改後的值作為引數傳給函式。

例如:
【1、先例行引入】

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

【2、兩個子元件,分別點選顯示和直接顯示在頁面上】

class First extends React.Component {
    constructor() {
        super()
        this.log = this.log.bind(this)
    }

    render() {
        return <button onClick={this.log}>點選顯示路由資訊,點選後請檢視控制檯</button>
    }

    log() {
        console.log(this.props)
    }
}

const Second = props => <div>
    函式元件顯示路由資訊:(這裡是本級 Route 標籤的部分資訊)
    <pre>{JSON.stringify(props, undefined, 4)}</pre>
</div>

【3、父元件,負責對比其 props 與子元件不同】

class RoutingNested extends React.Component {
    constructor() {
        super()
    }

    render() {
        return <div>
            <h3>React-router 引數設定</h3>
            <h3>注意,這裡存的不是元件裡的路由資訊,而是上一級 Router 標籤的路由資訊</h3>
            <h3>路由資料被儲存在 this.props 裡,這是其中部分屬性 <pre>{JSON.stringify(this.props, undefined, 4)}</pre></h3>
            <Router>
                <div>
                    <li>
                        <Link to={`${this.props.match.url}/1?a=1`}
                              onClick={() => {
                                  console.log('Link 標籤(跳轉到/1)的 onClick 事件', this.props.location)
                              }}>
                            示例1
                        </Link>
                    </li>
                    <li>
                        <Link to={`${this.props.match.url}/2`}
                              onClick={() => {
                                  console.log('Link 標籤(跳轉到/2)的 onClick 事件', this.props.location)
                              }}>
                            示例2
                        </Link>
                    </li>
                    <hr/>

                    <Route path={`${this.props.match.url}/1`}
                           component={First}/>
                    <Route path={`${this.props.match.url}/2`} component={Second}/>
                </div>
            </Router>
        </div>
    }
}

 

四、引數

React路由取引數,有兩種:

1、?a=1 :這種屬於 search 字串,在 location.search 裡取值;
2、/a/123 :這種需要從 match.params裡取值;
但無論哪種,路由獲取到的值,是跳轉後的那一刻的值,而不是實時更新的最新值。

具體來說:

例1:假如 Link 標籤跳轉路徑實時繫結輸入框的一個值(假如值是 abc),這個值作為引數傳遞;
點選跳轉後,子元件讀取到當前傳的值 abc;
此時修改【1】中輸入框的值為 def;
請問子元件讀取到的值此時是多少?abc 還是 def;
答案是 abc;
原因是當前路徑是 abc,這個值讀取到的是當前路徑的值,而不是將要跳轉的路徑的值,因此不是實時更新的(顯然,也不應該是實時更新的);
手動修改位址列的 url:

例2:假如手動修改 url 為 ggg,那麼請問讀取到的值是多少?
我還真去試了一下。答案是除非你修改後,按回車跳轉路徑,會讀取到最新的;
否則,依然保持為修改前 abc;
即使你重新觸發 render 方法(比如修改 state 來實現),依然獲取到的是 abc ,而不是 ggg;
獲取最新值:

例3:如果你想要獲取到新值,那麼請重新點選跳轉(綁定了新的 url 的 Link 標籤)即可;
重新跳轉後(假如跳轉到同一個頁面),url 改變了,那麼元件會重新載入麼?
答案是否定的,如果跳轉到同一個元件,僅是引數改變,那麼元件是不會重新載入的,即元件內的資料保持之前不變,只有傳遞的引數改變了(生命週期函式也不會重新執行);


---生命週期:

1、元件的生命週期函式,只會在第一次掛載的時候執行,如果前後跳轉是同一個元件,那麼該元件的生命週期函式不會重複執行;
2、但 state 的生命週期,會多次執行(只要父元件的 state 改變了,子元件的相關函式會被執行);
3、由於路由資訊是通過 props 傳值的,因此也會多次觸發;
4、不過沒有影響,傳的值依然是舊值(因為路由資訊沒變);
5、假如你在 state 生命週期函式裡做了一些什麼事情,可能需要注意一下會不會帶來不良影響(雖然一般不會);
示例:

【例行引入和子元件】

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

const First = props => <div>
    第一個元件讀取引數(location.search) {props.location.search}
</div>

const Second = props => <div>
    第二個元件讀取引數(match.params.myParams) {props.match.params.myParams}
</div>

【父元件,負責配置路由和傳值】

class RoutingNested extends React.Component {
    constructor() {
        super()
        this.state = {
            firstNumber: 0,
            secondNumber: 0
        }
        this.changeFirst = this.changeFirst.bind(this)
        this.changeSecond = this.changeSecond.bind(this)
    }

    render() {
        return <div>
            <h3>4、React-router 傳參</h3>
            <h3>請在對應的跳轉標籤裡,輸入你想要傳的值</h3>
            <Router>
                <div>
                    <li>
                        <Link to={`${this.props.match.url}/1?a=${this.state.firstNumber}`}
                              onClick={() => {
                                  console.log('Link 標籤(跳轉到/1)的 onClick 事件', this.props.location)
                              }}>
                            示例1
                        </Link>
                        <input type="text" value={this.state.firstNumber} onChange={this.changeFirst}/>
                    </li>
                    <li>
                        <Link to={`${this.props.match.url}/2/${this.state.secondNumber}`}
                              onClick={() => {
                                  console.log('Link 標籤(跳轉到/2)的 onClick 事件', this.props.location)
                              }}>
                            示例2
                        </Link>
                        <input type="text" value={this.state.secondNumber} onChange={this.changeSecond}/>
                    </li>
                    <hr/>

                    <Route path={`${this.props.match.url}/1`} component={First}/>
                    <Route path={`${this.props.match.url}/2/:myParams`} component={Second}/>
                </div>
            </Router>
        </div>
    }

    changeFirst(e) {
        this.setState({
            firstNumber: e.target.value
        })
    }

    changeSecond(e) {
        this.setState({
            secondNumber: e.target.value
        })
    }
}

 

五、父元件傳 【值】 or 【函式】 給子元件

1、路由傳參是通過 props 傳參的;
2、子元件,是寫在 Route 標籤的 component 屬性中的;
3、通過 Route 標籤繫結值,是無法將值,從父元件傳給子元件的;

解決方案:

1)元件可以是一個函式;如const MySecond = () => <div>1</div>;
2)Route 標籤的 component 屬性支援以上函式寫法(顯而易見);
3)我們可以將子元件巢狀到函式返回的元件中;
4)此時我們的元件結構如下:父元件 >> Route 標籤 >> 函式元件 >> 子元件;
  ①父元件和 Route 進行資料通訊沒意義;
  ②父元件沒辦法和函式元件通訊;
  ③但父元件可以直接和子元件通訊;
5)因此,父元件將值先繫結給子元件標籤,然後再返回函式元件;

6)父元件是可以和子元件通訊的,但唯一問題是此時,如何將路由資訊從函式元件傳給子元件(路由資訊從Route傳給了函式元件);

 

  函式元件是可以拿到 props 的,通過 Object.assign() 將 props 和 父元件繫結給子元件的【函式/變數】混合到一起,再傳給子元件。

此時,子元件就同時拿到了 路由資料 和 父元件 傳給他的資料。

【傳一個物件給子元件】

唯一可能存在的問題是,這個資料怎麼傳給子元件(畢竟是一個物件),我們之前接觸的方法都是 k = v 方式傳給子元件,但顯然這個方法不能這麼做。

React也有解決方法,具體來說,利用 es6 的擴充套件運算子 ...

比較簡單的寫法是 <First {...props}/> ,將自動展開 props 並傳給 First 元件。

例如:

【例行引入依賴和子元件,子元件負責顯示值】

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

class First extends React.Component {
    render() {
        return <div>【1】當前 time 值為:{this.props.time}</div>
    }
}

const Second = (props) => <div>2】time(負數): {props.time * -1}
</div>

【父元件】

class RoutingNested extends React.Component {
    constructor() {
        super()
        this.state = {
            time: 0
        }
    }

    // 這個是生命週期,目的是為了測試 state 的傳遞
    componentWillMount() {
        this.timer = setInterval(() => {
            this.setState({
                time: this.state.time + 1
            })
        }, 1000)
    }

    // 解除安裝時,刪除定時器
    componentWillUnmount() {
        clearInterval(this.timer)
    }

    render() {
        // 這個寫法和寫在元件裡,基本沒什麼區別,不過這樣寫感覺好看一些
        const MySecond = props => {
            let obj = Object.assign({}, {time: this.state.time}, props)
            return <Second {...obj}/>
        }

        return <div>
            <h3>5、父元件傳參給子元件</h3>
            <p>父元件當前值為:{this.state.time}</p>
            <Router>
                <div>
                    <li>
                        <Link to={`${this.props.match.url}`}>
                            跳轉檢視傳參【1</Link>
                    </li>
                    <li>
                        <Link to={`${this.props.match.url}/2`}>
                            跳轉示例【2</Link>
                    </li>
                    <hr/>

                    {/* 這種是寫在元件裡,沒啥區別 */}
                    <Route exact path={`${this.props.match.url}/`}
                           component={props => {
                               let obj = Object.assign({}, {time: this.state.time}, props)
                               return <First {...obj}/>
                           }}/>
                    <Route path={`${this.props.match.url}/2`} render={MySecond}/>
                </div>
            </Router>
        </div>
    }
}

 

六、path 和 url 的區別

在 match 屬性中,有 path 和 url 屬性,大部分時間他們是一樣的,那麼區別是什麼呢?

假如路由匹配路徑是 /Params/2/:myParams,實際跳轉路徑是 /Params/2/1(此時匹配成功)。

那麼;

url:/Params/2/1
path:/Params/2/:myParams

在元件裡,每個元件的路由資料,都是各自獨立的。

在之前分析中,已知:

match 屬性的值,儲存的是該 Route 標籤的路由;
location 屬性的值,其中 url 和 path 不同 Route 元件中,值是相同的;

但【2】並不準確,準確的說,自帶的 hash , search , pathname 這三個屬性的值,是相同的;

假如你在裡面添加了其他資料,那麼結果就有所不同了。

例如 Link 標籤,他有一個屬性 to,可以用於路徑跳轉。

比較常見的是以下這種寫法:

<Link to={`${props.match.url}/`}>子路由</Link>

但是,to 的值,也可以用物件,例如這樣:

<Link to={{
    pathname: `${this.props.match.url}/1`,
    myState: '這是我自定義的變數'
}}>示例1</Link>

當路由資訊匹配時:

1、父元件的路由資訊為:

{
    "match": {
        "path": "/RouteInfo",
        "url": "/RouteInfo",
        "isExact": false,
        "params": {}
    },
    "location": {
        "pathname": "/RouteInfo/1",
        "search": "",
        "hash": ""
    },
    "history": {
        "length": 9,
        "action": "POP",
        "location": {
            "pathname": "/RouteInfo/1",
            "search": "",
            "hash": ""
        }
    }
}

2、被傳值的子元件的路由資訊:

{
    "match": {
        "path": "/RouteInfo/1",
        "url": "/RouteInfo/1",
        "isExact": true,
        "params": {}
    },
    "location": {
        "pathname": "/RouteInfo/1",
        "myState": "這是我自定義的變數",
        "search": "",
        "hash": ""
    },
    "history": {
        "length": 24,
        "action": "PUSH",
        "location": {
            "pathname": "/RouteInfo/1",
            "myState": "這是我自定義的變數",
            "search": "",
            "hash": ""
        }
    }
}

可以看到,被傳值的子元件的路由資訊,location 會多了一個屬性。

但是請注意,以下幾種情況會導致失去這些資訊:

重新整理頁面;
訪問更深一層的子元件(因為資訊被更新了);
重新整理後,訪問相同的 url。舉例來說,你訪問了該 url,傳值了也收到了,然後重新整理頁面,再點選該 url,是沒有的。原因是相同 url 跳轉;

八、重定向

重定向有兩種方式,第一種是通過 <Redirect> 標籤實現,第二種是通過程式設計式導航方式實現。

<Redirect> 標籤】

<Redirect to={'/default'}/>

【程式設計式導航方式】

this.props.history.push('/default')

兩個的效果是一樣的

 

九、登入攔截

1、需要元件:

  1)登入功能元件;

  2)受保護元件(需要登入後才能訪問);
  3)受保護元件的父元件(可選,如果 1 和 2 不是同一個路徑,則需要,否則可以不需要)(用於進入受保護元件之前,檢查登入資訊,不符合要求則跳轉到登入元件);
邏輯:(有父元件,登入和受保護元件不是同一個路徑)

2、嘗試訪問受保護元件;
  1)進入父元件,檢查儲存登入資訊的物件,確認當前是否登入;
  2)如果已登入,則允許訪問受保護元件;
  3)如果未登入,則觸發重定向,進入登入元件;
  4)登入元件輸入資訊,嘗試登入;
  5)輸入資訊符合要求後(不符合則報錯),將資訊寫入儲存登入資訊的物件,並跳轉到受保護元件的路徑;
  6)登入後,如果需要登出,則清除登入資訊,並再次進行頁面跳轉;
比較核心的就是幾點:

1、進入受保護頁面時,需要先檢查一下登入資訊;
2、登入和登出時,除了寫入和清除登入資訊之外,還需要進行一次路徑跳轉(登入 -> 受保護頁面,受保護頁面 -> 未登入時的預設頁面)(如何重定向參照【八】);

十、路由配置

const RouteConfig = [
    {
        path: 'first',
        component: First,
        name: '第一個路由',
        routes: [
            {
                path: '1',
                component: ChildOne,
                name: '1-1'
            }
        ]
    },
    {
        path: 'second',
        component: Second,
        name: '第二個路由'
    }
]

解釋:

1、component 是元件;
2、path 是該元件對應的 url;
3、name 非必須,這裡是為了省事,自動生成 Link 標籤的描述文字才加的;
4、routes 可選,是該元件的子元件路由資訊。具體來說,就是將這個值傳給路由的子元件,然後子元件就可以拿這個生成自己的子路由資訊了;

單純看這個比較麻煩,我們先來看一個函式吧:

const createRouter = (routes, props) => (
    <Router>
        <div>
            {/* 自動生成 Link 標籤 */}
            {createLink(routes, props)}
            <hr/>
            {/* 自動生成 Route 標籤 */}
            {createRoute(routes, props)}
        </div>
    </Router>
)
createRouter(RouteConfig, props)

這個函式幹了三件事:

1、建立了一個 Router 標籤;

2、根據 routes (來源於上面的路由配置表),自動生成了多個 Link 標籤;

3、以及多個 Route 標籤;

預期上,這兩個生成標籤的函式,他們生成的 Link 標籤和 Route 標籤是一一對應的;

然後我們再分別看這兩個生成函式,先看第一個生成 Link 標籤的:

const createLink = (routes, props) => (
    <ol>
        {
            routes.map(route => (
                <li key={route.path}>
                    <Link to={`${props.match.url}/${route.path}`}>{route.name}</Link>
                </li>
            ))
        }
    </ol>
)

注意,createLink 傳入的第一個引數,是一個數組(參考上面的 RouteConfig 變數);

遍歷這個陣列,生成一個 li 標籤,內包裹一個 Link 標籤,其 to 屬性是當前 url(props.match.url),後面加上路由路徑 route.path,描述文字就是 route.name。

示例(map 返回陣列的一個元素):

<li key='first'>
    <Link to={`/first`}>第一個路由</Link>
</li>

最後結果就是自動生成了導航欄。

然後我們再看另外一個生成 Route 標籤的函式:

const createRoute = (routes, props) => (
    <React.Fragment>
        {routes.map((route, i) => {
            let obj = {
                key: i,
                ...route,
                path: `${props.match.url}/${route.path}`,
                component: props => {
                    let obj = {routes: route.routes, ...props}
                    return <route.component {...obj}/>
                }
            }
            return <Route {...obj}/>
        })}
    </React.Fragment>
)

懂了上面那個後,這個自然而言也很容易懂了。

遍歷 routes ,取出 component 屬性(即該路徑對應的元件),

將當前子路由的路由配置表,如下:

routes: [
    {
        path: '1',
        component: ChildOne,
        name: '1-1'
    }
]

混合到路由資訊裡(見 obj.component 屬性,如果看不懂,請參考上面【7】中,component 的屬性是一個函式的那個示例)。

這樣,對應的子元件,就可以拿到路由配置表中,該元件的子路由資訊了。

具體舉例來說,就是 First 這個元件,可以從 props 裡取出以下資料:

routes: [
    {
        path: '1',
        component: ChildOne,
        name: '1-1'
    }
]

因此,子元件可以繼續呼叫上面兩個函式,來自動生成 Link 標籤,和 Route 標籤。

簡單總結一下上面的過程:

1、呼叫函式 createRouter ,傳參 路由配置表;
2、createRouter 函式會呼叫 自動生成 Link 標籤 和 自動生成 Route 標籤的函式;
3、createLink 函式,根據路由配置表,自動生成了當前層級的 Link 標籤;
4、createRoute 函式,根據路由配置表,自動生成了當前層級的 Route 標籤;
5、createRoute 函式,假如路由配置表某個對應元件,有 routes 屬性,則自動將這個屬性的值,傳給了該元件,於是該元件可以拿到自己的,這一層級的子元件路由配置表;