基于history实现路由里的withRouter、Link、NavLink

104 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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>
  )
}

image.png

image.png

通过控制台输出我们看到路由信息成功注入到被包装的组件中。

实现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>
  )
}

image.png

image.png

image.png

通过测试可以看到当类名匹配时a标签类名的触发更改,而Link组件实现了页面的更改。

小结

本篇章我们实现了withRouter组件、Link组件和NavLink组件,三个组件的核心都是利用到上下文数据中的history去处理。而三个组件的逻辑本身并不负责,通过包装处理可以实现预期的功能。

至此,结合前面的几个篇章,我们实现了一个完整的react路由。