React Router 用法

React Router 用法

1、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>
    }
}

 

2、路由嵌套

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

假如頂級路由的url爲:/1,那麼次級路由匹配後的路徑通常來講是 /1/2;es6

可是假如當前路徑是 /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這個路徑。react-router

相對應的,咱們在 Route 標籤裏也要添加相同的內容:dom

<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>
    }
}

 

3、props

react-router 的路由信息,都存儲在組件的 props 裏。測試

之因此是存在 props 裏,是由於咱們寫在父組件裏的,是 Route 標籤,咱們須要顯示的組件,是做爲 Route 標籤的屬性而傳進去的。this

而咱們的組件,做爲 Route 標籤的子組件而存在,所以,路由數據經過 props 傳給咱們的組件。url

所以:

一、只有 Route 標籤裏傳入的組件,才能經過 props 屬性讀取路由屬性(除非你本身手動傳給子組件);

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

三、match.isExact:假如當前路徑和 route 標籤裏的 path 徹底相同,該值爲 true,不然爲 false(例如當匹配到次級路由時,那麼上級路由的這個屬性則爲 false,次級當前的爲 true)(當 url 爲 / 時顯示該組件,/a 不顯示組件,須要使用這個);

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

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

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

七、路由信息,當路由變化時,是會跟着一塊兒更新的,但並非實時更新的;

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

【1】若是 Link 標籤裏有一個 onClick 事件,那麼顯然能夠拿到 location 屬性的值。

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

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

【2】那何時能夠獲取到更新以後的 url 呢?

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

所以若是須要第一時間在父組件內拿到更新後的值,那麼須要在父組件,將回調函數傳給子組件才能夠實現。

實現原理:能夠參考組件通訊,父組件將回調函數傳給表單組件,而後表單組件負責執行這個回調函數,並將修改後的值做爲參數傳給函數。

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

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

【二、兩個子組件,分別點擊顯示和直接顯示在頁面上】

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>

【三、父組件,負責對比其 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>
    }
}

 

4、參數

React路由取參數,有兩種:

一、?a=1 :這種屬於 search 字符串,在 location.search 裏取值;
二、/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 改變了,那麼組件會從新加載麼?
答案是否認的,若是跳轉到同一個組件,僅是參數改變,那麼組件是不會從新加載的,即組件內的數據保持以前不變,只有傳遞的參數改變了(生命週期函數也不會從新執行);


---生命週期:

一、組件的生命週期函數,只會在第一次掛載的時候執行,若是先後跳轉是同一個組件,那麼該組件的生命週期函數不會重複執行;
二、但 state 的生命週期,會屢次執行(只要父組件的 state 改變了,子組件的相關函數會被執行);
三、因爲路由信息是經過 props 傳值的,所以也會屢次觸發;
四、不過沒有影響,傳的值依然是舊值(由於路由信息沒變);
五、假如你在 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>四、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
        })
    }
}

 

5、父組件傳 【值】 or 【函數】 給子組件

一、路由傳參是經過 props 傳參的;
二、子組件,是寫在 Route 標籤的 component 屬性中的;
三、經過 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>五、父組件傳參給子組件</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>
    }
}

 

6、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>

當路由信息匹配時:

一、父組件的路由信息爲:

{
    "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": ""
        }
    }
}

二、被傳值的子組件的路由信息:

{
    "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 跳轉;

8、重定向

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

<Redirect> 標籤】

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

【編程式導航方式】

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

兩個的效果是同樣的

 

9、登陸攔截

一、須要組件:

  1)登陸功能組件;

  2)受保護組件(須要登陸後才能訪問);
  3)受保護組件的父組件(可選,若是 1 和 2 不是同一個路徑,則須要,不然能夠不須要)(用於進入受保護組件以前,檢查登陸信息,不符合要求則跳轉到登陸組件);
邏輯:(有父組件,登陸和受保護組件不是同一個路徑)

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

一、進入受保護頁面時,須要先檢查一下登陸信息;
二、登陸和登出時,除了寫入和清除登陸信息以外,還須要進行一次路徑跳轉(登陸 -> 受保護頁面,受保護頁面 -> 未登陸時的默認頁面)(如何重定向參照【八】);

10、路由配置

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

解釋:

一、component 是組件;
二、path 是該組件對應的 url;
三、name 非必須,這裏是爲了省事,自動生成 Link 標籤的描述文字才加的;
四、routes 可選,是該組件的子組件路由信息。具體來講,就是將這個值傳給路由的子組件,而後子組件就能夠拿這個生成本身的子路由信息了;

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

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

這個函數幹了三件事:

一、建立了一個 Router 標籤;

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

三、以及多個 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 標籤。

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

一、調用函數 createRouter ,傳參 路由配置表;二、createRouter 函數會調用 自動生成 Link 標籤 和 自動生成 Route 標籤的函數;三、createLink 函數,根據路由配置表,自動生成了當前層級的 Link 標籤;四、createRoute 函數,根據路由配置表,自動生成了當前層級的 Route 標籤;五、createRoute 函數,假如路由配置表某個對應組件,有 routes 屬性,則自動將這個屬性的值,傳給了該組件,因而該組件能夠拿到本身的,這一層級的子組件路由配置表;

相關文章
相關標籤/搜索