国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

react-router v4.x 源碼拾遺2

luoyibu / 1573人閱讀

摘要:如果將添加到當(dāng)前組件,并且當(dāng)前組件由包裹,那么將引用最外層包裝組件的實(shí)例而并非我們期望的當(dāng)前組件,這也是在實(shí)際開發(fā)中為什么不推薦使用的原因,使用一個(gè)回調(diào)函數(shù)是一個(gè)不錯(cuò)的選擇,也同樣的使用的是回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的。

回顧:上一篇講了BrowserRouter 和 Router之前的關(guān)系,以及Router實(shí)現(xiàn)路由跳轉(zhuǎn)切換的原理。這一篇來(lái)簡(jiǎn)短介紹react-router剩余組件的源碼,結(jié)合官方文檔,一起探究實(shí)現(xiàn)的的方式。

1. Switch.js

Switch對(duì)props.chidlren做遍歷篩選,將第一個(gè)與pathname匹配到的Route或者Redirect進(jìn)行渲染(此處只要包含有path這個(gè)屬性的子節(jié)點(diǎn)都會(huì)進(jìn)行篩選,所以可以直接使用自定義的組件,如果缺省path這個(gè)屬性,并且當(dāng)匹配到這個(gè)子節(jié)點(diǎn)時(shí),那么這個(gè)子節(jié)點(diǎn)就會(huì)被渲染同時(shí)篩選結(jié)束,即Switch里任何時(shí)刻只渲染唯一一個(gè)子節(jié)點(diǎn)),當(dāng)循環(huán)結(jié)束時(shí)仍沒(méi)有匹配到的子節(jié)點(diǎn)返回null。Switch接收兩個(gè)參數(shù)分別是:

①:location, 開發(fā)者可以填入location參數(shù)來(lái)替換地址欄中的實(shí)際地址進(jìn)行匹配。

②:children,子節(jié)點(diǎn)。

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
import matchPath from "./matchPath";

class Switch extends React.Component {
    // 接收Router組件傳遞的context api,這也是為什么Switch要寫在
    // Router內(nèi)部的原因    
  static contextTypes = {
    router: PropTypes.shape({
      route: PropTypes.object.isRequired
    }).isRequired
  };

  static propTypes = {
    children: PropTypes.node,
    location: PropTypes.object
  };

  componentWillMount() {
    invariant(
      this.context.router,
      "You should not use  outside a "
    );
  }
  
  componentWillReceiveProps(nextProps) {
      // 這里的兩個(gè)警告是說(shuō),對(duì)于Switch的location這個(gè)參數(shù),我們不能做如下兩種操作
      // 從無(wú)到有和從有到無(wú),猜測(cè)這樣做的原因是Switch作為一個(gè)渲染控制容器組件,在每次
      // 渲染匹配時(shí)要做到前后的統(tǒng)一性,即不能第一次使用了地址欄的路徑進(jìn)行匹配,第二次
      // 又使用開發(fā)者自定義的pathname就行匹配 
    warning(
      !(nextProps.location && !this.props.location),
      " elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render."
    );

    warning(
      !(!nextProps.location && this.props.location),
      " elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render."
    );
  }

  render() {
    // Router提供的api,包括history對(duì)象,route對(duì)象等。route對(duì)象包含兩個(gè)參數(shù)
    // 1.location:history.location,即在上一章節(jié)里講到的history這個(gè)庫(kù)
    // 根據(jù)地址欄的pathname,hash,search,等創(chuàng)建的一個(gè)location對(duì)象。
    // 2.match 就是Router組件內(nèi)部的state, 即{path: "/", url: "/", params: {}, isEaxct: true/false}
    const { route } = this.context.router; 
    const { children } = this.props; // 子節(jié)點(diǎn)
     // 自定義的location或者Router傳遞的location
    const location = this.props.location || route.location;
    // 對(duì)所有子節(jié)點(diǎn)進(jìn)行循環(huán)操作,定義了mactch對(duì)象來(lái)接收匹配到
    // 的節(jié)點(diǎn){path,url,parmas,isExact}等信息,當(dāng)子節(jié)點(diǎn)沒(méi)有path這個(gè)屬性的時(shí)候
    // 且子節(jié)點(diǎn)被匹配到,那么這個(gè)match會(huì)直接使用Router組件傳遞的match
    // child就是匹配到子節(jié)點(diǎn)
    let match, child;
    React.Children.forEach(children, element => {
        // 判斷子節(jié)點(diǎn)是否是一個(gè)有效的React節(jié)點(diǎn)
        // 只有當(dāng)match為null的時(shí)候才會(huì)進(jìn)入匹配的操作,初看的時(shí)候感覺有些奇怪
        // 這里主要是matchPath這個(gè)方法做了什么?會(huì)在下一節(jié)講到,這里只需要知道
        // matchPath接收了pathname, options={path, exact...},route.match等參數(shù)
        // 使用正則庫(kù)判斷path是否匹配pathname,如果匹配則會(huì)返回新的macth對(duì)象,
        // 否則返回null,進(jìn)入下一次的循環(huán)匹配,巧妙如斯       
      if (match == null && React.isValidElement(element)) {
        const {
          path: pathProp,
          exact,
          strict,
          sensitive,
          from
        } = element.props; // 從子節(jié)點(diǎn)中獲取props信息,主要是pathProp這個(gè)屬性
        // 當(dāng)pathProp不存在時(shí),使用替代的from,否則就是undefined
        // 這里的from參數(shù)來(lái)自Redirect,即也可以對(duì)redirect進(jìn)行校驗(yàn),來(lái)判斷是否渲染redirect
        const path = pathProp || from;

        child = element;
        match = matchPath(
          location.pathname,
          { path, exact, strict, sensitive },
          route.match
        );
      }
    });
    // 如果match對(duì)象匹配到了,則調(diào)用cloneElement對(duì)匹配到child子節(jié)點(diǎn)進(jìn)行clone
    // 操作,并傳遞了兩個(gè)參數(shù)給子節(jié)點(diǎn),location對(duì)象,當(dāng)前的地址信息
    // computedMatch對(duì)象,匹配到的路由參數(shù)信息。    
    return match
      ? React.cloneElement(child, { location, computedMatch: match })
      : null;
  }
}

