手寫那些年用過的React路由

認知

以前沒有先後端分離的時候,路由幾乎都是針對後臺而言的,有人說掌控了路由就至關於佔了主權地位,我以爲這句話是不爲過的。由於路由才能決定你的頁面從哪裏來,到哪裏去。如今的先後端分離項目,路由幾乎都給了前端處理,好比你常用的vue-router,react-router,今天就react路由爲基礎,一塊兒實現下React開發中經常使用的路由那些個東東。前端

選擇

react-router 仍是 react-router-dom? 在 React 的使用中,咱們通常要引入兩個包,react 和 react-dom,那麼 react-router 和react-router-dom 是否是兩個都要引用呢?答案是它們兩個只要引用一個就好了,二者區別是後者比前者多出了 <Link> <BrowserRouter> 這樣的 DOM 類組件。 所以咱們只需引用 react-router-dom 這個包就好了。固然,若是搭配 redux ,你還須要使用 react-router-redux。 what's the diff between react-router-dom & react-router? #4648

HashRouter

本文依舊運用測試驅動開發的模式進行理順思路。。。。。。Begin。。。。
vue

  1. 根據不一樣的路由渲染三個不一樣的組件(Home,News,About)
let Home = () => <div>首頁</div>;
let News = () => <div>新聞</div>;
let About = () => <div>關於咱們</div>;
複製代碼

渲染方法:react

ReactDOM.render(
    <Router>
        <Route path='/home' component={Home}></Route>
        <Route path='/news' component={News}></Route>
        <Route path='/about component={About}></Route> </Router>, document.querySelector('#app')
)

複製代碼

經過如下傳遞參數的方法,觀察獲得有用的參數(圖1)git

let Home = (props,context)=> {
    console.log(props)
    return  <div>首頁</div>;
} 
複製代碼

提取最主要的參數:

{
  history:{
    push()
  },
  location:{pathname:'/home'},
  match{
    params:{},
    path:'/home',
    url:'/home'
  }
}
複製代碼

編寫父組件HashRouter.js,以及單條路由Route.js
HashRouter.js:
在組件掛載的時候,監聽hashchange事件,即從新渲染頁面github

componentWillMount(){
    window.location.hash = window.location.hash || '/';
    let render = ()=>{
        this.setState({});
    }
    window.addEventListener('hashchange',render);
}
複製代碼

經過上下文context進行父子組件之間的通訊,傳遞location.pathname值vue-router

static childContextTypes = {
    location:PropTypes.object
}
constructor(props){
    super(props);
    this.state = {};
}
getChildContext(){
    return {
        location:{pathname:window.location.hash.slice(1)||'/'}
    }
}
複製代碼

HashRouter具體render的對象爲子組件,自己並無東西須要render。express

render(){
    return this.props.children;
}
複製代碼

Route.js:
獲取context中的pathname,跟組件傳進來的path進行比較,看是否相等,相等則渲染傳遞進來的props中的componentnpm

static contextTypes = {
    location: PropTypes.object
}
render(){
    let {path,component:Component} = this.props;
    let {location:{pathname}} = this.context;
    if(path == pathname || pathname.startsWith(path)){
        return <Component location={this.context.location} history={this.context.history}/>;
    }
    return null;
}
複製代碼

到此經過瀏覽器改變輸入的hash值,就能夠切換到不一樣的組件,顯示不一樣的內容。
2. 經過導航的形式,點擊頁面的導航條,切換到不一樣的頁面(Link組件) 本質爲a標籤。點擊經過上下文的history改變hash。redux

HashRouter:
static childContextTypes = {
    location:PropTypes.object
}
getChildContext(){
    return {
        location:{pathname:window.location.hash.slice(1)||'/'},
        history:{
            push(path){
                window.location.hash = path;
            }
        }
    }
}
複製代碼

Link.js後端

import React,{Component} from 'react';
import ProTypes from 'prop-types';
export default class Link extends Component{
    static contextTypes = {
        history:ProTypes.object
    }
    render(){
        return (
            // <a href={"#"+this.props.to}>{this.props.children}</a>
            <a onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
        )
    }
}
複製代碼

調用:

<ul className='nav navbar-nav'>
    <li>
        <Link to='/home'>首頁</Link>
    </li>
    <li>
        <Link to='/news'>新聞管理</Link>
    </li>
    <li>
        <Link to='/about'>關於咱們</Link>
    </li>
</ul>
複製代碼
  1. 二級路由
    咱們建立一個新聞管理的類News.js,在這裏進行二級路由的分發,有一個新聞列表(路由爲/news/list,組件爲NewsList),和一個添加新聞(路由爲/news/add,組件爲NewsAdd),點擊添加新聞和新聞列表 能夠跳轉到相對應的路由 此項功能經過以前的實現是支持的,無須對咱們本身的HashRouter等進行改寫

  2. 路徑參數實現之params
    express中,vue中都有相似於'/news/datail/:id'這樣的路徑,後面的id是動態匹配的,即爲路徑參數。並且類似的是這些實現都用到了path-to-regxp這個庫。這裏咱們也重點使用這個庫實現路徑參數的功能。 在News.js中添加一條路由信息,能夠跳轉到NewsDetail詳情頁。

<Route path='/news/datail/:id' component={NewsDetail}></Route>
複製代碼

而後在Route.js添加constructor,經過path-to-regexp獲取到正則匹配路徑信息,而且修改render中路徑匹配的方法以下:

constructor(props) {
    super(props);
    let { path } = props;
    this.keys = [];
    this.regxp = pathToRegexp(path, this.keys, { end: false });
    this.keys = this.keys.map(key => key.name);
}
let { location } = this.context;
let result = location.pathname.match(this.regxp);
let props = {
    location,
    history: this.context.history
}
if (result) {
    let [url, ...values] = result;
    props.match = {
        url,
        path,
        params: this.keys.reduce((memo, key, idx) => {
            memo[key] = values[idx];
            return memo;
        }, {})
    }
    return <Component {...props}></Component>
} else {
    return null;
}
複製代碼

上述props.match是很重要的一部,拿到match的信息 圖1中也有顯示

  1. Switch組件 有的時候在咱們寫路由信息的時候,會手誤寫成兩個,好比
<Route path='/home' component={Home}></Route>
<Route path='/home' component={Home}></Route>
<Route path='/news' component={News}></Route>
<Route path='/about' component={About}></Route>
複製代碼

這裏有兩個/home,那麼頁面就會顯示兩次,這時候咱們須要些一個Switch組件,套在最外層,那麼原理就是依次匹配,匹配到了直接返回,再也不往下匹配。由此得出Switch.js的邏輯:

<Switch>
    <Route path='/home' component={Home}></Route>
    <Route path='/home' component={Home}></Route>
    <Route path='/news' component={News}></Route>
    <Route path='/about' component={About}></Route>
</Switch>
export default class Switch extends Component {
    static contextTypes = {
        location: ProTypes.object
    }
    render() {
        let { pathname } = this.context.location;
        let children = this.props.children;
        for (let i = 0; i < children.length; i++) {
            let child = children[i];
            let { path } = child.props;
            if (pathToRegexp(path, [], { end: false }).test(pathname)) {
                return child;
            }
        }
        return null;
    }
}
複製代碼

小結

未完待續。。。。

相關文章
相關標籤/搜索