前前言
本文来自推啊前端团队 刘爽 同学,主要介绍一下 前端路由相关内容和解读一下React-Router源码。欢迎在评论区讨论哦!!!
什么是路由
通常会在网络工程里面听到路由这个词,前端工程化后路由的概念用语页面的跳转,浏览器监测到路由的变化在页面显示路由所对应的页面。在早期,路由的概念时根据URL的变更重新渲染页面布局和内容的过程,而且这个过程时由服务器端实现的。他所描述的是URL 和 函数之间的映射关系。
在web前端的单页面应用,路由描述的是URL 与 UI 之间的映射关系。这种映射关系显著的特点是URL的改变不会引起页面的刷新。
**
两种路由方式
在前面也说了web前端路由的特点,URL 改变的目的是为了更新UI,与此同时不能够刷新页面,想要更新页面视图UI,我们就需要监听 URL 的变化。所以在前端实现路由引擎需要注意两点
- URL 的改变不刷页面,
- 如何监听 URL 的改变
在前端领域有两种路由方式能够实现以上标准
hash 路由
hash 路由就是我们常说的锚点。即在URL后面添加#,# 号后面就是hash路由部分。可以通过监听事件监听hash路由的变化
window.addEventListener("hashchange", function(){
console.log("路由改变")
})
window.onhashchange = function(){
console.log("路由改变")
}
复制代码
history 路由
history对象表示,当前窗口用户的导航记录,该对象不会向外暴露用户访问过的URL,但是可以通过方法实现前进和后退。HTML5 添加了新方法 pushState和replaceState,表示添加和替换历史记录的条目,语法如下。
history.pushState(state, title[, url])
history.replaceState(state, title[, url])
- state: 一个于指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数中。如果不需要这个对象,此处可以填null
- title: 当前大多数浏览器都忽略此参数
- url: 新历史记录条目的URL由此参数指定,新网址必须与当前网址同源
复制代码
通过popstate监听路由改变
window.addEventListener('popstate',function(){
console.log("路由改变")
})
复制代码
React-Router
这一栏我们只讨论源码,对应的版本是5.2
history:负责浏览器页面,链接改变通知当前页面location对象发生了改变,开发者根据变化渲染内容。
Router:负责监听页面对象发生了改变,并开始重新渲染页面
Route:页面开始渲染后,根据具体的页面location信息展示具体路由地址对应的内容。
BrowserRouter 和 Router
最开始使用 react-router 的时候需要的方式如下:
import { createBrowserHistory } from 'history'
import { Router } from 'react-router'
const BrowserRouter = React.cloneElement(Router, { history: createBrowserHistory() })
export default () => (
<BrowserRouter>
...
</BrowserRouter>
)
// 或者
export default () => (
<Router history={createBrowserHistory()}>
...
</Router>
)
复制代码
react-router V4 版本之后可以直接使用 BrowserRouter 如下所示
import { BrowserRouter } from 'react-router-dom'
export default () => (
<BrowserRouter>
...
</BrowserRouter>
)
复制代码
BrowserRouter 相关源码如下:
可以看到 BrowserRouter 就是对 Router 组件的一层封装,传入history 属性。其主要部分还是Router的源码
// packages/react-router-dom/modules/BrowserRouter.js
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
复制代码
// packages/react-router/modules/Router.js
class Router extends React.Component {
static computeRootMatch(pathname) {
return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
}
constructor(props) {
super(props);
this.state = {
// BrowserRouter 传入的 history
location: props.history.location
};
if (!props.staticContext) {
// 监听URL是否改变
this.unlisten = props.history.listen(location => {
// 这里的内容不影响整体逻辑
});
}
}
// 使用上下文把history location 等信息传入到children
render() {
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
}
复制代码
**总结:**根据源码可以看到 Router 处理的内容并不多,1. 定义一个上下文把相关信息传入children;2. 监听URL的变化改变当前的state。
Route
Route 的源码也十分简单,根据传入的信息匹配要显示的页面,如下所示
// packages/react-router/modules/Route.js
class Route extends React.Component {
// Consumer 上下文,用于接手上文传入信息
render() {
return (
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Route> outside a <Router>");
// location 信息 如果用户传入使用用户传入信息,否则使用Provider 传入
const location = this.props.location || context.location;
// 核心内容:根据URL路径匹配
// match: {path,url,isExact,params}
// 如果没有匹配到 就是上文的 computeRootMatch 内容
const match = this.props.computedMatch
? this.props.computedMatch // <Switch> already computed the match for us
: this.props.path
? matchPath(location.pathname, this.props)
: context.match;
// 这里略过了一些判断...
let { children, component, render } = this.props;
// 开始渲染 children, 一共有三种渲染的方式
// 1. <Route exact path="/" component={Home} />
// 2. <Route exact path="/" render={props=>{return <Home />}} />
// 3. <Route exact path="/"><Home /> </Route>
return (
<RouterContext.Provider value={props}>
{props.match
? children
? typeof children === "function"
? children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? children(props)
: null}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}
复制代码
**总结:**Route 就是用来渲染组件的,根据匹配的URL 即 Route 的path 属性,匹配出需要渲染的内容。在渲染时有三种方式 render
component
children
。在里面使用三目运算,写了很长,其实并不难理解。先判断有没有children,在判断chidren 是不是一个函数,这是渲染children的逻辑,如果没有children 判断有没有component ,最后判断有没有render方法。
Switch
如果 Route 组件被 Switch 包裹,那么匹配到的URL 会返回 被包裹的Route 的第一个匹配到的元素。核心代码如下:
// packages/react-router/modules/Switch.js
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
return match
? React.cloneElement(element, { location, computedMatch: match })
: null;
复制代码
**总结:**Switch 里面的内容并不多,就是把包裹的Route做了一个遍历,返回第一个匹配到的内容
总结:
整体来讲react-router 的源码并不是很难理解,都是一些简单的判断。相对于这一篇内容,对于react-router的如何使用并没有介绍,只是对源码做一个简单的解读。下一篇就实现一个简易版react-router。实现前文所介绍的api。