在刚入行的时候一直明白什么单页面应用是什么,说白了就是混淆了前台路由和后台路由,如今来缕缕它们: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>
)
}
}
复制代码
我的使用一种框架时总有一种想知道为啥这样用的强迫症,否则用框架用的不舒服,不要求从源码上知道其原理,可是必须得从心理上说服本身。