export default Switch;
2. matchPath.js

mathPath是react-router用來(lái)將path生成正則對(duì)象并對(duì)pathname進(jìn)行匹配的一個(gè)功能方法。當(dāng)path不存在時(shí),會(huì)直接返回Router的match結(jié)果,即當(dāng)子組件的path不存在時(shí)表示該子組件一定會(huì)被選渲染(在Switch中如果子節(jié)點(diǎn)沒(méi)有path,并不一定會(huì)被渲染,還需要考慮節(jié)點(diǎn)被渲染之前不能匹配到其他子節(jié)點(diǎn))。matchPath依賴一個(gè)第三方庫(kù)path-to-regexp,這個(gè)庫(kù)可以將傳遞的options:path, exact, strict, sensitive 生成一個(gè)正則表達(dá)式,然后對(duì)傳遞的pathname進(jìn)行匹配,并返回匹配的結(jié)果,服務(wù)于Switch,Route組件。參數(shù)如下:

① :pathname, 真實(shí)的將要被匹配的路徑地址,通常這個(gè)地址是地址欄中的pathname,開發(fā)者也可以自定義傳遞location對(duì)象進(jìn)行替換。

②:options,用來(lái)生成pattern的參數(shù)集合:
path: string, 生成正則當(dāng)中的路徑,比如“/user/:id”,非必填項(xiàng)無(wú)默認(rèn)值
exact: false,默認(rèn)值false。即使用正則匹配到結(jié)果url和pathname是否完全相等,如果傳遞設(shè)置為true,兩者必須完全相等才會(huì)返回macth結(jié)果
strict: false,默認(rèn)值false。即pathname的末尾斜杠會(huì)不會(huì)加入匹配規(guī)則,正常情況下這個(gè)參數(shù)用到的不多。
sensitive: false, 默認(rèn)值false。即正則表達(dá)式是否對(duì)大小寫敏感,同樣用到的不多,不過(guò)某些特殊場(chǎng)景下可能會(huì)用到。

源碼如下:
import pathToRegexp from "path-to-regexp";
// 用來(lái)緩存生成過(guò)的路徑的正則表達(dá)式,如果遇到相同配置規(guī)則且相同路徑的緩存,那么直接使用緩存的正則對(duì)象
const patternCache = {}; 
const cacheLimit = 10000; // 緩存的最大數(shù)量
let cacheCount = 0; // 已經(jīng)被緩存的個(gè)數(shù)

const compilePath = (pattern, options) => {
    // cacheKey表示配置項(xiàng)的stringify序列化,使用這個(gè)作為patternCache的key
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  // 每次先從patternCache中尋找符合當(dāng)前配置項(xiàng)的緩存對(duì)象,如果對(duì)象不存在那么設(shè)置一個(gè)
  const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
   // 如果存在以 path 路徑為key的對(duì)象,表示該路徑被生成過(guò),那么直接返回該正則信息
   // 至于為什么要做成多層的key來(lái)緩存,即相同的配置項(xiàng)作為第一層key,pattern作為第二層key
   // 應(yīng)該是即便我們使用obj["xx"]的方式來(lái)調(diào)用某個(gè)值,js內(nèi)部依然是要進(jìn)行遍歷操作的,這樣封裝
   // 兩層key,是為了更好的做循環(huán)的優(yōu)化處理,減少了遍歷查找的時(shí)間。
  if (cache[pattern]) return cache[pattern];

  const keys = []; // 用來(lái)存儲(chǔ)動(dòng)態(tài)路由的參數(shù)key
  const re = pathToRegexp(pattern, keys, options);
  const compiledPattern = { re, keys }; //將要被返回的結(jié)果
    // 當(dāng)緩存數(shù)量小于10000時(shí),繼續(xù)緩存
  if (cacheCount < cacheLimit) {
    cache[pattern] = compiledPattern;
    cacheCount++;
  }
    // 返回生成的正則表達(dá)式已經(jīng)動(dòng)態(tài)路由的參數(shù)
  return compiledPattern;
};

/**
 * Public API for matching a URL pathname to a path pattern.
 */
