本篇文章将对react-router中残余的组件进行源码剖析

<Redirect>

和其余的路由组件一样, <Redirect>应用<RouterContext.Consumer>接管路由数据;

定义<Redirect>的prop-types

Redirect.propTypes = {  push: PropTypes.bool,  from: PropTypes.string,  to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired};

<Redirect>的渲染逻辑

首先通过传入的push确定<Redirect>的跳转形式是push还是replace:

// push为props.push, 默认为falseconst method = push ? history.push : history.replace;

接着确定跳转的location: createLocationhistory库的办法, 会依据传入的参数创立一个location对象:

// to为props.to, computedMatch为props.computedMatchconst location = createLocation(  computedMatch  ? typeof to === "string"  ? generatePath(to, computedMatch.params)  : {    ...to,    pathname: generatePath(to.pathname, computedMatch.params)  }  : to);

注:

  1. <Redirect>作为<Switch>的子组件并被匹配时, <Switch>将会将匹配计算得出的computedMatch传给<Redirect>
  2. generatePath是react-router提供的一个api, 用于将path和parameters组合生成一个pathname

接下来就是<Redirect>跳转逻辑实现:

<Lifecycle  onMount={() => {    method(location);  }}  onUpdate={(self, prevProps) => {    const prevLocation = createLocation(prevProps.to);    if (      !locationsAreEqual(prevLocation, {        ...location,        key: prevLocation.key      })    ) {      method(location);    }  }}  to={to}/>

<Lifecycle>组件构造非常简单, 反对传入onMount, onUpdate以及onUnmount三个办法, 别离代表着componentDidMount, componentDidUpdate, componentWillUnmount;

因而<Redirect>应用Lifecycle触发的动作如下:

  1. <Redirect>componentDidMount生命周期中进行push/replace跳转;
  2. componentDidUpdate生命周期中应用history库的locationsAreEqual办法, 比拟上一个location和新的location是否雷同, 若是location不雷同, 则执行push/replace跳转事件;
// LifeCycle.jsimport React from "react";class Lifecycle extends React.Component {  componentDidMount() {    if (this.props.onMount) this.props.onMount.call(this, this);  }  componentDidUpdate(prevProps) {    if (this.props.onUpdate) this.props.onUpdate.call(this, this, prevProps);  }  componentWillUnmount() {    if (this.props.onUnmount) this.props.onUnmount.call(this, this);  }  render() {    return null;  }}export default Lifecycle;

<Link>

<Link>实现了react-router中路由跳转;

定义<Link>的prop-types

const toType = PropTypes.oneOfType([  PropTypes.string,  PropTypes.object,  PropTypes.func]);const refType = PropTypes.oneOfType([  PropTypes.string,  PropTypes.func,  PropTypes.shape({ current: PropTypes.any })]);Link.displayName = "Link";Link.propTypes = {  innerRef: refType,  onClick: PropTypes.func,  replace: PropTypes.bool,  target: PropTypes.string,  to: toType.isRequired};

实际上<Link>还有一个prop: component, 但不分明这里为什么不对component进行类型申明;

<Link>的渲染逻辑

<Link>应用<RouterContext.Consumer>接管路由信息;

<Link>通过对props.to进行解决, 得出href属性, 申明props对象:

