基于history实现路由里的Switch

119 阅读2分钟

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

前言

在之前的篇章中我们实现了路由里的Router、Route组件,详情点击进入:《 基于history实现路由里的Route 》、 《 基于history实现路由里的Router 》。

其中我们通过history的监听实现路由组件重新匹配渲染,不过如题,缺少Switch组件下的匹配规则,并不能实现单一匹配,在匹配到相应组件之后仍然继续往下匹配,所以本篇章我们主要通过实现Switch组件去干预匹配规则,实现单一匹配。

缺陷

如下代码,我们通过引入前面封装的代码,配置下列路由组件:

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

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

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

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

我们进入/a页面:

image.png /b页面:

image.png

继续添加路由组件:

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

image.png

在/a页面下我们出现这种情况主要是因为在执行匹配规则时我们是通过if判断往下执行,我们并没有实现匹配到对应组件后中止匹配操作。

实现Switch组件

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

export default class Switch extends Component {
    // 循环children,得到第一个匹配的Route组件,若没有匹配,则返回null
    getMatchChild = ({ location }) => {
        let children = [];
        if (Array.isArray(this.props.children)) {
            children = this.props.children;
        }else if (typeof this.props.children === "object") {
            children = [this.props.children];
        }
        for (const child of children) {
            if (child.type !== Route) {
                throw new TypeError("子元素不是Route组件");
            }
            const { path = "/", exact = false, strict = false, sensitive = false } = child.props;
            const result = matchPath(path, location.pathname, { exact, strict, sensitive });

            if (result) {
                return child;
            }
        }
        return null;
    }

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

在上面的代码中,我们实现的Switch主要接收上下文数据并且返回一个children。核心在于遍历children队列时,当匹配到符合项时直接return出当前项,从而避免后续再次匹配到符合项。

此处循环遍历的处理便是Switch的核心逻辑:

 for (const child of children) {
    if (child.type !== Route) {
        throw new TypeError("子元素不是Route组件");
    }
    const { path = "/", exact = false, strict = false, sensitive = false } = child.props;
    const result = matchPath(path, location.pathname, { exact, strict, sensitive });

    if (result) {
        return child;
    }
 }

测试

执行下列代码:

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

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

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

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

进入/a页面:

image.png

进入/b页面:

image.png

可见通过在for循环里return出符合项即能够实现组件的单一匹配。

小结

Switch组件的核心在于接收上下文数据,通过循环待渲染的组件,当匹配到符合项时直接中止循环并返回符合项给后续操作。

至此,我们通过history的监听并更改状态触发路由组件重新执行,通过Route组件提供上下文,Router使用上下文对象并承载待渲染组件项和匹配规则,Switch组件匹配第一个符合项,实现了一个基本的react-router-dom。