持续创作,加速成长!这是我参与「掘金日新计划 · 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页面:
/b页面:
继续添加路由组件:
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>
)
}
在/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页面:
进入/b页面:
可见通过在for循环里return出符合项即能够实现组件的单一匹配。
小结
Switch组件的核心在于接收上下文数据,通过循环待渲染的组件,当匹配到符合项时直接中止循环并返回符合项给后续操作。
至此,我们通过history的监听并更改状态触发路由组件重新执行,通过Route组件提供上下文,Router使用上下文对象并承载待渲染组件项和匹配规则,Switch组件匹配第一个符合项,实现了一个基本的react-router-dom。