(    {    component = LinkAnchor,    replace,    to,    innerRef, // TODO: deprecate    ...rest  }) => {    //  ... 通过解决props.to得出href  const props = {    ...rest,    href,    navigate() {      const location = resolveToLocation(to, context.location);      const method = replace ? history.replace : history.push;      method(location);    }  };    // ...}

并将下面得出的props注入component中:

return React.createElement(component, props);

从源码能够看到, 此处的component默认为LinkAnchor, 因而咱们来浏览以下<LinkAnchor>的源码:

LinkAnchor的props构造如下:

{  innerRef, // TODO: deprecate  navigate,  onClick,  ...rest}

次要是navigate以及onClick:

navigate<Link>源码中能够看到, 次要是通过传入的replace属性判断跳转类型, 依据对应跳转类型抉择history.replace或是history.push进行路由跳转:

navigate() {  const location = resolveToLocation(to, context.location);  const method = replace ? history.replace : history.push;  method(location);}

onClick更好了解, 是<Link>组件的点击事件申明;

<LinkAnchor>通过传入的props生成了一个props, 并返回一个注入了props的超链接:

let props = {    // ...};return <a {...props} />;

次要性能实现在于超链接的onClick, 点击事件中首先判断是否存在props.onClick, 存在的话则立刻执行; 接着进行是否执行props.navigate的判断:

是否进行跳转须要满足以下所有条件:

  • event.button === 0: 点击事件为鼠标左键;
  • !target || target === "_self": _target不存在, 或者_target_self;
  • !isModifiedEvent(event): 点击事件产生时未有其余按键同时按住;

    注: isModifiedEvent用于判断点击事件产生时是否有其余按键同时按住;

if (  !event.defaultPrevented && // onClick prevented default  event.button === 0 && // ignore everything but left clicks  (!target || target === "_self") && // let browser handle "target=_blank" etc.  !isModifiedEvent(event) // ignore clicks with modifier keys) {  // ...}

满足以上所有条件时执行以下代码:

event.preventDefault();navigate();

event.preventDefault()阻止超链接默认事件, 防止点击<Link>后从新刷新页面;

navigate()应用history.pushhistory.replace进行路由跳转, 并触发<Router>中申明的history监听事件, 从新渲染路由组件!

withRouter

定义withRouter的prop-types

wrappedComponentRef使得高阶组件可能拜访到它包裹组件的ref;

C.propTypes = {  wrappedComponentRef: PropTypes.oneOfType([    PropTypes.string,    PropTypes.func,    PropTypes.object  ])};

withRouter的渲染逻辑

withRouter是一个高阶组件, 反对传入一个组件, 返回一个能拜访路由数据的路由组件, 本质上是将组件作为<RouterContext.Consumer>的子组件, 并将context的路由信息作为props注入组件中;

const C = props => {  // ...返回组件  const { wrappedComponentRef, ...remainingProps } = props;    return (      <RouterContext.Consumer>                {context => {          return (            <Component              {...remainingProps}              {...context}              ref={wrappedComponentRef}            />          );        }}      </RouterContext.Consumer>    );};return hoistStatics(C, Component);

hoistStatics是三方库hoist-non-react-statics, 用于解决高阶组件中原组件static失落的问题; 同时应用反对传入props: wrappedComponentRef, wrappedComponentRef绑定原组件的ref, 因而能够通过wrappedComponentRef拜访到原组件; 须要留神的是, 函数式组件没有ref, 因为函数式组件并没有实例, 所以应用withRouter包裹函数式组件时, 不反对应用wrappedComponentRef拜访原组件!

Hooks

React Router ships with a few hooks that let you access the state of the router and perform navigation from inside your components.

Please note: You need to be using React >= 16.8 in order to use any of these hooks!

react-router提供了一些hooks, 让咱们能够在组件中获取到路由的状态并且执行导航; 如果须要应用这些钩子, 咱们须要应用React >= 16.8;

react-router的hooks实际上是利用React提供的hooks: useContext, 让咱们能够在组件中拜访到HistoryContext以及RouterContext中的数据;

useHistory

import React from 'react';import HistoryContext from "./HistoryContext.js";const useContext = React.useContext;export function useHistory() {  return useContext(HistoryContext);};

useLocation

import React from 'react';import RouterContext from "./RouterContext.js";const useContext = React.useContext;export function useLocation() {    return useContext(RouterContext).location;};

useParams

import React from 'react';import RouterContext from "./RouterContext.js";const useContext = React.useContext;export function useParams() {  const match = useContext(RouterContext).match;    return match ? match.params : {};};

useRouteMatch

import React from 'react';import RouterContext from "./RouterContext.js";import matchPath from "./matchPath.js";const useContext = React.useContext;export function useRouteMatch(path) {  const location = useLocation();  const match = useContext(RouterContext).match;  return path ? matchPath(location.pathname, path) : match;}

注:

  • useRouteMatch应用hook: useLocation, 去获取location;
  • matchPath是react-router的一个公共api, 反对传入一个pathname以及path, 若是pathpathname匹配则返回一个match对象, 不匹配则返回一个null;

结尾

从源码对react-router v5进行原理剖析系列到此结束, 实际上还有一些比拟冷的组件没有进行源码浏览(挖个坑, 当前有空能够填);

认真想想, 这还是第一次系统性地去浏览一个高星的库, 这次源码浏览让我感觉受益匪浅, 比照一下本人写的库, 不论是从设计还是总体封装都是差了十万八千里(笑, 还得努致力;

作者之前是偏差vue, 因为最近开始系统性地学React, 所以想趁着学习的激情, 把React一些高星的库挖挖, 看看能不能从源码中了解到一些react开发中的小技巧或是设计思维, 所以目标是达到了;

感叹一下: React的生态是真的凋敝, 根底库也是多到目迷五色, 其实在我看来这也算个小毛病, 因为工具的多样化有可能会呈现以下问题: 因为开发过程中没沟通好, 导致我的项目中引入多个雷同的库, 目前保护的平台的确有这种问题, 以前的开发也是百花齐放呢(怒;

在这里抛出一个问题呀:

在react中, 我能够通过这么写去笼罩组件的props:

const props = {  title: '新题目'};<Component title="旧题目" {...props}></Component>

而在vue中用以下的写法却不能笼罩之前组件的props:

<template>  <Component title="旧题目" v-bind="{title: '新题目'}"></Component></template>

有看过vue源码的兄台来解答一下纳闷吗? 那么接下来的指标就是去看看vue的源码啦!