const matchPath = (pathname, options = {}, parent) => {
    // options也可以直接傳遞一個(gè)path,其他參數(shù)方法會(huì)自動(dòng)添加默認(rèn)值
  if (typeof options === "string") options = { path: options };
    // 從options獲取參數(shù),不存在的參數(shù)使用默認(rèn)值
  const { path, exact = false, strict = false, sensitive = false } = options;
    // 當(dāng)path不存在時(shí),直接返回parent,即父級(jí)的match匹配信息
  if (path == null) return parent;
    // 使用options的參數(shù)生成,這里將exact的參數(shù)名改為end,是因?yàn)閜ath-to-regexp用end參數(shù)來(lái)表示
    // 是否匹配完整的路徑。即如果默認(rèn)false的情況下,path: /one 和 pathname: /one/two,
    // path是pathname的一部分,pathname包含了path,那么就會(huì)判斷此次匹配成功
  const { re, keys } = compilePath(path, { end: exact, strict, sensitive });
  const match = re.exec(pathname); // 對(duì)pathname進(jìn)行匹配

  if (!match) return null; // 當(dāng)match不存在時(shí),表示沒(méi)有匹配到,直接返回null
     // 從match中獲取匹配到的結(jié)果,以一個(gè)path-to-regexp的官方例子來(lái)表示
     // const keys = []
     // const regexp = pathToRegexp("/:foo/:bar", keys)
    // regexp.exec("/test/route")
    //=> [ "/test/route", "test", "route", index: 0, input: "/test/route", groups: undefined ]
  const [url, ...values] = match;
  const isExact = pathname === url; // 判斷是否完全匹配

  if (exact && !isExact) return null; // 當(dāng)exact值為true且沒(méi)有完全匹配時(shí)返回null

  return {
    path, // the path pattern used to match
    url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
    isExact, // whether or not we matched exactly
    params: keys.reduce((memo, key, index) => {
        // 獲取動(dòng)態(tài)路由的參數(shù),即傳遞的path: "/:user/:id", pathname: "/xiaohong/23",
        // params最后返回的結(jié)果就是 {user: xiaohong, id: 23}
      memo[key.name] = values[index];
      return memo;
    }, {})
  };
};

export default matchPath;

簡(jiǎn)單介紹一下path-to-regexp的用法,path-to-regexp的官方地址:鏈接描述

const pathToRegexp = require("path-to-regexp")
const keys = []
const regexp = pathToRegexp("/foo/:bar", keys)
// regexp = /^/foo/([^/]+?)/?$/i  表示生成的正則表達(dá)式
// keys = [{ name: "bar", prefix: "/", delimiter: "/", optional: false, repeat: false, pattern: "[^/]+?" }]
// keys表示動(dòng)態(tài)路由的參數(shù)信息
regexp.exec("/test/route") // 對(duì)pathname進(jìn)行匹配并返回匹配的結(jié)果
//=> [ "/test/route", "test", "route", index: 0, input: "/test/route", groups: undefined ]
3. Route.js

Route.js 是react-router最核心的組件,通過(guò)對(duì)path進(jìn)行匹配,來(lái)判斷是否需要渲染當(dāng)前組件,它本身也是一個(gè)容器組件。細(xì)節(jié)上需要注意的是,只要path被匹配那么組件就會(huì)被渲染,并且Route組件在非Switch包裹的前提下,不受其他組件渲染的影響。當(dāng)path參數(shù)不存在的時(shí)候,組件一定會(huì)被渲染。

源碼如下:
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
import matchPath from "./matchPath";
// 判斷children是否為空
const isEmptyChildren = children => React.Children.count(children) === 0;
class Route extends React.Component {
  static propTypes = {
    computedMatch: PropTypes.object, // 當(dāng)外部使用Switch組件包裹時(shí),此參數(shù)由Switch傳遞進(jìn)來(lái)表示當(dāng)前組件被匹配的信息
    path: PropTypes.string,
    exact: PropTypes.bool,
    strict: PropTypes.bool,
    sensitive: PropTypes.bool,
    component: PropTypes.func, // 組件
    render: PropTypes.func, // 一個(gè)渲染函數(shù),函數(shù)的返回結(jié)果為一個(gè)組件或者null,一般用來(lái)做鑒權(quán)操作
    children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), // props.children, 子節(jié)點(diǎn)
    location: PropTypes.object //自定義的location信息
  };
    // 接收Router組件傳遞的context api
  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.object.isRequired,
      route: PropTypes.object.isRequired,
      staticContext: PropTypes.object // 由staticRouter傳遞,服務(wù)端渲染時(shí)會(huì)用到
    })
  };
    // 傳遞給子組件的 context api
  static childContextTypes = {
    router: PropTypes.object.isRequired
  };
    // Router組件中也有類似的一套操作,不同的是將Router傳遞的match進(jìn)行了替換,而
    // location對(duì)象如果當(dāng)前傳遞了自定義的location,也就會(huì)被替換,否則還是Router組件中傳遞過(guò)來(lái)的location
  getChildContext() {
    return {
      router: {
        ...this.context.router,
        route: {
          location: this.props.location || this.context.router.route.location,
          match: this.state.match
        }
      }
    };
  }
    // 返回當(dāng)前Route傳遞的options匹配的信息,匹配過(guò)程請(qǐng)看matchPath方法
  state = {
    match: this.computeMatch(this.props, this.context.router)
  };

  computeMatch(
    { computedMatch, location, path, strict, exact, sensitive },
    router
  ) {
      // 特殊情況,當(dāng)有computeMatch這個(gè)參數(shù)的時(shí)候,表示當(dāng)前組件是由上層Switch組件
      // 已經(jīng)進(jìn)行渲染過(guò)后進(jìn)行clone的組件,那么直接進(jìn)行渲染不需要再進(jìn)行匹配了
    if (computedMatch) return computedMatch;

    invariant(
      router,
      "You should not use  or withRouter() outside a "
    );

    const { route } = router; //獲取Router組件傳遞的route信息,即包括location、match兩個(gè)對(duì)象
    const pathname = (location || route.location).pathname;
    // 返回matchPath匹配的結(jié)果
    return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
  }

  componentWillMount() {
      // 當(dāng)同時(shí)傳遞了component 和 render兩個(gè)props,那么render將會(huì)被忽略
    warning(
      !(this.props.component && this.props.render),
      "You should not use  and  in the same route;  will be ignored"
    );
        // 當(dāng)同時(shí)傳遞了 component 和 children并且children非空,會(huì)進(jìn)行提示
        // 并且 children 會(huì)被忽略
    warning(
      !(
        this.props.component &&
        this.props.children &&
        !isEmptyChildren(this.props.children)
      ),
      "You should not use  and  in the same route;  will be ignored"
    );
         // 當(dāng)同時(shí)傳遞了 render 和 children并且children非空,會(huì)進(jìn)行提示
        // 并且 children 會(huì)被忽略
    warning(
      !(
        this.props.render &&
        this.props.children &&
        !isEmptyChildren(this.props.children)
      ),
      "You should not use  and  in the same route;  will be ignored"
    );
  }
    // 不允許對(duì)Route組件的locatin參數(shù) 做增刪操作,即Route組件應(yīng)始終保持初始狀態(tài),
    // 可以被Router控制,或者被開發(fā)者控制,一旦創(chuàng)建則不能進(jìn)行更改
  componentWillReceiveProps(nextProps, nextContext) {
    warning(
      !(nextProps.location && !this.props.location),
      " elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render."
    );

    warning(
      !(!nextProps.location && this.props.location),
      " elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render."
    );
        // 這里看到并沒(méi)有對(duì)nextProps和this.props做類似的比較,而是直接進(jìn)行了setState來(lái)進(jìn)行rerender
        // 結(jié)合上一章節(jié)講述的Router渲染的流程,頂層Router進(jìn)行setState之后,那么所有子Route都需要進(jìn)行
        // 重新匹配,然后再渲染對(duì)應(yīng)的節(jié)點(diǎn)數(shù)據(jù)
    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    });
  }

  render() {
    const { match } = this.state; // matchPath的結(jié)果
    const { children, component, render } = this.props; //三種渲染方式
    const { history, route, staticContext } = this.context.router; // context router api
    const location = this.props.location || route.location; // 開發(fā)者自定義的location優(yōu)先級(jí)高
    const props = { match, location, history, staticContext }; // 傳遞給子節(jié)點(diǎn)的props數(shù)據(jù)
    // component優(yōu)先級(jí)最高
    if (component) return match ? React.createElement(component, props) : null;
    // render優(yōu)先級(jí)第二,返回render執(zhí)行后的結(jié)果
    if (render) return match ? render(props) : null;
    // 如果children是一個(gè)函數(shù),那么返回執(zhí)行后的結(jié)果 與render類似
    // 此處需要注意即children是不需要進(jìn)行match驗(yàn)證的,即只要Route內(nèi)部
    // 嵌套了節(jié)點(diǎn),那么只要不同時(shí)存在component或者render,這個(gè)內(nèi)部節(jié)點(diǎn)一定會(huì)被渲染
    if (typeof children === "function") return children(props);
    // Route內(nèi)的節(jié)點(diǎn)為非空,那么保證當(dāng)前children有一個(gè)包裹的頂層節(jié)點(diǎn)才渲染
    if (children && !isEmptyChildren(children))
      return React.Children.only(children);
    // 否則渲染一個(gè)空節(jié)點(diǎn)
    return null;
  }
}

