咱们使用React开发项目的时候,基本上是单页面应用,也就离不开路由。路由看似神秘,当咱们简单的模拟一下它的核心功能后,发现也就这么回事儿。本文就详细的介绍一下react-router-dom
的 HashRouter
的核心实现逻辑。react
本文实现的功能主要包含:git
HashRouter
Route
Link
MenuLink
Switch
Redirect
先不说代码是怎样写的,先上图,让你们看一下这个HashRouter
究竟是个什么东东:
好吧,确定有人会说这些圈圈又是什么东东呀,客官不要着急,待我慢慢解释:github
HashRouter
是一个大的容器,它控制着他本身到底渲染成什么样子,那么它是经过什么控制的呢,看它的名字就能猜出来,那就是window.location.hash
。HashRouter
开始渲染的时候就会拿它本身身上的pathname
属性跟它肚子里的Route
的path
进行匹配,匹配上的话,就会渲染Route
的component
对应的组件。Link
是怎样切换路由的呢,很简单,就是经过this.props.history.push(path)
来改变HashRouter
中的pathname
属性,进而驱动Route们
进行从新渲染,再次匹配咱们的路由,最终实现路由的切换。介绍了一下简单的逻辑,接下来咱们就看一下具体是怎样实现的吧,以下图:react-router
HashRouter
是一个继承了React.Component
的类,这个类上的state
包括location
,监听着hash
的变化以驱动Route
组件的从新渲染,另外还有一个history
属性,能够切换页面的路由。Route
、Link
、MenuLink
、Switch
、 Redirect
,其中Route
的是基础是核心,MenuLink
和某些有特定逻辑的渲染都是在Route
的基础上实现的。Route
组件上能够接收三种变量,包括component
、render
、children
,其中render
、children
是都是函数,render
是根据特定的逻辑渲染元素,children
是用来渲染MenuLink
,这两个函数都接收当前路由的props
,函数的返回值是要渲染的元素。Switch
实现的逻辑是,返回children
中跟hash
匹配到的第一个“孩子”。HashRouter
HashRouter
将window.loacation.hash
跟本身的state
挂钩,经过改变本身的state
驱动页面的从新渲染。dom
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class HashRouter extends Component { constructor() { super(); this.state = { location: { pathname: window.location.hash.slice(1) || '/', // 当前页面的hash值 state: {} //保存的状态 } }; } // 定义上下文的变量类型 static childContextTypes = { location: PropTypes.object, history: PropTypes.object } // 定义上下文的变量 getChildContext() { return { location: this.state.location, history: { push: (path) => { // 就是更新 window.hash值 if (typeof path === 'object') { let {pathname, state} = path; this.setState({ location: { ...this.state.location, state // {from: '/profile'} } }, () => { window.location.hash = pathname; }) } else { window.location.hash = path; } } } } } render() { return this.props.children; // 渲染页面元素 } componentDidMount() { window.location.hash = window.location.hash.slice(1) || '/'; // 监听window的hash的变化,驱动页面的从新刷新 window.addEventListener('hashchange', () => { this.setState({ location: { ...this.state.location, pathname: window.location.hash.slice(1) || '/' } }); }) } }
Route
Route
的渲染核心逻辑就是将本身的path
和当前页面的hash
进行匹配,匹配上了就渲染相应的元素,匹配不上就什么都不渲染。函数
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp' export default class Route extends Component { // 定义上下文context的类型 static contextTypes = { location: PropTypes.object, history: PropTypes.object } render() { // 解构传入Route的props let {path, component: Component, render, children} = this.props; // 解构上下文的属性 let {location, history} = this.context; let props = { location, history }; // 将传入Route的path和当前的hash进行匹配 let keys = []; let regexp = pathToRegexp(path, keys, {end: false}); keys = keys.map(key => key.name); let result = location.pathname.match(regexp); if (result) { // 匹配上了 let [url, ...values] = result; props.match = { path, url, params: keys.reduce((memo, key, index) => { // 获取匹配到的参数 memo[key] = values[index]; return memo; }, {}) }; if (Component) { // 普通的Route return <Component {...props} />; } else if (render) { // 特定逻辑的渲染 return render(props); } else if (children) { // MenuLink的渲染 return children(props); } else { return null; } } else { // 没有匹配上 if (children) { // MenuLink的渲染 return children(props); } else { return null; } } } }
Redirect
Redirect
就干了一件事,就是改变HashRouter
的state
,驱动从新渲染。this
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class Redirect extends Component { // 定义上下文context的Type static contextTypes = { history: PropTypes.object } componentDidMount() { // 跳转到目标路由 this.context.history.push(this.props.to); } render() { return null; } }
MenuLink
import React, {Component} from 'react'; import Route from "./Route"; import Link from './Link' export default ({to, children}) => { // 若是匹配到了,就给当前组件一个激活状态的className return <Route path={to} children={props => ( <li className={props.match ? "active" : ""}> <Link to={to}>{children}</Link> </li> ) }/> }
Link
Link
就是渲染成一个a标签,而后给一个点击事件,点击的时候更改HashRouter
的状态,驱动从新渲染。url
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class Link extends Component { static contextTypes = { history: PropTypes.object } render() { return ( <a onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a> ) } }
Switch
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp'; export default class Switch extends Component { static contextTypes = { location: PropTypes.object } render() { let {pathname} = this.context.location; let children = this.props.children; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; let path = child.props.path; if (pathToRegexp(path, [], {end: false}).test(pathname)) { // 将匹配到的第一个元素返回 return child; } } return null } }
好了,这几个功能介绍完了,你是否对HashRouter
的原理有所了解了呢?本文只是贴出部分代码,若是有须要请看demo能够手动体验一下哦。spa
参考文献:code