React路由之BrowserRouter实现原理
一、路由用法
1.安装路由库
npm i react-router-dom
2.引入
import React from "react";
import ReactDOM from "react-dom";
import {HashRouter as Router, Route} from 'react-router-dom' //路由库
import Home from './components/Home'
import User from './components/User'
import Profile from './components/Profile'
/**
* Router是路由容器
* Route是路由规则,一个Route代表一个路由规则
* path 代表路径 component代表要渲染的组件
*/
ReactDOM.render(
<Router>
<Route path="/" component={Home}></Route>
<Route path="/user" component={User}></Route>
<Route path="/profile" component={Profile}></Route>
</Router>,document.getElementById('root')
)
Home组件 Home.js
import React from "react";
export default function(props) {
console.log(props)
return (
<div>Home</div>
)
}
Profile组件 Profile.js
import React from "react";
export default function(props) {
return (
<div>Profile</div>
)
}
User组件 User.js
import React from "react";
export default function(props) {
return (
<div>User</div>
)
}
根据路径渲染组件component
可以看到不管是渲染哪个组件,path=“/”的这个路径对应的组件都会渲染出来,这是因为react在渲染的时候会匹配只要是/结尾的,都会渲染出来,要想过滤掉这个home,可以添加一个exact,这个时候在渲染别的组件的时候就不会再渲染了。
ReactDOM.render(
<Router>
<Route exact path="/" component={Home}></Route>
<Route path="/user" component={User}></Route>
<Route path="/profile" component={Profile}></Route>
</Router>,document.getElementById('root')
)
打印props属性:
- length表示当前的路由条目,只有前进的时候才会增加条目,后退并不会增加条目数
- action表示当前路径是通过push来的还是pop来的
- block表示阻止跳转,prompt表示弹出提示是否进行跳转。
- createHref,location对象转换成字符串或者将字符产转换成location对象。
- go 表示跳几步,goBack表示向后跳,goForward表示向前跳
- location 代表当前的路径对象
接下来实现一个自己的路由库。
React的路由库是借助window.history对象实现的,所以我们今天的实现是需要基于window.history来实现。
先来了解一下history:
history 有三种实现方式:
1、createBrowserHistory:用于支持 HTML5 历史记录 API 的现代 Web 浏览器(请参阅跨浏览器兼容性)
2、createHashHistory:用于旧版Web浏览器
3、createMemoryHistory:用于node环境下。
history对象的内部基础方法:
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
以上三种实现方法,都是在history内部方法的基础上进行了改写(覆盖)。
二、路由实现
1、createBrowserHistory
export default function createBrowserHistory(){
const globalHistory = window.history
const initialLocation = {
pathname:window.location.pathname,
state:globalHistory.state //历史对象上的状态
}
const history = {
length: globalHistory.length,
action: "POP", //当前路径是通过push、pop、replace哪个方法来的
location: initialLocation,
createHref, //通过location得到一个字符串
push, // 改变location 添加新条目
replace, //新路径替换当前条目
go,
goBack,
goForward,
block,
listen //监听路由变化
};
retrun history
}
整体代码是这样的,下面主要看看里面每个方法的实现
1.1createHref()
这个方法是根据当前history对象返回一个转换后的字符串
function createHref (location) {
return location.protocol + location.host + location.pathname + location.search + location.hash
}
1.2.push方法
用于路由跳转的方法
function setState (state) {
//Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(history, state) //把action,location覆盖到对应的history对象的值
history.length = globalHistory.length
}
function push (path,state) { //path新的路径 state 新的状态
const action = 'PUSH' //改变action的值
const location = {push, state} //新的对象
globalHistory.pushState(state,null,path) //调用history的pushState方法实现跳转
setState({action,location}) //setState这个setState并不是react自带的setState方法,
}
1.3.listen方法
监听路由的变化
function setState (state) {
//Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(history, state) //把action,location覆盖到对应的history对象的值
history.length = globalHistory.length
listeners.forEach(listener => listener())
}
var listeners= []
function listen (listener) {
listeners.push(listener)
}
1.4.replace 方法
替换当前路径
function replace (path,state) { //path新的路径 state 新的状态
const action = 'REPLACE' //改变action的值
const location = {push, state} //新的对象
globalHistory.replaceState(state,null,path) //调用history的replaceState方法实现跳转
setState({action,location}) //setState这个setState并不是react自带的setState方法,
}
function setState (state) {
//Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(history, state) //把action,location覆盖到对应的history对象的值
history.length = globalHistory.length
}
5.go() goBack() goForward()
go表示跳转几步
function go (n) {
globalHistory.go(n) //调用原始的go方法
}
function goBack () {
go(-1)
}
function goForward () {
go(1)
}
6.block
表示询问是否跳转
let isBlock //是否跳转
function block (prompt) {
isBlock = prompt
}
全部实现:
export default function createBrowserHistory(){
const globalHistory = window.history
const initialLocation = {
pathname:window.location.pathname,
state:globalHistory.state //历史对象上的状态
}
function createHref (location) {
return location.protocol + location.host + location.pathname + location.search + location.hash
}
function setState (state) {
//Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(history, state) //把action,location覆盖到对应的history对象的值
history.length = globalHistory.length
listeners.forEach(listener => listener())
}
var listeners= []
function listen (listener) {
listeners.push(listener)
}
function push (path,state) { //path新的路径 state 新的状态
const action = 'PUSH' //改变action的值
const location = {push, state} //新的对象
globalHistory.pushState(state,null,path) //调用history的pushState方法实现跳转
setState({action,location}) //setState这个setState并不是react自带的setState方法,
}
function replace (path,state) { //path新的路径 state 新的状态
const action = 'REPLACE' //改变action的值
const location = {push, state} //新的对象
globalHistory.replaceState(state,null,path) //调用history的replaceState方法实现跳转
setState({action,location}) //setState这个setState并不是react自带的setState方法,
}
function go (n) {
globalHistory.go(n) //调用原始的go方法
}
function goBack () {
go(-1)
}
function goForward () {
go(1)
}
let isBlock //是否跳转
function block (prompt) {
isBlock = prompt
}
const history = {
length: globalHistory.length,
action: "POP", //当前路径是通过push、pop、replace哪个方法来的
location: initialLocation,
createHref, //通过location得到一个字符串
push, // 改变location 添加新条目
replace, //新路径替换当前条目
go,
goBack,
goForward,
block,
listen //监听路由变化
};
}
总结:
react-router是在history基础上实现了URL与UI的同步,其中URL对应的是location,UI对应的是react component。
下篇我们实现一下HashRouter。