export default Route;
4. withRouter.js

withRouter.js 作為react-router中的唯一HOC,負(fù)責(zé)給非Route組件傳遞context api,即 router: { history, route: {location, match}}。它本身是一個(gè)高階組件,并使用了
hoist-non-react-statics這個(gè)依賴庫(kù),來(lái)保證傳遞的組件的靜態(tài)屬性。
高階組件的另外一個(gè)問(wèn)題就是refs屬性,引用官方文檔的解釋:雖然高階組件的約定是將所有道具傳遞給包裝組件,但這對(duì)于refs不起作用,是因?yàn)閞ef不是真正的prop,它是由react專門處理的。如果將添加到當(dāng)前組件,并且當(dāng)前組件由hoc包裹,那么ref將引用最外層hoc包裝組件的實(shí)例而并非我們期望的當(dāng)前組件,這也是在實(shí)際開發(fā)中為什么不推薦使用refs string的原因,使用一個(gè)回調(diào)函數(shù)是一個(gè)不錯(cuò)的選擇,withRouter也同樣的使用的是回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的。react官方推薦的解決方案是 React.forwardRef API(16.3版本), 地址如下:鏈接描述

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
import Route from "./Route"; 
// withRouter使用的也是Route容器組件,這樣Component就可以直接使用props獲取到history等api

const withRouter = Component => {
    // withRouter使用一個(gè)無(wú)狀態(tài)組件
  const C = props => {
      // 接收 wrappedComponentRef屬性來(lái)返回refs,remainingProps保留其他props
    const { wrappedComponentRef, ...remainingProps } = props;
    // 實(shí)際返回的是Componetn由Route組件包裝的, 并且沒(méi)有path等屬性保證Component組件一定會(huì)被渲染
    return (
       (
          
        )}
      />
    );
  };

  C.displayName = `withRouter(${Component.displayName || Component.name})`;
  C.WrappedComponent = Component;
  C.propTypes = {
    wrappedComponentRef: PropTypes.func
  };
    // 將Component組件的靜態(tài)方法復(fù)制到C組件
  return hoistStatics(C, Component);
};

export default withRouter;
5. Redirect.js

Redirect組件是react-router中的重定向組件,本身是一個(gè)容器組件不做任何實(shí)際內(nèi)容的渲染,其工作流程就是將地址重定向到一個(gè)新地址,地址改變后,觸發(fā)Router組件的回調(diào)setState,進(jìn)而更新整個(gè)app。參數(shù)如下

① push: boolean,
默認(rèn)false,即重定向的地址會(huì)替換當(dāng)前路徑在history歷史記錄中的位置,如果值為true,即在歷史記錄中增加重定向的地址,不會(huì)刪掉當(dāng)前的地址,和push和repalce的區(qū)別一樣

