你需要了解的React Router(hash模式)实现原理

12,870 阅读4分钟

你应该知道的单页应用

单页应用是什么?

单页Web应用(single page web application,SPA),就是只有一张Web页面的应用。是只加载单个HTML页面,并在用户与应用程序交互时动态更新该页面的Web应用程序。而ReactVue就是构建单页应用的前端主流框架。

单页应用和多页应用的对比

对比 单页面应用 多页面应用
组成 一个外壳页面和多个页面组件(片段)构成 多个完整的页面组成
资源(css,js) 可共用,只需要在外壳部分加载 需要时向服务器请求,资源独立
首次加载 首屏加载慢 每次加载区别不大
用户体验 用户体验好,内容改变不需要重新加载整个页面,后续服务器压力小 页面加载慢,每次加载需要对服务器进行请求
转场动画 可以实现 无法实现
搜索引擎优化(SEO) 效果较差,可通过SSR服务端渲染,成本较高 效果好

SPA单页应用的原理(路由的作用)

通过监听路由的变化,去匹配路由所对应的组件,并将组件映射到路由上。当路由改变时框架能有效地更新并正确地渲染组件。

你需要了解的HashRouter实现过程

这里我介绍HashRouter的实现原理、过程

路由的三大构成

React Router中有三类组件:

  1. Router(包括HashRouter与BrowserRouter)

对应路由的两种模式hash和history

  1. route matching组件(Route)

控制路径对应的显示组件

  1. navigation组件(Link)

路由切换,跳转

手动实现过程

以一个demo为例

export default class App extends Component {
  render() {
    return (
      <HashRouter>
        <Route path="/home" component={Home}></Route>
        <Route path="/user" component={User}></Route>
      </HashRouter>
    )
  }
}
ReactDOM.render(<App />,
  document.getElementById('root')
);

这里Route组件获得了对应的路径和所要展示的组件,并嵌套在Router组件中。

HashRouter组件

首先我们要知道BOM的一些特性

BOM是一套操作浏览器的API,而window是BOM中的一个顶级的对象,我们可以通过this.props打印挂载在window下的一些信息,以掘金为例

样例

而HashRouter的实现就依赖于这些Api,我们可以通过window.location.href拿到我们所在的url值。

当然通过这个就可以了吗?等等,Route作为HashRouter的嵌套组件是怎么拿到url路径去匹配path的呢?

这里React-Router采用了React16.3版本提出的api React.createContext

Context通过组件树提供一个传递数据的方法

可以解决父组件向子组件、孙子孙子...传值,为多组件嵌套数据传递提供解决方案。

//我们通过一个context.js方法引入这个api
import React from 'react';
let { Provider, Consumer } = React.createContext()
export { Provider, Consumer }

而HashRouter便充当了这个生产者的角色,通过window.addEventListener('hashChange',callback)监听hash值的变化,并传递给其嵌套的组件。

具体代码如下:

import React, { Component } from 'react';
import { Provider } from './context'
// 该组件下Api提供给子组件使用
class HashRouter extends Component {
  constructor() {
    super()
    this.state = {
      location: {
        pathname: window.location.hash.slice(1) || '/'
      }
    }
  }
  // url路径变化 改变location
  componentDidMount() {
    window.location.hash = window.location.hash || '/'
    window.addEventListener('hashchange', () => {
      this.setState({
        location: {
          ...this.state.location,
          pathname: window.location.hash.slice(1) || '/'
        }
      }, () => console.log(this.state.location))
    })
  }
  render() {
    let value = {
      location: this.state.location
    }
    return (
      <Provider value={value}>
        {
          this.props.children
        }
      </Provider>
    );
  }
}

export default HashRouter;

Route组件

Route组件则充当的消费者的角色,通过一个回调接收到HashRouter传递过来的url路径值,并进行后面的匹配渲染组件

import React, { Component } from 'react';
import { Consumer } from './context'
const { pathToRegexp } = require("path-to-regexp");
class Route extends Component {
  render() {
    return (
      <Consumer>
        {
          state => {
            console.log(state)
            let {path, component: Component} = this.props
            let pathname = state.location.pathname
            let reg = pathToRegexp(path, [], {end: false})
            // 判断当前path是否包含pathname
            if(pathname.match(reg)) {
              return <Component></Component>
            }
            return null
          }
        }
      </Consumer>
    );
  }
}
export default Route;

由于官方实现的正则表达式较为复杂,这里我借助了path-to-regexp这个插件去进行正则匹配的处理。

实现效果:

总结

本次只是简单的模拟了下HashRouter的实现过程,对React-Router的实现原理也有了一定的认识。最后,学习的过程,重在总结,乐在分享,具体的代码大家可以看我的github 欢迎大家留言和我交流分享😀。