一般项目中,react路由是通过react-router-dom库实现的,react-router + history = react-router-dom,本文先来实现react-router。
一、基本原理
路由组件打印props的结果如下:
路由基本原理:
Router路由器管理每一个路由条目Route。
Router和Route是通过context通信的。在Router中创建RouterContext对象,提供value属性值为{history, location, match}。当我们去访问页面时,拿到location中的pathname属性和Route的path属性进行匹配并渲染。
二、实现思路
1. 实现react-router
react-router最终暴露一个具有
history属性的Router组件,history属性的值是history库里提供createHashHistory()方法,执行这个方法获得hashHistory的实例对象,作为history的值传入Router组件。
Router组件包裹所有Route并通过context通信。因为路由组件接收的props包含{history, location, match},因此我们在Router中创建context对象,通过Provider提供value属性,值就是{history, location, match}。
history: props.history // react-router中传递给Router组件的history对象
location: props.history.location // 浏览器历史对象上存放的最新的路径
match: // 后续文章中实现
2. 实现Route
Route需要做的事情是:拿自己的path和Provider提供的location.pathname相比,如果相同就渲染对应的组件。
2-1. 获取pathname
因为Router和Route是通过context通信,因此我们从context上可以直接拿到location对象,也就可以获取pathname属性了。
2-2. 获取path
我们在Route组件内部打印
props,就获取到我们在使用时给它传的path和component属性。
2-3. 渲染react元素
拿到pathname和path后比较是否相同,如果相同就渲染path相对应的component。
此时浏览器地址栏输入url,刷新后就已经可以渲染对应的组件。
但是页面不会自动刷新,为什么呢?因为我们还没有对url进行监听。
3. 实现监听
在Router中,给history添加listen监听事件(history内置对象可以拦截listen事件并执行),此事件的入参为地址栏上的新的url,因此我们可以直接把组件内的location的值,更新为这个入参即可。
三、代码实现
1. src/index.js
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";
ReactDOM.render(
<Router>
<div>
{/* <Route path="/" component={Home} /> */}
<Route path="/" component={Home} exact />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile} />
</div>
</Router>,
document.getElementById("root")
);
2. src/components
2-1. components/Home.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<div>
<p>Home</p>
</div>
)
}
}
2-2. components/User.js
import React, { Component } from 'react'
export default class User extends Component {
render() {
return (
<div>
<p>User</p>
</div>
)
}
}
2-3. component/profile.js
import React, { Component } from "react";
export default class Profile extends Component {
render() {
console.log(this.props);
return (
<div>
<p>Profile</p>
</div>
);
}
}
3. src/react-router-dom
3-1. react-router-dom/index.js
export * from "../react-router";
export { default as HashRouter } from "./HashRouter";
export { default as BrowserRouter } from "./BrowserRouter";
3-2. react-router-dom/HashRouter.js
import React, { Component } from "react";
import { Router } from "../react-router";
import { createHashHistory } from "history";
export default class HashRouter extends Component {
history = createHashHistory();
render() {
return <Router history={this.history}>{this.props.children}</Router>;
}
}
3-3. react-router-dom/BrowserRouter.js
import React, { Component } from "react";
import { Router } from "../react-router";
import { createBrowserRouter } from "history";
export default class BrowserRouter extends Component {
history = createBrowserRouter();
render() {
return <Router history={this.history}>{this.props.children}</Router>;
}
}
4. src/react-router
4-1. react-router/index.js
export { default as Router } from "./Router";
export { default as Route } from "./Route";
export { default as RouterContext } from "./RouterContext";
4-2. react-router/RouterContext.js
import React from "react";
export default React.createContext({});
4-3. react-router/Router.js
import React from "react";
import RouterContext from "./RouterContext";
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
// 历史对象上有最新的路径
location: props.history.location,
};
// 监听到url发生变化
props.history.listen((location) => {
this.setState({ location })
})
}
render() {
let value = {
history: this.props.history,
location: this.state.location,
};
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
);
}
}
export default Router;
4-4. react-router/Route.js
import React, { Component } from "react";
import RouterContext from "./RouterContext";
export default class Route extends Component {
static contextType = RouterContext;
render() {
console.log(this.props);
// 每个路由条目都要拿Provider提供的location.pathname和自己的path相比
const { history, location } = this.context;
const { path, component: RouteComponent } = this.props;
const match = location.pathname === path;
const routeProps = { history, location };
// 要渲染的react元素
let element = null;
if (match) {
element = <RouteComponent {...routeProps} />;
}
return element;
}
}
四、总结
- 入口文件中引入Router路由容器和Route路由条目组件:Router中调用
createHashHistory方法获取history对象,通过属性传入Router。 - Router文件中引入
context,内部构建value对象:- history: 第一步中传过来的
- location: 默认是浏览器的history对象提供的location
创建
context后的RouterContext组件,并传入value对象并暴露组件。渲染Route条目子组件。
- Route文件中,进行路径的匹配渲染相应的组件。