② from: string, 無(wú)默認(rèn)值, 即頁(yè)面的來(lái)源地址 ③ to: object|string,
無(wú)默認(rèn)值,即將重定向的新地址,可以是object {pathname: "/login", search: "?name=xxx",
state: {type: 1}},對(duì)于location當(dāng)中的信息,當(dāng)不需要傳遞參數(shù)的時(shí)候,可以直接簡(jiǎn)寫to為pathname

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
// createLocation傳入path, state, key, currentLocation,返回一個(gè)新的location對(duì)象
// locationsAreEqual 判斷兩個(gè)location對(duì)象的值是否完全相同
import { createLocation, locationsAreEqual } from "history"; 
import generatePath from "./generatePath"; // 將參數(shù)pathname,search 等拼接成一個(gè)完成url

class Redirect extends React.Component {
  static propTypes = {
    computedMatch: PropTypes.object, // Switch組件傳遞的macth props
    push: PropTypes.bool,
    from: PropTypes.string,
    to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
  };

  static defaultProps = {
    push: false
  };
    // context api
  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired
      }).isRequired,
      staticContext: PropTypes.object // staticRouter時(shí)額外傳遞的context
    }).isRequired
  };
    // 判斷是否是服務(wù)端渲染
  isStatic() {
    return this.context.router && this.context.router.staticContext;
  }

  componentWillMount() {
    invariant(
      this.context.router,
      "You should not use  outside a "
    );
    // 服務(wù)端渲染時(shí)無(wú)法使用didMount,在此鉤子進(jìn)行重定向
    if (this.isStatic()) this.perform();
  }

  componentDidMount() {
    if (!this.isStatic()) this.perform();
  }

  componentDidUpdate(prevProps) {
    const prevTo = createLocation(prevProps.to); // 上一次重定向的地址
    const nextTo = createLocation(this.props.to); // 當(dāng)前的重定向地址
    
    if (locationsAreEqual(prevTo, nextTo)) {
        // 當(dāng)新舊兩個(gè)地址完全相同時(shí),控制臺(tái)打印警告并不進(jìn)行跳轉(zhuǎn)
      warning(
        false,
        `You tried to redirect to the same route you"re currently on: ` +
          `"${nextTo.pathname}${nextTo.search}"`
      );
      return;
    }
    // 不相同時(shí),進(jìn)行重定向
    this.perform();
  }

  computeTo({ computedMatch, to }) {
    if (computedMatch) {
        // 當(dāng) 當(dāng)前Redirect組件被外層Switch渲染時(shí),那么將外層Switch傳遞的params
        // 和 Redirect的pathname,組成一個(gè)object或者string作為即將要重定向的地址
      if (typeof to === "string") {
        return generatePath(to, computedMatch.params);
      } else {
        return {
          ...to,
          pathname: generatePath(to.pathname, computedMatch.params)
        };
      }
    }

    return to;
  }

  perform() {
    const { history } = this.context.router; // 獲取router api
    const { push } = this.props; // 重定向方式
    const to = this.computeTo(this.props); // 生成統(tǒng)一的重定向地址string||object

    if (push) {
      history.push(to);
    } else {
      history.replace(to);
    }
  }
    // 容器組件不進(jìn)行任何實(shí)際的渲染
  render() {
    return null;
  }
}

export default Redirect;

Redirect作為一個(gè)重定向組件,當(dāng)組件重定向后,組件就會(huì)被銷毀,那么這個(gè)componentDidUpdate在這里存在的意義是什么呢,按照代碼層面的理解,它的作用就是提示開發(fā)者重定向到了一個(gè)重復(fù)的地址。思考如下demo


  

當(dāng)?shù)刂吩L問(wèn)"/album/5" 的時(shí)候,Redirect的from參數(shù) 匹配到了這個(gè)路徑,然后又將地址重定向到了‘/album/5’,此時(shí)又調(diào)用頂層Router的render,但是由于地址相同,此時(shí)Switch依然會(huì)匹配Redirect組件,Redirect組件并沒(méi)有被銷毀,此時(shí)就會(huì)進(jìn)行提示,目的就是為了更友好的提示開發(fā)者
在此貼一下對(duì)這個(gè)問(wèn)題的討論:鏈接描述
locationsAreEqual的源碼如下:比較簡(jiǎn)單就不在贅述了,這里依賴了一個(gè)第三方庫(kù)valueEqual,即判斷兩個(gè)object的值是否相等

export const locationsAreEqual = (a, b) =>
  a.pathname === b.pathname &&
  a.search === b.search &&
  a.hash === b.hash &&
  a.key === b.key &&
  valueEqual(a.state, b.state)
6. generatePath.js

generatePath是react-router組件提供的工具方法,即將傳遞地址信息path、params處理成一個(gè)可訪問(wèn)的pathname

源碼如下:
import pathToRegexp from "path-to-regexp";

// 在react-router中只有Redirect使用了此api, 那么我們可以簡(jiǎn)單將
// patternCache 看作用來(lái)緩存進(jìn)行重定向過(guò)的地址信息,此處的優(yōu)化和在matchPath進(jìn)行
// 的緩存優(yōu)化相似
const patternCache = {}; 
const cacheLimit = 10000;
let cacheCount = 0;

