基于history实现路由里的Route

308 阅读3分钟

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

前言

在前面的篇章中,我们主要介绍了如何实现react路由里的Router组件,在《 基于history实现路由里的Router 》这个篇章里,我们了解到Router组件的主要功能是通过关联地址栏,给后代组件提供上下文数据。

本篇章我们将要实现一个Route组件,通过Route组件实现上下文数据的消费使用。

封装Route

通过Router组件提供的上下文,我们可以利用该上下文,实现子组件的动态渲染。

import React, { Component } from 'react'
import ctx from "./RouterContext";

// 之前篇章中通过path-to-regexp封装的地址匹配规则
import matchPath from "./matchPath";

export default class Route extends Component {
    // 封装:通过上下文动态匹配符合的子组件然后渲染
    renderChildren(ctx) {
        if (this.props.children !== undefined && this.props.children !== null) {
            if (typeof this.props.children === "function") {
                return this.props.children(ctx);
            }
            else {
                return this.props.children;
            }
        }
        
        if (!ctx.match) {
            return null;
        }
   
        if (typeof this.props.render === "function") {
            return this.props.render(ctx);
        }
        
        if (this.props.component) {
            const Component = this.props.component;
            return <Component {...ctx} />
        }
        return null;
    }

 
    matchRoute(location) {
        const { exact = false, strict = false, sensitive = false } = this.props;
        return matchPath(this.props.path || "/", location.pathname, {
            exact,
            strict,
            sensitive
        })
    }

    consumerFunc = (value) => {
        const ctxValue = {
            history: value.history,
            location: value.location,
            match: this.matchRoute(value.location)
        }
        return <ctx.Provider value={ctxValue}>
            {this.renderChildren(ctxValue)}
        </ctx.Provider>
    }

    render() {
        return <ctx.Consumer>
            {this.consumerFunc}
        </ctx.Consumer>
    }
}

在之前的篇章《 来,手写一个简单的路由(一) 》中,我们主要map去遍历符合的组件然后去render子组件,在上面的代码主要if判断去处理符合规则的子组件。

在Route组件里,我们新建上下文转发Router提供的上下文数据,然后继续传递给后代,而在Route组件里,同时也利用Router提供的上下文数据进行子组件的动态渲染。

在这里我们主要判断下列情况:

  • 组件的children是否有值:
    • 有值:
      • 是否是函数:
        • 是:调用函数并传递上下文数据
        • 不是:直接渲染
    • 没有值:
      • 返回null
  • 组件的render是否有值:
    • 有值并且是函数:
      • 调用render并传递上下文数据
  • 组件的component是否有值:
    • 有值:
      • 直接调用
    • 没有值:
      • 返回null

其中我们主要调用的matchPath就是在上一篇基于path-to-regexp库封装的方法。

至此,我们通过生产-消费模式实现了简单的路由模型。

测试

我们的Route组件封装在react-router中,react-router-dom主要是引入并导出,所以我们引用封装的react-router-dom:

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

export default function App() {
  return (
    <BrowserRouter>
      <Route path="/a" component={A}>
      </Route>
      <Route path="/b" component={B} />
      <Route component={Change} />
    </BrowserRouter>
  )
}

function A() {
  return <h1>A</h1>
}

function B() {
  return <h1>B</h1>
}

function Change({ history }) {
  return <div>
    <button onClick={() => {
      history.push("/a")
    }}>to A</button>
    <button onClick={() => {
      history.push("/b")
    }}>to B</button>
  </div>
}

image.png

点击跳转到/a页面:

image.png 点击跳转到/b页面:

image.png

小结

本篇章我们基于生产-消费模式,利用Router组件提供的上下文,封装Route组件,该组件主要是通过匹配规则动态渲染子组件,而匹配规则正是通过上下文数据提供的。

在react-router中封装Route组件,而Route组件利用path-to-regexp库封装的匹配方法实现目标数据。在react-router-dom中则对react-router提供的Route组件进行引入并导出。