基于history实现路由里的Router

186 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情

前言

在之前的篇章中我们讲述了react-router的核心history库,而在《 来,手写一个简单的路由(一)》和《 来,手写一个简单的路由(二)》中我们通过事件和setState去动态更新组件。

在本篇章中我们将基于history库以及react-router-dom v5之前的版本,实现路由组件的封。

react-router-dom封装

在react-router-dom里,其主要是对react-router库进一步封装,而react-router的核心则是依赖history库。

我们新建一个react-router-dom,模拟该库的实现,引入之前封装的history,实现自己的BrowserRouter

// BrowserRouter组件
import React, { Component } from 'react'
import { createBrowserHistory } from "./history"

// 待完成...
import { Router } from "../react-router" 

export default class BrowserRouter extends Component {
    history = createBrowserHistory(this.props)
    render() {
        return <Router history={this.history}>
            {this.props.children}
        </Router>
    }
}

此处的代码便是我们在react-router-dom里导出的BrowserRouter代码,这里我们主要是通过创建history实例,将history实例通过props传给react-router导出的Router组件。而createBrowserHistory接收的参数便是我们在配置路由文件时传入的props。

react-router里的Router

Router组件主要的作用是通过上下文往其子组件注入上下文信息,其中上下文数据包括history、location、match信息。

import React, { Component,createContext } from 'react'
import PropTypes from "prop-types";
const ctx = createContext();

export default class Router extends Component {

    static propTypes = {
        history: PropTypes.object.isRequired,
        children: PropTypes.node
    }

    state = {
        location: this.props.history.location
    }
    
    ctxValue = {}

    componentDidMount() {
        this.unListen = this.props.history.listen((location, action) => {
            this.props.history.action = action;
            this.setState({
                location
            })
        })
    }

    componentWillUnmount() {
        this.unListen();//取消监听
    }

    render() {
        console.log(this.props.children)
        this.ctxValue.history = this.props.history;
        this.ctxValue.location = this.state.location;
        this.ctxValue.match = this.state.location.pathname;
        return <ctx.Provider value={this.ctxValue}>
            {this.props.children}
        </ctx.Provider>
    }
}

在上面的代码中,我们主要通过整合props传过来的数据,处理成上下文对象,然后注入到上下文的生产者里,供后代组件使用。

在路由整个声明周期里,我们通过history进行监听的开始与销毁。

为了使得match的处理和路由返回的目标数据结构一致,我们可以封装方法对其进行匹配处理。

在react-router内部,match的处理主要依赖path-to-regexp这个库:

import {pathToRegexp} from "path-to-regexp";

export default function matchPath(path, pathname, options) {
    const keys = [];
    const regExp = pathToRegexp(path, keys, getOptions(options));
    const result = regExp.exec(pathname);
    if (!result) {
        return null;
    }
    let groups = Array.from(result);
    groups = groups.slice(1);
    const params = getParams(groups, keys);
    return {
        isExact: pathname === result[0],
        params,
        path,
        url: result[0]
    };
}

function getOptions(options = {}) {
    const defaultOptions = {
        exact: false,
        sensitive: false,
        strict: false
    }
    const opts = { ...defaultOptions, ...options };
    return {
        sensitive: opts.sensitive,
        strict: opts.strict,
        end: opts.exact
    }
}

function getParams(groups, keys) {
    const obj = {};
    for (let i = 0; i < groups.length; i++) {
        const value = groups[i];
        const name = keys[i].name;
        obj[name] = value;
    }
    return obj;
}

在《关于path-to-regexp的那点事》里,我们讲解了该库的基本使用,而其库正式react-router里上下文对象的match方法实现的核心。

基于封装的match方法,改造Router提供的上下文数据:

import matchPath from "./matchPath"

...
this.ctxValue.match = matchPath("/", this.state.location.pathname);
...

为此我们通过封装react-router-dom和react-router,实现了简单的Router组件,其主要是往后代组件提供上下文数据。核心处理是通过事件监听触发setState改变状态,重新执行组件。

// 与之前通过自定义事件监听触发的setState类似。
componentDidMount() { 
    this.unListen = this.props.history.listen((location, action) => { 
        this.props.history.action = action; 
        this.setState({ location }) 
    }) 
}

基本使用:

import React from "react";
import { BrowserRouter } from "./react-router-dom"

export default function App() {
  return <BrowserRouter>
    hello world
  </BrowserRouter>
}

小结

本篇章主要通过对react-router和react-router-dom的简单封装,实现Router组件,我们需要认识各个库的依赖关系,以及每个模块的作用。而Router的作用就是给后代组件提供上下文数据。