const compileGenerator = pattern => {
  const cacheKey = pattern;
  // 對(duì)于每次將要重定向的地址,首先從本地cache緩存里去查詢有無(wú)記錄,沒(méi)有記錄的
  // 的話以重定向地址重新創(chuàng)建一個(gè)object
  const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
    // 如果獲取到了記錄那么直接返回上次匹配的正則對(duì)象
  if (cache[pattern]) return cache[pattern];
    // 調(diào)用pathToRegexp將pathname生成一個(gè)函數(shù),此函數(shù)可以對(duì)對(duì)象進(jìn)行匹配,最終
    // 返回一個(gè)匹配正確的地址信息,示例demo在下面,也可以訪問(wèn)path-to-regexp的
    // 官方地址:https://github.com/pillarjs/path-to-regexp
  const compiledGenerator = pathToRegexp.compile(pattern);
    // 進(jìn)行緩存
  if (cacheCount < cacheLimit) {
    cache[pattern] = compiledGenerator;
    cacheCount++;
  }
    // 返回正則對(duì)象的函數(shù)
  return compiledGenerator;
};

/**
 * Public API for generating a URL pathname from a pattern and parameters.
 */
const generatePath = (pattern = "/", params = {}) => {
    // 默認(rèn)重定向地址為根路徑,當(dāng)為根路徑時(shí),直接返回
  if (pattern === "/") {
    return pattern;
  }
  const generator = compileGenerator(pattern);
  // 最終生成一個(gè)url地址,這里的pretty: true是path-to-regexp里的一項(xiàng)配置,即只對(duì)
  // `/?#`地址欄里這三種特殊符合進(jìn)行轉(zhuǎn)碼,其他字符不變。至于為什么這里還需要將Switch
  // 匹配到的params傳遞給將要進(jìn)行定向的路徑不是很理解?即當(dāng)重定向的路徑是 "/user/:id"
  // 并且當(dāng)前地址欄的路徑是 "/user/33", 那么重定向地址就會(huì)被解析成 "/user/33",即不變
  return generator(params, { pretty: true }); 
};

export default generatePath;

pathToRegexp.compile 示例demo,接收一個(gè)pattern參數(shù),最終返回一個(gè)url路徑,將pattern中的動(dòng)態(tài)路徑替換成匹配的對(duì)象當(dāng)中的對(duì)應(yīng)key的value

const toPath = pathToRegexp.compile("/user/:id")

toPath({ id: 123 }) //=> "/user/123"
toPath({ id: "café" }) //=> "/user/caf%C3%A9"
toPath({ id: "/" }) //=> "/user/%2F"

toPath({ id: ":/" }) //=> "/user/%3A%2F"
toPath({ id: ":/" }, { encode: (value, token) => value }) //=> "/user/:/"

const toPathRepeated = pathToRegexp.compile("/:segment+")

toPathRepeated({ segment: "foo" }) //=> "/foo"
toPathRepeated({ segment: ["a", "b", "c"] }) //=> "/a/b/c"

const toPathRegexp = pathToRegexp.compile("/user/:id(d+)")

toPathRegexp({ id: 123 }) //=> "/user/123"
toPathRegexp({ id: "123" }) //=> "/user/123"
toPathRegexp({ id: "abc" }) //=> Throws `TypeError`.
toPathRegexp({ id: "abc" }, { noValidate: true }) //=> "/user/abc"
7. Prompt.js

Prompt.js 也許是react-router中很少被用到的組件,它的作用就是可以方便開發(fā)者對(duì)路由跳轉(zhuǎn)進(jìn)行 ”攔截“,注意這里并不是真正的攔截,而是react-router自己做到的hack,同時(shí)在特殊需求下使用這個(gè)組件的時(shí)候會(huì)引發(fā)其他bug,至于原因就不在這里多說(shuō)了,上一篇文章中花費(fèi)了很大篇幅來(lái)講這個(gè)功能的實(shí)現(xiàn),參數(shù)如下

① when: boolean, 默認(rèn)true,即當(dāng)使用此組件時(shí)默認(rèn)對(duì)路由跳轉(zhuǎn)進(jìn)行攔截處理。

② message: string或者func,當(dāng)為string類型時(shí),即直接展示給用戶的提示信息。當(dāng)為func類型的時(shí)候,可以接收(location, action)兩個(gè)參數(shù),我們可以根據(jù)參數(shù)和自身的業(yè)務(wù)選擇性的進(jìn)行攔截,只要不返回string類型 或者 false,router便不會(huì)進(jìn)行攔截處理

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import invariant from "invariant";

class Prompt extends React.Component {
  static propTypes = {
    when: PropTypes.bool,
    message: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired
  };

  static defaultProps = {
    when: true // 默認(rèn)進(jìn)行攔截
  };

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        block: PropTypes.func.isRequired
      }).isRequired
    }).isRequired
  };

  enable(message) {
    if (this.unblock) this.unblock();
    // 講解除攔截的方法進(jìn)行返回
    this.unblock = this.context.router.history.block(message);
  }

  disable() {
    if (this.unblock) {
      this.unblock();
      this.unblock = null;
    }
  }

  componentWillMount() {
    invariant(
      this.context.router,
      "You should not use  outside a "
    );

    if (this.props.when) this.enable(this.props.message);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.when) {
        // 只有將本次攔截取消后 才能進(jìn)行修改message的操作
      if (!this.props.when || this.props.message !== nextProps.message)
        this.enable(nextProps.message);
    } else {
        // when 改變?yōu)閒alse時(shí)直接取消
      this.disable();
    }
  }

  componentWillUnmount() {
      // 銷毀后取消攔截
    this.disable();
  }

  render() {
    return null;
  }
}

export default Prompt;
8 Link.js

Link是react-router中用來(lái)進(jìn)行聲明式導(dǎo)航創(chuàng)建的一個(gè)組件,與其他組件不同的是,它本身會(huì)渲染一個(gè)a標(biāo)簽來(lái)進(jìn)行導(dǎo)航,這也是為什么Link.js 和 NavLink.js 會(huì)被寫在react-router-dom組件庫(kù)而不是react-router。當(dāng)然在實(shí)際開發(fā)中,受限于樣式和封裝性的影響,直接使用Link或者NavLink的場(chǎng)景并不是很多。先簡(jiǎn)單介紹一下Link的幾個(gè)參數(shù)

