文章首發於:github.com/USTB-musion…html
用react-router v4能夠實現單頁面應用,能夠將組件映射到路由上,將對應的組件渲染到想要渲染的位置。 react路由有兩種方式:一種是HashRouter,即利用hash實現路由切換。另外一種是BrowserRouter,即利用html5 API實現路由的切換。本文是在閱讀react-router v4源碼以後簡單的實現。html5
本文將從如下幾部分進行總結:react
如下是參照react-router 官方文檔實現的一個簡單例子:git
import React, { Component } from 'react';
import { render } from 'react-dom';
import { HashRouter as Router, Route, Link, Redirect, Switch } from 'react-router-dom';
import Home from './Home';
import Profile from './Profile';
import User from './User';
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<Router>
<div>
<div>
<Link to="/home">首頁</Link>
<Link to="/profile">我的中心</Link>
<Link to="/user">用戶</Link>
</div>
<div>
<Switch>
<Route path="/home" exact={true} component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/user" component={User}></Route>
<Redirect to="/home"></Redirect>
</Switch>
</div>
</div>
</Router>
)
}
}
render(<App></App>, document.querySelector('#root'));
複製代碼
這樣就能實現一個超級簡單的單頁面應用,根據路徑的變化渲染相應的組件。點擊首頁會跳轉到Home組件,點擊我的中心會跳轉到Profile組件,點擊用戶會跳轉到User組件。在這個例子當中。看下這行代碼github
import { HashRouter as Router, Route, Link, Redirect, Switch } from 'react-router-dom';
複製代碼
若是不引入'react-router-dom'這個包,而是本身實現一個my-react-router-dom,暴露出HashRouter,Route,Link,Redirect,Switch這幾個組件,而且這幾個組件和react-router-dom提供的功能基本同樣,那究竟怎麼實現呢?設計模式
// 這是index.js文件
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Redirect from './Redirect';
import Switch from './Switch';
export {
HashRouter,
Route,
Link,
Redirect,
Switch
}
複製代碼
再看一下context.js文件,context能夠跨組件傳遞數據:api
// 這是context.js文件
import React, { Component } from 'react';
// 這個方法是16.3新增的
let { Provider, Consumer} = React.createContext();
export { Provider, Consumer};
複製代碼
‘現有的原生 Context API 存在着一個致命的問題,那就是在 Context 值更新後,頂層組件向目標組件 props 透傳的過程當中,若是中間某個組件的 shouldComponentUpdate 函數返回了 false,由於沒法再繼續觸發底層組件的 rerender,新的 Context 值將沒法到達目標組件。這樣的不肯定性對於目標組件來講是徹底不可控的,也就是說目標組件沒法保證本身每一次均可以接收到更新後的 Context 值。’如何解讀 react 16.3 引入的新 context api ---誠身的回答bash
新版 Context API 提供Provider和Consumer兩個組件,顧名思義,provider(提供者)和Consumer(消費者):react-router
分析上面的例子,HashRouter的別名設置爲Router,傳入的 Router 中的每一項即爲一條路由配置,表示在匹配給定的地址時,應該使用什麼組件渲染視圖。HashRouter的簡單實現以下:dom
// 這是hashRouter.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './context';
export default class HashRouter extends Component {
constructor() {
super();
this.state = {
location: {
// slice(1)將#截掉
pathname: window.location.hash.slice(1) || ''
}
};
}
componentDidMount() {
// 首次進入頁面url會顯示#/
// 如localhost:3000會顯示爲localhost:3000/#/
window.location.hash = window.location.hash || '/';
// 監聽hash值變化,從新設置location狀態
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
})
})
}
render() {
// 每一個子route對象都會包含location
let value = {
location: this.state.location,
history: {
push(to) {
window.location.hash = to;
}
}
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
}
複製代碼
這樣一來,使用hashRouter的方式第一次進入頁面時,將顯示#/,如localhost:3000首次進入頁面將會顯示localhost:3000/#/,經過Provider組件來將location和history對象傳遞給子route,經過hashchange方法來監聽hash值的變化,進而從新設置location的狀態。
經過分析上面的例子,看這一行代碼:
<Route path="/home" exact={true} component={Home}></Route>
複製代碼
發現Route組件包含有path,exact,component這些屬性,那如何實現Route組件呢?看Route.js文件:
// 這是route.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
import pathToReg from 'path-to-regexp';
export default class Router extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
// <route path="xx" component="xx" exact={true}></route>
// path是route傳遞的
let { path, component: Component, exact = false } = this.props;
// pathname是location中的
let pathname = state.location.pathname;
// 根據path實現一個正則,經過正則匹配
// location中的/home/123是能匹配到Home組件的
let keys = [];
let reg = pathToReg(path, keys, { end: exact});
keys = keys.map(item => item.name);
let result = pathname.match(reg);
let [url, ...values] = result || [];
// 實現路由跳轉
let props = {
location: state.location,
history: state.history,
match: {
params: keys.reduce((obj, current, index) => {
obj[current] = values[index];
return obj;
}, {})
}
}
if (result) {
return <Component {...props}></Component>
}
return null
}}
</Consumer>
)
}
}
複製代碼
利用path-to-regexp這個庫來進行是否嚴格匹配路徑,利用新版context的Consumer組件和props來取出path,component,axact這幾個參數。若是匹配到path,則經過<Component {...props}>返回相應匹配到的組件。
分析上面的例子,Link的組件實現稍微簡單一些,點擊內容跳轉到to屬性對應的路徑便可:
// 這是Link組件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
export default class Link extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
return <a onClick={() => {
state.history.push(this.props.to);
}}>{this.props.children}</a>
}}
</Consumer>
)
}
}
複製代碼
經過history.push便可實現點擊this.props.children的內容跳轉到Link組件的to屬性對應的路徑。
重定向就是匹配不到後直接跳轉到Redirect中的to路徑:
// 這是Redirect.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
export default class Redirect extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
// 重定向就是匹配不到後直接跳轉到redirect中的to路徑
state.history.push(this.props.to);
return null;
}}
</Consumer>
)
}
}
複製代碼
Switch組件的做用就是隻匹配第一個匹配到的組件:
// 這是Switch.js的文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
import pathToRegExp from 'path-to-regexp';
// Switch的做用就是匹配一個組件
export default class Switch extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
{
let pathname = state.location.pathname;
// 取出Switch包含的組件
let children = this.props.children;
for ( var i = 0; i < children.length; i++) {
let child = children[i];
// Redirect組件可能沒有path屬性
let path = child.props.path || '';
pathToRegExp(path, [], {end: false});
// switch匹配成功了
if (reg.test(pathname)) {
// 將匹配到的組件返回便可
return child;
}
}
return null;
}
}}
</Consumer>
)
}
}
複製代碼
經過遍歷this.props.children來進行逐一匹配,若是匹配到相應的路徑,當即返回對應的組件,若是匹配不到,則返回空。
經過上面的例子,發現實現一個簡易版的react-router並非想象中那麼難。因爲時間關係,路由權限校驗,BrowserRouter,withRoute這些組件並無實現。下次有時間再好好分析思考一下如何實現:)
React 全新的 Context API —— qiqi105
重新的 Context API 看 React 應用設計模式 ——誠身
react-router@4.0 使用和源碼解析 ——夏爾先生