分步实现 react-router

安装 react-router-dom

yarn add react-router-dom

新建页面,可以随便创建,将创建的页面导入、并添加路由在 App.js ,如下:

import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
<Router>
   <Link to='/'>首页</Link> |
   <Link to='/react-redux'>react-redux</Link> |
   <Link to='/redux'>redux</Link> |
   <Link to='/hook'>Hook</Link>
   <Switch>
      <Route exact path="/" component={HomePage} />
      <Route path="/react-redux" component={ReactReduxPage} />
      <Route path="/redux" component={ReduxPage} />
      <Route path="/hook" component={HooksPage} />
      {/* <Route  component={404} /> */}
   </Switch>
</Router>

明确 react-router-dom Api 的作用

   BrowserRouter : Router的一种,通过使用HTML5提供的history API(pushState,replaceState,propstate)机制来维持页面UI同RUL的统一。
   HashRouter : 使⽤用URL的hash部分(即window.location.hash)来保持UI同RUL的统一。
   MemoryRouter : 把URL的历史记录保存在内存中的 <Router>(不读取、不写⼊地址栏)。在测试和⾮浏览器器环境中很有⽤用,如React Native。
   Router : 路由器,作为中间件传递数据。
   Link : a 链接,跳转页面。
   Switch : 匹配路由(独占路由)。
   Route : 路由,渲染页面(children、component、render)。
   Redirect : 重定向到的URL。
   Prompt : 跳出当前路由时,提示。

实现,BrowserRouter.js

其实BrowserRouter作为区分项目中路由的选择性 API,其作用肯定就是向下传递路径,也就是对象 history。然后子组件监听,并选择其与之相对应的匹配方式。

    1. 组件Router作为子组件,并下传递属性和 children。
    1. 利用history这个库的 api 来获取当前路径。
import React from 'react'
import Router from './Router';
import { createBrowserHistory } from 'history';
export default class BrowserRouter extends React.Component {
    constructor(props) {
        super(props);
        this.history = createBrowserHistory()
    }
    componentDidMount() { }
    render() {
        return <Router history={this.history} children={this.props.children} />
    }
}

实现,Router.js

    1. 作为路由器,也就是一个最上级的路由控制件。其作用就是初始化路由,并监听路由,然后将数据传递下去,触发更新。
    1. 因为要触发更新,且需要向子组件传递数据,而子组件为多层级(Link、Switch、Route 等),则需要使用 APIcontext
import React from 'react';
const RouterContext = React.createContext()
export default RouterContext
    1. 创建后,组件Router便可以使用,并向子组件传递属性。
<RouterContext.Provider
    value={{
       history: this.props.history,
       location: this.state.location,
       match: Router.computeRootMatch(this.state.location.pathname),
    }
    }
>
    { this.props.children}
</RouterContext.Provider >
    1. 处理监听,并复制
  props.history.listen((location) => {
      this.setState({
            location
      })
   })
    1. 初始化路径(每次打开页面需要初始化路径,才能显示)。
static computeRootMatch(pathname) {
     return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
  }

实现Link.js

    1. 根据路径可知,该组件起的作用定时那
    1. 所以实现 跳转,并去除刷新,即可完成此组件。
const handleClick = (event) => {
   event.preventDefault();
   context.history.push(to)
}
...
<a href={to} onClick={handleClick} {...restProps}>{children}</a>

实现Route.js

    1. 该组件最主要的作用就是渲染页面,但是源码中给了三个属性:children、component、render。这三个属性均可实现组件(页面)的渲染。
<Route exact path="/" children={ChildFn}  />
<Route exact path="/"  component={HomePage} />
<Route exact path="/" render={() => <div>Home</div>} />
    1. 路由配置并匹配检测渲染、传递属性及store
const { location } = context
const { path, children, component, render, computedMatch } = this.props
const match = computedMatch
   ? computedMatch : path
         ? matchPath(location.pathname, this.props) : context.match;
    1. 无论写 children、component、render 中的哪一个,都会渲染,那么为什么会有三个呢?源码是这样的:
<RouterContext.Provider value={props}>
   {props.match
      ? children
      ? typeof children === "function"
         ? __DEV__
            ? evalChildrenDev(children, props, this.props.path)
            : children(props)
         : children
      : component
      ? React.createElement(component, props)
      : render
      ? render(props)
      : null
      : typeof children === "function"
      ? __DEV__
      ? evalChildrenDev(children, props, this.props.path)
      : children(props)
      : null}
</RouterContext.Provider>

可见,匹配的顺序是children>component>render

实现Switch.js

    1. 作用是匹配(独占)路由,白话说就是:遇见第一个一样的,我就渲染与路径,无论是 或 。
    1. 反之,没有则匹配 404.
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={404} />

如果URL是/about时,<Switch> 将开始寻找匹配的<Route> 。此时找到第一个 将会被正确匹配,并渲染,然后,后面的便不再渲染,这就是独占。其原理就是:利用React原生APIReact.Children.forEach递归,并定位当前的child

React.Children.forEach(this.props.children, (child) => {
   if (match == null) {
         element = child;
         match = child.props.path ? matchPath(location.pathname, child.props) : context.match
   }
});

最后,就是整理思路。

    1. 明确API的作用和数据的流向。
    1. 利用可复用的组件和路径的匹配进行匹配并渲染。

本文源码归处☞源码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值