① onClick: func, 點(diǎn)擊跳轉(zhuǎn)的事件,開發(fā)時(shí)在跳轉(zhuǎn)前可以在此定義特殊的業(yè)務(wù)邏輯

② target: string, 和a標(biāo)簽的其他屬性類似,即 _blank self top 等參數(shù)

③ replace: boolean, 默認(rèn)false,即跳轉(zhuǎn)地址的方式,默認(rèn)使用pushState

④ to: string/object, 跳轉(zhuǎn)的地址,可以時(shí)字符串即pathname,也可以是一個(gè)object包含pathname,search,hash,state等其他參數(shù)

⑤ innerRef: string/func, a標(biāo)簽的ref,方便獲取dom節(jié)點(diǎn)

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import invariant from "invariant";
import { createLocation } from "history";

// 判斷當(dāng)前的左鍵點(diǎn)擊事件是否使用了復(fù)合點(diǎn)擊
const isModifiedEvent = event =>
  !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);

class Link extends React.Component {
  static propTypes = {
    onClick: PropTypes.func,
    target: PropTypes.string,
    replace: PropTypes.bool,
    to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
    innerRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
  };

  static defaultProps = {
    replace: false
  };
    // 接收Router傳遞的context api,來(lái)進(jìn)行push 或者 replace操作
  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired,
        createHref: PropTypes.func.isRequired
      }).isRequired
    }).isRequired
  };

  handleClick = event => {
    if (this.props.onClick) this.props.onClick(event); // 跳轉(zhuǎn)前的回調(diào)
    // 只有以下情況才會(huì)使用不刷新的跳轉(zhuǎn)方式來(lái)進(jìn)行導(dǎo)航
    // 1.阻止默認(rèn)事件的方法不存在
    // 2.使用的左鍵進(jìn)行點(diǎn)擊
    // 3.不存在target屬性
    // 4.沒(méi)有使用復(fù)合點(diǎn)擊事件進(jìn)行點(diǎn)擊
    if (
      !event.defaultPrevented && // onClick prevented default
      event.button === 0 && // ignore everything but left clicks
      !this.props.target && // let browser handle "target=_blank" etc.
      !isModifiedEvent(event) // ignore clicks with modifier keys
    ) {
      event.preventDefault(); // 必須要阻止默認(rèn)事件,否則會(huì)走a標(biāo)簽href屬性里的地址

      const { history } = this.context.router;
      const { replace, to } = this.props;
        // 進(jìn)行跳轉(zhuǎn)
      if (replace) {
        history.replace(to);
      } else {
        history.push(to);
      }
    }
  };

  render() {
    const { replace, to, innerRef, ...props } = this.props; // eslint-disable-line no-unused-vars

    invariant(
      this.context.router,
      "You should not use  outside a "
    );
    // 必須指定to屬性
    invariant(to !== undefined, "You must specify the "to" property");

    const { history } = this.context.router;
    // 將to轉(zhuǎn)換成一個(gè)location對(duì)象
    const location =
      typeof to === "string"
        ? createLocation(to, null, null, history.location)
        : to;
    // 將to生成對(duì)象的href地址
    const href = history.createHref(location);
    return (
        // 渲染成a標(biāo)簽
      
    );
  }
}

export default Link;
9. NavLink.js

NavLink.js 是Link.js的升級(jí)版,主要功能就是對(duì)Link添加了激活狀態(tài),方便進(jìn)行導(dǎo)航樣式的控制。這里我們可以設(shè)想下如何實(shí)現(xiàn)這個(gè)功能?可以使用Link傳遞的to參數(shù),生成一個(gè)路徑然后和當(dāng)前地址欄的pathname進(jìn)行匹配,匹配成功的給Link添加activeClass即可。其實(shí)NavLink也是這樣實(shí)現(xiàn)的。參數(shù)如下:

① to: 即Link當(dāng)中to,即將跳轉(zhuǎn)的地址,這里還用來(lái)進(jìn)行正則匹配

② exact: boolean, 默認(rèn)false, 即正則匹配到的url是否完全和地址欄pathname相等

③ strict: boolean, 默認(rèn)false, 即最后的 ‘/’ 是否加入匹配

④ location: object, 自定義的location匹配對(duì)象

⑤ activeClassName: string, 即當(dāng)Link被激活時(shí)候的class名稱

⑥ className: string, 對(duì)Link的改寫的class名稱

⑦ activeStyle: object, Link被激活時(shí)的樣式

⑧ style: object, 對(duì)Link改寫的樣式

⑨ isAcitve: func, 當(dāng)Link被匹配到的時(shí)候的回調(diào)函數(shù),可以再此對(duì)匹配到LInk進(jìn)行自定義的業(yè)務(wù)邏輯,當(dāng)返回false時(shí),Link樣式也不會(huì)被激活

⑩ aria-current: string, 當(dāng)Link被激活時(shí)候的html自定義屬性

源碼如下:
import React from "react";
import PropTypes from "prop-types";
import Route from "./Route";
import Link from "./Link";

