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> } }
在頂級路由匹配到組件後,子組件裏面也可能有一個次級路由。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> } }
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> } }
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 }) } }
一、路由傳參是經過 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> } }
在 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 跳轉;
重定向有兩種方式,第一種是經過 <Redirect>
標籤實現,第二種是經過編程式導航方式實現。
【<Redirect>
標籤】
<Redirect to={'/default'}/>
【編程式導航方式】
this.props.history.push('/default')
兩個的效果是同樣的
一、須要組件:
1)登陸功能組件;
2)受保護組件(須要登陸後才能訪問);
3)受保護組件的父組件(可選,若是 1 和 2 不是同一個路徑,則須要,不然能夠不須要)(用於進入受保護組件以前,檢查登陸信息,不符合要求則跳轉到登陸組件);
邏輯:(有父組件,登陸和受保護組件不是同一個路徑)
二、嘗試訪問受保護組件;
1)進入父組件,檢查存儲登陸信息的對象,確認當前是否登陸;
2)若是已登陸,則容許訪問受保護組件;
3)若是未登陸,則觸發重定向,進入登陸組件;
4)登陸組件輸入信息,嘗試登陸;
5)輸入信息符合要求後(不符合則報錯),將信息寫入存儲登陸信息的對象,並跳轉到受保護組件的路徑;
6)登陸後,若是須要登出,則清除登陸信息,並再次進行頁面跳轉;
比較核心的就是幾點:
一、進入受保護頁面時,須要先檢查一下登陸信息;
二、登陸和登出時,除了寫入和清除登陸信息以外,還須要進行一次路徑跳轉(登陸 -> 受保護頁面,受保護頁面 -> 未登陸時的默認頁面)(如何重定向參照【八】);
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 屬性,則自動將這個屬性的值,傳給了該組件,因而該組件能夠拿到本身的,這一層級的子組件路由配置表;