在剛入行的時候一直明白什麼單頁面應用是什麼,說白了就是混淆了前臺路由和後臺路由,如今來縷縷它們: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
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組件,從而獲得這三個屬性,這種作法叫高階組件。react-router
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>
)
}
}
複製代碼
我的使用一種框架時總有一種想知道爲啥這樣用的強迫症,否則用框架用的不舒服,不要求從源碼上知道其原理,可是必須得從心理上說服本身。