const NavLink = ({
  to,
  exact,
  strict,
  location,
  activeClassName,
  className,
  activeStyle,
  style,
  isActive: getIsActive,
  "aria-current": ariaCurrent,
  ...rest
}) => {
  const path = typeof to === "object" ? to.pathname : to;
  // 看到這里的時(shí)候會(huì)有一個(gè)疑問(wèn),為什么要將path里面的特殊符號(hào)轉(zhuǎn)義
  // 在Switch里一樣有對(duì)Route Redirect進(jìn)行劫持的操作,并沒(méi)有將里面的path進(jìn)行此操作,
  // Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202
  const escapedPath = path && path.replace(/([.+*?=^!:${}()[]|/])/g, "$1");
    
  return (
     {
        const isActive = !!(getIsActive ? getIsActive(match, location) : match);

        return (
           i).join(" ")
                : className
            }
            style={isActive ? { ...style, ...activeStyle } : style}
            aria-current={(isActive && ariaCurrent) || null}
            {...rest}
          />
        );
      }}
    />
  );
};

NavLink.propTypes = {
  to: Link.propTypes.to,
  exact: PropTypes.bool,
  strict: PropTypes.bool,
  location: PropTypes.object,
  activeClassName: PropTypes.string,
  className: PropTypes.string,
  activeStyle: PropTypes.object,
  style: PropTypes.object,
  isActive: PropTypes.func,
  "aria-current": PropTypes.oneOf([
    "page",
    "step",
    "location",
    "date",
    "time",
    "true"
  ])
};

NavLink.defaultProps = {
  activeClassName: "active",
  "aria-current": "page"
};

export default NavLink;

NavLink的to必須要在這里轉(zhuǎn)義的原因什么呢?下面其實(shí)列出了原因,即當(dāng)path當(dāng)中出現(xiàn)這些特殊字符的時(shí)候Link無(wú)法被激活,假如NavLink的地址如下:

link

點(diǎn)擊后頁(yè)面跳轉(zhuǎn)至 "/pricewatch/027357/intel-core-i7-7820x-(boxed)" 同時(shí) 頂層Router 啟動(dòng)新一輪的rerender。
而我們的Route組件一般針對(duì)這種動(dòng)態(tài)路由書寫的path格式可能是 "/pricewatch/:id/:type" 所以使用這個(gè)path生成的正則表達(dá)式,對(duì)地址欄中的pathname進(jìn)行匹配是結(jié)果的。
但是,在NavLink里,因?yàn)閠o代表的就是實(shí)際訪問(wèn)地址,并不是Route當(dāng)中那個(gè)寬泛的path,并且由于to當(dāng)中包含有 "()" 正則表達(dá)式的關(guān)鍵字,在使用path-to-regexp這個(gè)庫(kù)生成的正則表達(dá)式就變成了

/^/pricewatch/027357/intel-core-i7-7820x-((?:boxed))(?:/(?=$))?$/i

其中((?:boxed))變成了子表達(dá)式,而地址欄的真實(shí)路徑卻是 "/pricewatch/027357/intel-core-i7-7820x-(boxed)",子表達(dá)式部分無(wú)法匹配 "(" 這個(gè)特殊符號(hào),因此造成matchPath的匹配失敗。
所以才需要在NavLink這里對(duì)to傳遞的path進(jìn)行去正則符號(hào)化。
其根本原因是因?yàn)镽oute組件的path設(shè)計(jì)之初就是為了進(jìn)行正則匹配,它應(yīng)該是一個(gè)宏觀上的寬泛地址。而Link的to參數(shù)就是一個(gè)實(shí)際地址,強(qiáng)行將to設(shè)置為path,所以引起了上述bug。下面貼一下官方對(duì)這個(gè)問(wèn)題的討論
鏈接描述
鏈接描述
可見,當(dāng)我們總是追求某些功能組件的復(fù)用度時(shí),也許就埋下了未知的bug。當(dāng)然也無(wú)需擔(dān)心,該來(lái)的總會(huì)來(lái),有bug了改掉就好

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/109813.html

相關(guān)文章

  • react-router v4.x 源碼拾遺1

    摘要:還是先來(lái)一段官方的基礎(chǔ)使用案例,熟悉一下整體的代碼流程中使用了端常用到的等一些常用組件,作為的頂層組件來(lái)獲取的和設(shè)置回調(diào)函數(shù)來(lái)更新。 react-router是react官方推薦并參與維護(hù)的一個(gè)路由庫(kù),支持瀏覽器端、app端、服務(wù)端等常見場(chǎng)景下的路由切換功能,react-router本身不具備切換和跳轉(zhuǎn)路由的功能,這些功能全部由react-router依賴的history庫(kù)完成,his...

    itvincent 評(píng)論0 收藏0
  • react-router v4.x 源碼拾遺1

    摘要:還是先來(lái)一段官方的基礎(chǔ)使用案例,熟悉一下整體的代碼流程中使用了端常用到的等一些常用組件,作為的頂層組件來(lái)獲取的和設(shè)置回調(diào)函數(shù)來(lái)更新。 react-router是react官方推薦并參與維護(hù)的一個(gè)路由庫(kù),支持瀏覽器端、app端、服務(wù)端等常見場(chǎng)景下的路由切換功能,react-router本身不具備切換和跳轉(zhuǎn)路由的功能,這些功能全部由react-router依賴的history庫(kù)完成,his...

    Joyven 評(píng)論0 收藏0
  • react-router v4.x 源碼拾遺2

    摘要:如果將添加到當(dāng)前組件,并且當(dāng)前組件由包裹,那么將引用最外層包裝組件的實(shí)例而并非我們期望的當(dāng)前組件,這也是在實(shí)際開發(fā)中為什么不推薦使用的原因,使用一個(gè)回調(diào)函數(shù)是一個(gè)不錯(cuò)的選擇,也同樣的使用的是回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的。 回顧:上一篇講了BrowserRouter 和 Router之前的關(guān)系,以及Router實(shí)現(xiàn)路由跳轉(zhuǎn)切換的原理。這一篇來(lái)簡(jiǎn)短介紹react-router剩余組件的源碼,結(jié)合官方文...

    CoorChice 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<