持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
前言
前面的文章我们基于history库实现了react路由里的Route、Router、Switch等组件。本篇章我们主要是实现react路由里的withRouter组件、Link组件和NavLink组件。
简介
withRouter组件作为HOC高阶组件,用于将路由上下文中的数据,作为属性注入到组件中。
Link组件则是通过改变地址栏跳转到指定页面。
NavLink组件作为Link组件的特别版本,除了具有Link组件的功能外,还会在匹配上当前的url的时候触发响应的方法,一般作为类名的触发处理。
实现withRouter
实现withRouter组件只需要把上下文中的数据传递给包装的组件,所以我们可以进行上下文的转发:
import React from "react"
import ctx from "./RouterContext"
export default function withRouter(Comp) {
function RouterWrapper(props) {
return <ctx.Consumer>
{value => <Comp {...value} {...props} />}
</ctx.Consumer>
}
// 设置组件在调试工具中显示的名字
RouterWrapper.displayName = `withRouter(${Comp.displayName || Comp.name})`;
return RouterWrapper;
}
通过上下文转发我们实现了简单的withRouter,给包装的组件设置标识方便后续调试。
测试withRouter
测试例子如下,我们验证withRouter包装后的组件props是否被注入路由信息:
import React from 'react'
import { BrowserRouter, Route, withRouter } from "./react-router-dom"
function Comp(props) {
console.log(props)
return <div>
{props.text}
</div>
}
const TestComp = withRouter(Comp);
function A() {
return <div>
<h1>A</h1>
<TestComp text="abc" />
</div>
}
export default function App() {
return (
<BrowserRouter>
<Route path="/a" component={A} />
</BrowserRouter>
)
}
通过控制台输出我们看到路由信息成功注入到被包装的组件中。
实现Link
Link组件的核心是调用上下文数据里的history,通过push方法去触发状态的更新,导致路由组件重新运行:
import React from 'react'
import ctx from "../react-router/RouterContext"
import { parsePath } from "history"
export default function Link(props) {
const { to, ...rest } = props;
return <ctx.Consumer>
{value => {
let location;
if (typeof props.to === "object") {
location = props.to;
}else {
location = parsePath(props.to);
}
const href = value.history.createHref(location);
return <a {...rest} href={href} onClick={e => {
e.preventDefault();
value.history.push(location);
}}>{props.children}</a>
}}
</ctx.Consumer>
}
history库提供一个parsePath的方法,用于解析地址返回location对象,Link组件的核心是通过使用上下文数据中的history进行事件触发,而跳转地址参数则是props传递过来。
实现NavLink
NavLink是基于Link的包装,除了具备Link组件的功能外,可以通知组件当前地址参数是否匹配:
import React from 'react'
import Link from "./Link"
import ctx from "../react-router/RouterContext"
import matchPath from "../react-router/matchPath"
import { parsePath } from "history"
export default function NavLink(props) {
const {
activeClass = "active",
exact = false,
strict = false,
sensitive = false,
...rest } = props
return (
<ctx.Consumer>
{({ location }) => {
let targetLocation;
if (typeof props.to === "string") {
targetLocation = parsePath(props.to);
}
const result = matchPath(targetLocation.pathname, location.pathname, { exact, strict, sensitive })
if (result) {
return <Link {...rest} className={activeClass} />
}
else{
return <Link {...rest} />
}
}}
</ctx.Consumer>
)
}
这里我们通过判断props中的目标地址是否与当前location的地址是否匹配,触发类名活跃度的更改,而更改则传递给Link组件,区别在于类名。
测试Link和NavLink
import React from 'react'
import { BrowserRouter, Route, withRouter,Link,NavLink } from "./react-router-dom"
function Comp(props) {
return <div>
{props.text}
</div>
}
const TestComp = withRouter(Comp);
function A() {
return <div>
<h1>A</h1>
<TestComp text="abc" />
</div>
}
function B() {
return <div>
<h1>B</h1>
</div>
}
export default function App() {
return (
<BrowserRouter>
<p>
<Link to={{
pathname: "/a",
search: "?a=1&b=2"
}}>a</Link>
</p>
<p>
<NavLink to="/b">b</NavLink>
</p>
<Route path="/a" component={A} />
<Route path="/b" component={B} />
</BrowserRouter>
)
}
通过测试可以看到当类名匹配时a标签类名的触发更改,而Link组件实现了页面的更改。
小结
本篇章我们实现了withRouter组件、Link组件和NavLink组件,三个组件的核心都是利用到上下文数据中的history去处理。而三个组件的逻辑本身并不负责,通过包装处理可以实现预期的功能。
至此,结合前面的几个篇章,我们实现了一个完整的react路由。