在剛入行的時候一直明白什麼單頁面應用是什麼,說白了就是混淆了前臺路由和後臺路由,如今來縷縷它們:html
若是還不理解,那麼能夠用express搭建本地服務器看看效果(ps:爲何用express,由於懶,koa的話還得下載koa-router插件):前端
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('welcome to home'); }) app.get('/a', function (req, res) { res.send('welcome to a'); }) var server = app.listen(8081) 複製代碼
在瀏覽器中輸入localhost:8081/ react
在瀏覽器中輸入localhost:8081/#a 在瀏覽器中輸入localhost:8081/a 結合圖片和上面的陳述應該知道前端路由和後臺路由的區別,除了hash路由還有一種方法能夠修改url而且不向後臺發送請求,它是history.pushState(),注意兼容處理: 可是這種方法有一個問題,若是再按一次回車鍵,它是會向後臺發送請求的,若是後臺路由沒有相應的匹配,那麼會報404的錯誤,通常須要後臺作處理。var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('welcome to home'); }) var server = app.listen(8081) 複製代碼
主要是監聽hashchange事件,而後再獲取數據從新渲染頁面正則表達式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <a href="#/a">pageALink</a> <a href="#/b">pageBLink</a> <span id='body'></span> <script> window.addEventListener('hashchange',(e)=>{ document.getElementById('body').innerHTML = window.location },false) </script> </body> </html> 複製代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <a onClick="go('/a')">pageALink</a> <a onClick="go('/b')">pageBLink</a> <span id='body'></span> <script> function go (pathname){ window.history.pushState({},null,pathname); document.getElementById('body').innerHTML = window.location; } //pushState和replaceState是沒法觸發popstate事件 //這裏主要處理瀏覽器前進後退功能,不加下面的代碼就沒法實現前進後退功能 window.addEventListener('popstate',(e)=>{ let pathname = window.location; document.getElementById('body').innerHTML = window.location; }) </script> </body> </html> 複製代碼
import BrowserRouter from './BrowserRouter'; import Route from './Route'; import Link from './Link'; import Switch from './Switch'; import Redirect from './Redirect'; export { BrowserRouter, Route, Link, Switch, Redirect } 複製代碼
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import {BrowserRouter as Router,Route} from './react-router-dom' render(<Router> <Render/> <div> <Route path="/" component={Home}> <Route path="/user" component={User}> </div> </Router>,window.root); 複製代碼
從上面的用法,能夠知道BrowserRouter實際上是一個組件,它有如下功能:express
import React from 'react'; import {Provider} from './context'; // // 想染路徑變化 刷新組件 路徑定義在狀態中 路徑變化就更新狀態 export default class BrowserRouter extends React.Component{ state = { // 獲取打開網頁時的默認路徑 location:{ pathname: window.location.pathname || '/', } } componentWillMount(){ window.addEventListener('popstate',()=>{ let pathname = window.location.pathname; this.handleChangeState(pathname); },false); } //當瀏覽器的路由改變時觸發,改變state從而從新渲染組件 handleChangeState(pathname){ this.setState({ location:{ ...this.state.location, pathname } }) } // 渲染Route, render(){ let that = this; let value = { ...this.state, history:{ push(pathname){ // 這個方法主要是提供給Link使用的 // 當點擊Link時,會改變瀏覽器url而且從新渲染組件 window.history.pushState({},null,pathname); that.handleChangeState(pathname); } } } return( <Provider value={value}> {this.props.children} //嵌入的Route組件 </Provider> ) } } 複製代碼
Route主要將所表明組件的path和當前的url(state.pathname)進行匹配,若是匹配成功則返回其表明的組件,那麼就會渲染其表明的組件,不然返回null。redux
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import {BrowserRouter as Router,Route} from './react-router-dom' render(<Router> <Link to="/">首頁 </Link> /*因爲Link不會有點擊後的樣式變化,因此一般使用下面這用方法自定義link*/ <Route path="/user" children={(match)=>{ return <li><a className={match?'active':''}>用戶</a></li>} } <Render/> <div> <Route path="/" component={Home}> <Route path="/user" component={User}> /*採用render參數會執行對應的函數*/ <Route path="/user" render={(props)=>{ return <user/> }}/> </div> </Router>,window.root); 複製代碼
import React from 'react'; import {Consumer} from './context'; // 路徑轉化成正則,在另外一篇文章【koa會用也會寫——(koa-router)】能夠找到其原理 import pathToRegExp from 'path-to-regexp'; // 不是經過Route渲染出來的組件沒有match、location、history三個屬性 export default class Route extends React.Component{ render(){ return <Consumer> {(value)=>{ // BrowserRouter中state.pathname和瀏覽器url一致 let {pathname} = value.location; // Route組件上的參數 let {path='/',component:Component,render,children,exact=false} = this.props; //用來保存匹配路徑的參數鍵值 /user/:name/:id => [name,id] let keys = []; //將Route的path參數轉化爲正則表達式 let reg = pathToRegExp(path,keys,{end:exact}); if(reg.test(pathname)){ let result = pathname.match(reg); let match = {} // 將獲取路徑參數exp:{id:xxx,name:xxx} if(result){ let [,...arr] = result; match.params = keys.reduce((memo,next,idx)=>{ memo[keys[idx].name]=arr[idx] return memo; },{}); } // 將匹配路徑的參數和原來的參數合併傳給Route表明的組件 let props = { ...value,match } // component直接渲染組件 // render執行render(props) // children不論是否匹配都會執行children(props) if(Component){ return <Component {...props}></Component> }else if(render){ return render(props); }else if(children){ return render(props); } }else{ // children 不論是否匹配到都會 if(children){ return render(props); } return null //Route的路徑不匹配返回null,不渲染Route表明的組件 } }} </Consumer> } } 複製代碼
Switch組件其實就是包裝在Route外面的一層組件,它會對Route進行篩選後返回惟一Route,若是 沒有Switch的話,能夠渲染多個Route表明的組件瀏覽器
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import Article from './components/Article'; import {BrowserRouter as Router,Route,Switch} from './react-router-dom' render(<Router> <Switch> <Route path="/" exact={true} component={Home}></Route> <Route path="/user" exact={true} component={User}></Route> <Route path="/article/:id" component={Article}/> </Switch> </Router>,window.root); 複製代碼
import React from 'react'; import {Consumer} from './context'; import pathToRegExp from 'path-to-regexp'; export default class Switch extends React.Component{ render(){ return <Consumer> {(value)=>{ // BrowserRouter中state.pathname和瀏覽器url一致 let pathname = value.location.pathname; // 將Route的path對url進行匹配,匹配成功返回惟一的Route React.Children.forEach(this.props.children,(child)=>{ let {path='/',exact=false} = child.props; let reg = pathToRegExp(path,[],{end:exact}); if(reg.test(pathname)){ return child } }) }} </Consumer> } } 複製代碼
對於沒有匹配到的Route會默認重定向渲染Redirect,其實就是直接改變url和BrowserRouter中state.pathname致使從新渲染組件bash
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import Article from './components/Article'; import {BrowserRouter as Router,Route,Link,Switch,Redirect} from './react-router-dom' render(<Router> <Switch> <Route path="/" exact={true} component={Home}></Route> <Route path="/user" exact={true} component={User}></Route> <Route path="/article/:id" component={Article}/> <Redirect to="/"/> </Switch> </Router>,window.root); 複製代碼
import React from 'react'; import {Consumer} from './context'; export default class Redirect extends React.Component{ render(){ return <Consumer> {({history})=>{ //修改url,從新渲染組件 history.push(this.props.to); return null }} </Consumer> } } 複製代碼
和Redirect組件相似,區別在於Redirect直接調用context上面的方法修改url,而Link須要點擊觸發調用context上面的方法服務器
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import Article from './components/Article'; import {BrowserRouter as Router,Route,Link,Switch,Redirect} from './react-router-dom' render(<Router> <Link to="/">首頁 </Link> <Link to="/user">用戶</Link> <Switch> <Route path="/" exact={true} component={Home}></Route> <Route path="/user" exact={true} component={User}></Route> <Route path="/article/:id" component={Article}/> <Redirect to="/"/> </Switch> </Router>,window.root); 複製代碼
import React from 'react'; import {Consumer} from './context'; export default class Link extends React.Component{ render(){ return <Consumer> {({history})=>{ //點擊觸發回調用,修改url,從新渲染組件 return <a onClick={()=>{ history.push(this.props.to) }}>{this.props.children}</a> }} </Consumer> } } 複製代碼
不是經過Route渲染出來的組件沒有match、location、history三個屬性,可是又想要使用這三個屬性,那該怎麼辦呢,因此能夠在外面套一層Route組件,從而獲得這三個屬性,這種作法叫高階組件。markdown
import React from 'react'; import Route from './Route' let withRouter = (Component) =>{ return ()=>{ return <Route component={Component}></Route> } } export default withRouter; 複製代碼
import React, { Component } from 'react'; import {withRouter} from 'react-router-dom'; class withRouterLink extends Component { change = ()=>{ this.props.history.push('/withRouterLink') // url變化,組件的跳轉 } render() { return ( <div className="navbar-brand" onClick={this.change}>withRouter</div> ) } } // 高階組件 export default withRouter(Logo) 複製代碼
import React from 'react'; import ReactDOM,{render} from 'react-dom'; import Home from './components/Home.js'; import User from './components/User.js'; import Article from './components/Article.js'; import withRouterLink from './components/withRouterLink.js'; import {BrowserRouter as Router,Route,Link,Switch,Redirect} from './react-router-dom' render(<Router> <Link to="/">首頁 </Link> <Link to="/user">用戶</Link> <withRouterLink></withRouterLink> <Switch> <Route path="/" exact={true} component={Home}></Route> <Route path="/user" exact={true} component={User}></Route> <Route path="/article/:id" component={Article}/> </Switch> </Router>,window.root); 複製代碼
通常網頁都會有登錄註冊功能,若是沒有登錄,不少頁面是訪問受限的,登錄以後又會跳轉到原頁面。
import Index from './pages/index.js'; import Protected from './pages/Protected' export default class App extends Component { render() { return ( <Router> <Index> <Switch> <Route path="/home" exact={true} component={Home}/> <Protected path="/profile" component={Profile}/> <Route path="/login" component={Login}/> <Redirect to="/home"/> </Switch> </Index> </Router> ) } } 複製代碼
import React, { Component } from 'react' import {Route,Redirect} from 'react-router-dom' export default class Protected extends Component { render() { let login = localStorage.getItem('login'); // this.props裏面有 path 有component //若是用戶沒有登陸重定向到登陸頁 return login?<Route {...this.props}></Route>:<Redirect to={{pathname:"/login",state:{"from":'/profile'}}}/> } } 複製代碼
import React, { Component } from 'react' export default class Login extends Component { render() { console.log(this.props) return ( <div> <button onClick={()=>{ // 經過參數識別 跳轉是否正確 localStorage.setItem('login','ok'); //拿到profile頁面跳轉到login頁面傳的from if(this.props.location.state){ this.props.history.push(this.props.location.state.from); }else{ this.props.history.push('/'); } }} className="btn btn-danger">登陸</button> <button onClick={()=>{ localStorage.clear('login'); }} className="btn btn-danger">退出</button> </div> ) } } 複製代碼
我的使用一種框架時總有一種想知道爲啥這樣用的強迫症,否則用框架用的不舒服,不要求從源碼上知道其原理,可是必須得從心理上說服本身。