关于 react-router- dom 的使用 和 原理

1,442 阅读3分钟

react-router

使用

1、 安装

npm i react-router-dom
import {
  HashRouter,
  BrowserRouter,
  Route,
  Link,
  Switch,
} from 'react-router-dom'

用最大的 HashRouter / BrowserRouter 来包裹组件

<BrowserRouter>

    <ul>
        <li>
             <Link to='/list'>显示列表</Link>
        </li>
        <li>
             <Link to='/Clock'>显示时间</Link>
        </li>
    </ul>

        <div>
        {/* exact 精准匹配 */}
            <Route exact path='/list' component={List}></Route>
            <Route path='/ContextTest' component={ContextTest}></Route>
            <Route exact path='/Clock' component={Clock}></Route>
            <Route path='/reducer' component={Reducer}></Route>
            <Route path='/reftest' component={RefTest}></Route>
            <Route path='/redux' component={Redux}></Route>
            <Route path='/reactredux' component={ReactReduxPage}></Route>
         </div>


</BrowserRouter>

当使用switch组件的时候就会从上到下找到合适的组件 叫独占路由

        <Switch>
            <Route exact path='/list' component={List}></Route>
            <Route path='/ContextTest' component={ContextTest}></Route>
            <Route exact path='/Clock' component={Clock}></Route>
            <Route path='/reducer' component={Reducer}></Route>
        </Switch>

如果不写 path 就是匹配任意值

子路由的渲染优先级是 children > component > render

Redirect

要重定向到的位置,其中 pathname 可以是 path-to-regexp 能够理解的任何有效的 URL 路径。

<Redirect 
    path={'/'} 
    to={{ pathname: '/login', search: '?utm=your+face', state: { referrer: currentLocation }}} 
/>

上例中的 state 对象可以在重定向到的组件中通过 this.props.location.state 进⾏访问。⽽ referrer

键(不是特殊名称)将通过路径名 /login 指向的登录组件中的 this.props.location.state.referrer 进⾏访问。





实现 react-router

暂定 实现一下组件

import { Route, Link, Switch, BrowserRouter } from '../react-router/index'

我们知道 react 路由有 hashRouter、browerRouter、memoryRouter

这几个router的作用是传递不同的 historyApi 给 子组件去使用, 将三者api封装成统一的api





1、关于 BrowserRouter

传递了 history 的 api给下层

export class BrowserRouter extends Component {
  constructor(props) {
    super(props)
    this.history = history.createBrowserHistory()
  }

  render() {
    return (
      <Router children={this.props.children} history={this.history}></Router>
    )
  }
}





2、 关于 Router

这层组件 承接了 不同 路由方式的api的 中间层, 上层传递了history 的统一api 给这层使用

作用:

  1. 监听路由变化 ,若路由发生改变,则重新渲染当前组件
  2. 通过 createContext 把 history api 传递给 子组件
  3. 把 location 也传给子组件
  4. 传递一个默认path 表示 根路径匹配
import RouterContext from './context'

export class Router extends Component {
  // 静态方法  传递一个默认path 表示 根路径
  static computeRootMatch(pathname) {
    return { path: '/', url: '/', params: {}, isExact: pathname === '/' }
  }

  constructor(props) {
    super(props)
    this.state = {
      location: props.history.location,
    }
    // 提供监听
    this.unlisten = props.history.listen((obj) => {
      this.setState({ location: obj.location })
    })
  }
  componentWillUnmount() {
    this.unlisten() // 执行取消监听
  }
  render() {
    const { children, history } = this.props
    // 提供history跳转 ,和 location 的参数
    return (
      <RouterContext.Provider
        value={{
          history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname),
        }}
      >
        {children}
      </RouterContext.Provider>
    )
  }
}

我们输出 History 看看

history 包含了以下方法

比如我们常使用 的 go、push、replace、back

action: (...)
back: ƒ ()
block: ƒ (a)
createHref: ƒ g(a)
forward: ƒ ()
go: ƒ r(a)
listen: ƒ (a)
location: Object
push: ƒ w(a, d)
replace: ƒ u(a, d)
get action: ƒ action()
get location: ƒ location()
hash: ""
key: "h7fgir2z"
pathname: "/ContextTest"
search: "?fuck=true"
state: null





3、 实现 Link

其 只提供的 a 标签支持跳转,需屏蔽默认事件

history api 通过 useContext(RouterContext) 获取

// 组件 link

export function Link({ children, to, ...restProps }) {
  const { history } = useContext(RouterContext)
  const handleClick = (e) => {
    // 页面跳转
    e.preventDefault()
    history.push(to)
  }

  return (
    <a href={''} to={to} onClick={handleClick}>
      {children}
    </a>
  )
}





4、Route

1、 作用是显示 匹配路由的 children

2、 如果有个switch的组件,那computedMatch就会有值

3、 取值优先级 computedMatch > matchPath(location.pathname, this.props) > context.match 分别就是 取 switch组件给的匹配结果 => 自己的匹配结果 => 根目录结果

4、children > component > render 除此之外children如果是个函数那么必渲染(上层有switch组件除外)

5、 通过测试发现 switch 组件会改变 它的children,只会渲染匹配的,没有使用switch组件时,所有 Route 都会渲染,只是渲染结果可能为null。


import matchPath from './matchPath'

export class Route extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {(context) => {
          const { location } = context
          const { children, component, render, path, computedMatch } = this.props

          // 表示匹配的 结果
          const match = computedMatch 
            ? computedMatch
            : path
            ? matchPath(location.pathname, this.props)
            : context.match

          // 这里取值的优先顺序是
          
          // computedMatch > matchPath(location.pathname, this.props) > 默认的 match

          // console.log('match:', match)
          
          const props = { ...context, match }

          // 匹配 children,component,render null
          // 不匹配

          return match
            ? children // 1
              ? typeof children === 'function' // 2
                ? children(props)
                : children
              : component // 3
              ? React.createElement(component, props)
              : render // 4
              ? render()
              : null
            : typeof children === 'function' // 1
            ? children()
            : null
        }}
      </RouterContext.Consumer>
    )
  }
}





Switch 组件

找出 匹配的组件

export class Switch extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {(context) => {
          let match;
          let element;

          const { location } = context
          
          React.Children.forEach(this.props.children, (child) => {
            if (match == null && React.isValidElement(child)) {
              element = child
              match = matchPath(location.pathname, child.props) // 匹配出来的
            }
          })

          return match
            ? React.cloneElement(element, { computedMatch: match })
            : null
        }}
      </RouterContext.Consumer>
    )
  }
}





Redirect 组件

该组件得和 Route 同级,得有 path 和 to 属性 ,path是为了 给 switch 去和 location.pathname,否则无论如何都会渲染

export class Redirect extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {(context) => {
          const { history } = context
          const { to, push = false } = this.props
          return (
            <LifeCycle
              onMount={() => {
                push ? history.push(to) : history.replace(to)
              }}
            ></LifeCycle>
          )
        }}
      </RouterContext.Consumer>
    )
  }
}

class LifeCycle extends Component {
  componentDidMount() {
    if (this.props.onMount) {
      this.props.onMount.call(this, this)
    }
  }
  render() {
    return null
  }
}





关于 withRouter

该方法是一个高阶组件的用法

需在Route 组件包一层 修改下match的值, 通过 provider

// 高阶组件
export const withRouter = (WrappedComponent) => (props) => {
  return (
    <RouterContext.Consumer>
      {(context) => {
        return <WrappedComponent {...props} {...context} />
      }}
    </RouterContext.Consumer>
  )
}





Prompt 实现

阻碍的原理是 调用 history.block(message)