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

資訊專欄INFORMATION COLUMN

react-router v4.x 源碼拾遺1

Joyven / 2478人閱讀

摘要:還是先來(lái)一段官方的基礎(chǔ)使用案例,熟悉一下整體的代碼流程中使用了端常用到的等一些常用組件,作為的頂層組件來(lái)獲取的和設(shè)置回調(diào)函數(shù)來(lái)更新。

react-router是react官方推薦并參與維護(hù)的一個(gè)路由庫(kù),支持瀏覽器端、app端、服務(wù)端等常見(jiàn)場(chǎng)景下的路由切換功能,react-router本身不具備切換和跳轉(zhuǎn)路由的功能,這些功能全部由react-router依賴的history庫(kù)完成,history庫(kù)通過(guò)對(duì)url的監(jiān)聽(tīng)來(lái)觸發(fā) Router 組件注冊(cè)的回調(diào),回調(diào)函數(shù)中會(huì)獲取最新的url地址和其他參數(shù)然后通過(guò)setState更新,從而使整個(gè)應(yīng)用進(jìn)行rerender。所以react-router本身只是封裝了業(yè)務(wù)上的眾多功能性組件,比如Route、Link、Redirect 等等,這些組件通過(guò)context api可以獲取到Router傳遞history api,比如push、replace等,從而完成頁(yè)面的跳轉(zhuǎn)。
還是先來(lái)一段react-router官方的基礎(chǔ)使用案例,熟悉一下整體的代碼流程

import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

function BasicExample() {
  return (
    
      
  • Home
  • About
  • Topics

); } function Home() { return (

Home

); } function About() { return (

About

); } function Topics({ match }) { return (

Topics

  • Rendering with React
  • Components
  • Props v. State

Please select a topic.

} />
); } function Topic({ match }) { return (

{match.params.topicId}

); } export default BasicExample;

Demo中使用了web端常用到的BrowserRouter、Route、Link等一些常用組件,Router作為react-router的頂層組件來(lái)獲取 history 的api 和 設(shè)置回調(diào)函數(shù)來(lái)更新state。這里引用的組件都是來(lái)自react-router-dom 這個(gè)庫(kù),那么react-router 和 react-router-dom 是什么關(guān)系呢。
說(shuō)的簡(jiǎn)單一點(diǎn),react-router-dom 是對(duì)react-router所有組件或方法的一個(gè)二次導(dǎo)出,并且在react-router組件的基礎(chǔ)上添加了新的組件,更加方便開(kāi)發(fā)者處理復(fù)雜的應(yīng)用業(yè)務(wù)。

1.react-router 導(dǎo)出的所有內(nèi)容

統(tǒng)計(jì)一下,總共10個(gè)方法
1.MemoryRouter.js、2.Prompt.js、3.Redirect.js、4.Route.js、5.Router.js、6.StaticRouter.js、7.Switch.js、8.generatePath.js、9.matchPath.js、10.withRouter.js

2.react-router-dom 導(dǎo)出的所有內(nèi)容

統(tǒng)計(jì)一下,總共14個(gè)方法
1.BrowserRouter.js、2.HashRouter.js、3.Link.js、4.MemoryRouter.js、5.NavLink.js、6.Prompt.js、7.Redirect.js、8.Route.js、9.Router.js、10.StaticRouter.js、11.Switch.js、12.generatePath.js、13.matchPath.js、14.withRouter.js
react-router-dom在react-router的10個(gè)方法上,又添加了4個(gè)方法,分別是BrowserRouter、HashRouter、Link、以及NavLink。
所以,react-router-dom將react-router的10個(gè)方法引入后,又加入了4個(gè)方法,再重新導(dǎo)出,在開(kāi)發(fā)中我們只需要引入react-router-dom這個(gè)依賴即可。

下面進(jìn)入react-router-dom的源碼分析階段,首先來(lái)看一下react-router-dom的依賴庫(kù)

React, 要求版本大于等于15.x

history, react-router的核心依賴庫(kù),注入組件操作路由的api

invariant, 用來(lái)拋出異常的工具庫(kù)

loose-envify, 使用browserify工具進(jìn)行打包的時(shí)候,會(huì)將項(xiàng)目當(dāng)中的node全局變量替換為對(duì)應(yīng)的字符串

prop-types, react的props類型校驗(yàn)工具庫(kù)

react-router, 依賴同版本的react-router

warning, 控制臺(tái)打印警告信息的工具庫(kù)

①.BrowserRouter.js, 提供了HTML5的history api 如pushState、replaceState等來(lái)切換地址,源碼如下

import warning from "warning";
import React from "react";
import PropTypes from "prop-types";
import { createBrowserHistory as createHistory } from "history";
import Router from "./Router";

/**
 * The public API for a  that uses HTML5 history.
 */
class BrowserRouter extends React.Component {
  static propTypes = {
    basename: PropTypes.string, // 當(dāng)應(yīng)用為某個(gè)子應(yīng)用時(shí),添加的地址欄前綴
    forceRefresh: PropTypes.bool, // 切換路由時(shí),是否強(qiáng)制刷新
    getUserConfirmation: PropTypes.func, // 使用Prompt組件時(shí) 提示用戶的confirm確認(rèn)方法,默認(rèn)使用window.confirm
    keyLength: PropTypes.number, // 為了實(shí)現(xiàn)block功能,react-router維護(hù)創(chuàng)建了一個(gè)訪問(wèn)過(guò)的路由表,每個(gè)key代表一個(gè)曾經(jīng)訪問(wèn)過(guò)的路由地址
    children: PropTypes.node // 子節(jié)點(diǎn)
  };
  // 核心api, 提供了push replace go等路由跳轉(zhuǎn)方法
  history = createHistory(this.props); 
  // 提示用戶 BrowserRouter不接受用戶自定義的history方法,
  // 如果傳遞了history會(huì)被忽略,如果用戶使用自定義的history api,
  // 需要使用 Router 組件進(jìn)行替代
  componentWillMount() {
    warning(
      !this.props.history,
      " ignores the history prop. To use a custom history, " +
        "use `import { Router }` instead of `import { BrowserRouter as Router }`."
    );
  }
  // 將history和children作為props傳遞給Router組件 并返回
  render() {
    return ;
  }
}

export default BrowserRouter;

**總結(jié):BrowserRouter組件非常簡(jiǎn)單,它本身其實(shí)就是對(duì)Router組件的一個(gè)包裝,將HTML5的history api封裝好再賦予 Router 組件。BrowserRouter就好比一個(gè)容器組件,由它來(lái)決定Router的最終api,這樣一個(gè)Router組件就可以完成多種api的實(shí)現(xiàn),比如HashRouter、StaticRouter 等,減少了代碼的耦合度
②. Router.js, 如果說(shuō)BrowserRouter是Router的容器組件,為Router提供了html5的history api的數(shù)據(jù)源,那么Router.js 亦可以看作是子節(jié)點(diǎn)的容器組件,它除了接收BrowserRouter提供的history api,最主要的功能就是組件本身會(huì)響應(yīng)地址欄的變化進(jìn)行setState進(jìn)而完成react本身的rerender,使應(yīng)用進(jìn)行相應(yīng)的UI切換,源碼如下**

import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";

/**
 * The public API for putting history on context.
 */
class Router extends React.Component {
    // react-router 4.x依然使用的使react舊版的context API
    // react-router 5.x將會(huì)作出升級(jí)
  static propTypes = {
    history: PropTypes.object.isRequired,
    children: PropTypes.node
  };
  // 此處是為了能夠接收父級(jí)容器傳遞的context router,不過(guò)父級(jí)很少有傳遞router的
  // 存在的目的是為了方便用戶使用這種潛在的方式,來(lái)傳遞自定義的router對(duì)象
  static contextTypes = {
    router: PropTypes.object
  };
  // 傳遞給子組件的context api router, 可以通過(guò)context上下文來(lái)獲得
  static childContextTypes = {
    router: PropTypes.object.isRequired
  };
  // router 對(duì)象的具體值
  getChildContext() {
    return {
      router: {
        ...this.context.router,
        history: this.props.history, // 路由api等,會(huì)在history庫(kù)進(jìn)行講解
        route: {
          location: this.props.history.location, // 也是history庫(kù)中的內(nèi)容
          match: this.state.match // 對(duì)當(dāng)前地址進(jìn)行匹配的結(jié)果
        }
      }
    };
  }
  // Router組件的state,作為一個(gè)頂層容器組件維護(hù)的state,存在兩個(gè)目的
  // 1.主要目的為了實(shí)現(xiàn)自上而下的rerender,url改變的時(shí)候match對(duì)象會(huì)被更新
  // 2.Router組件是始終會(huì)被渲染的組件,match對(duì)象會(huì)隨時(shí)得到更新,并經(jīng)過(guò)context api
  // 傳遞給下游子組件route等
  state = {
    match: this.computeMatch(this.props.history.location.pathname)
  };
  // match 的4個(gè)參數(shù)
  // 1.path: 是要進(jìn)行匹配的路徑可以是 "/user/:id" 這種動(dòng)態(tài)路由的模式
  // 2.url: 地址欄實(shí)際的匹配結(jié)果
  // 3.parmas: 動(dòng)態(tài)路由所匹配到的參數(shù),如果path是 "/user/:id"匹配到了,那么
  // params的內(nèi)容就是 {id: 某個(gè)值}
  // 4.isExact: 精準(zhǔn)匹配即 地址欄的pathname 和 正則匹配到url是否完全相等
  computeMatch(pathname) {
    return {
      path: "/",
      url: "/",
      params: {},
      isExact: pathname === "/"
    };
  }

  componentWillMount() {
    const { children, history } = this.props;
    // 當(dāng) 子節(jié)點(diǎn)并非由一個(gè)根節(jié)點(diǎn)包裹時(shí) 拋出錯(cuò)誤提示開(kāi)發(fā)者
    invariant(
      children == null || React.Children.count(children) === 1,
      "A  may have only one child element"
    );

    // Do this here so we can setState when a  changes the
    // location in componentWillMount. This happens e.g. when doing
    // server rendering using a .
    // 使用history.listen方法,在Router被實(shí)例化時(shí)注冊(cè)一個(gè)回調(diào)事件,
    // 即location地址發(fā)生改變的時(shí)候,會(huì)重新setState,進(jìn)而rerender
    // 這里使用willMount而不使用didMount的原因時(shí)是因?yàn)?,服?wù)端渲染時(shí)不存在dom,
    // 故不會(huì)調(diào)用didMount的鉤子,react將在17版本移除此鉤子,那么到時(shí)候router應(yīng)該如何實(shí)現(xiàn)此功能?
    this.unlisten = history.listen(() => {
      this.setState({
        match: this.computeMatch(history.location.pathname)
      });
    });
  }
   // history參數(shù)不允許被更改
  componentWillReceiveProps(nextProps) {
    warning(
      this.props.history === nextProps.history,
      "You cannot change "
    );
  }
  // 組件銷(xiāo)毀時(shí) 解綁history對(duì)象中的監(jiān)聽(tīng)事件
  componentWillUnmount() {
    this.unlisten();
  }
  // render的時(shí)候使用React.Children.only方法再驗(yàn)證一次
  // children 必須是一個(gè)由根節(jié)點(diǎn)包裹的組件或dom
  render() {
    const { children } = this.props;
    return children ? React.Children.only(children) : null;
  }
}

export default Router;

總結(jié):Router組件職責(zé)很清晰就是作為容器組件,將上層組件的api進(jìn)行向下的傳遞,同時(shí)組件本身注冊(cè)了回調(diào)方法,來(lái)滿足瀏覽器環(huán)境下或者服務(wù)端環(huán)境下location發(fā)生變化時(shí),重新setState,達(dá)到組件的rerender。那么history對(duì)象到底是怎么實(shí)現(xiàn)對(duì)地址欄進(jìn)行監(jiān)聽(tīng)的,又是如何對(duì)location進(jìn)行push 或者 replace的,這就要看history這個(gè)庫(kù)做了啥。

createBrowserHistory.js 使用html5 history api封裝的路由控制器

createHashHistory.js 使用hash方法封裝的路由控制器

createMemoryHistory.js 針對(duì)native app這種原生應(yīng)用封裝的路由控制器,即在內(nèi)存中維護(hù)一份路由表

createTransitionManager.js 針對(duì)路由切換時(shí)的相同操作抽離的一個(gè)公共方法,路由切換的操作器,攔截器和訂閱者都存在于此

DOMUtils.js 針對(duì)web端dom操作或判斷兼容性的一個(gè)工具方法集合

LocationUtils.js 針對(duì)location url處理等抽離的一個(gè)工具方法的集合

PathUtils.js 用來(lái)處理url路徑的工具方法集合

這里主要分析createBrowserHistory.js文件

import warning from "warning"
import invariant from "invariant"
import { createLocation } from "./LocationUtils"
import {
  addLeadingSlash,
  stripTrailingSlash,
  hasBasename,
  stripBasename,
  createPath
} from "./PathUtils"
import createTransitionManager from "./createTransitionManager"
import {
  canUseDOM,
  addEventListener,
  removeEventListener,
  getConfirmation,
  supportsHistory,
  supportsPopStateOnHashChange,
  isExtraneousPopstateEvent
} from "./DOMUtils"

const PopStateEvent = "popstate"
const HashChangeEvent = "hashchange"

const getHistoryState = () => {
  // ...
}

/**
 * Creates a history object that uses the HTML5 history API including
 * pushState, replaceState, and the popstate event.
 */
const createBrowserHistory = (props = {}) => {
  invariant(
    canUseDOM,
    "Browser history needs a DOM"
  )

  const globalHistory = window.history
  const canUseHistory = supportsHistory()
  const needsHashChangeListener = !supportsPopStateOnHashChange()

  const {
    forceRefresh = false,
    getUserConfirmation = getConfirmation,
    keyLength = 6
  } = props
  const basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ""

  const getDOMLocation = (historyState) => {
     // ...
  }

  const createKey = () =>
    Math.random().toString(36).substr(2, keyLength)

  const transitionManager = createTransitionManager()

  const setState = (nextState) => {
     // ...
  }

  const handlePopState = (event) => {
    // ...
  }

  const handleHashChange = () => {
    // ...
  }

  let forceNextPop = false

  const handlePop = (location) => {
     // ...
  }

  const revertPop = (fromLocation) => {
    // ...
  }

  const initialLocation = getDOMLocation(getHistoryState())
  let allKeys = [ initialLocation.key ]

  // Public interface

  const createHref = (location) =>
    basename + createPath(location)

  const push = (path, state) => {
    // ...
  }

  const replace = (path, state) => {
    // ...
  }

  const go = (n) => {
    globalHistory.go(n)
  }

  const goBack = () =>
    go(-1)

  const goForward = () =>
    go(1)

  let listenerCount = 0

  const checkDOMListeners = (delta) => {
    // ...
  }

  let isBlocked = false

  const block = (prompt = false) => {
    // ...
  }

  const listen = (listener) => {
    // ...
  }

  const history = {
    length: globalHistory.length,
    action: "POP",
    location: initialLocation,
    createHref,
    push,
    replace,
    go,
    goBack,
    goForward,
    block,
    listen
  }

  return history
}

export default createBrowserHistory

createBrowserHistory.js 總共300+行代碼,其原理就是封裝了原生的html5 的history api,如pushState,replaceState,當(dāng)這些事件被觸發(fā)時(shí)會(huì)激活subscribe的回調(diào)來(lái)進(jìn)行響應(yīng)。同時(shí)也會(huì)對(duì)地址欄進(jìn)行監(jiān)聽(tīng),當(dāng)history.go等事件觸發(fā)history popstate事件時(shí),也會(huì)激活subscribe的回調(diào)。

由于代碼量較多,而且依賴的方法較多,這里將方法分成幾個(gè)小節(jié)來(lái)進(jìn)行梳理,對(duì)于依賴的方法先進(jìn)行簡(jiǎn)短闡述,當(dāng)實(shí)際調(diào)用時(shí)在深入源碼內(nèi)部去探究實(shí)現(xiàn)細(xì)節(jié)

1. 依賴的工具方法

import warning from "warning"  // 控制臺(tái)的console.warn警告
import invariant from "invariant" // 用來(lái)拋出異常錯(cuò)誤信息
// 對(duì)地址參數(shù)處理,最終返回一個(gè)對(duì)象包含 pathname,search,hash,state,key 等參數(shù)
import { createLocation } from "./LocationUtils" 
import { 
  addLeadingSlash,  // 對(duì)傳遞的pathname添加首部`/`,即 "home" 處理為 "/home",存在首部`/`的不做處理
  stripTrailingSlash,  // 對(duì)傳遞的pathname去掉尾部的 `/`
  hasBasename, // 判斷是否傳遞了basename參數(shù)
  stripBasename, // 如果傳遞了basename參數(shù),那么每次需要將pathname中的basename統(tǒng)一去除
  createPath // 將location對(duì)象的參數(shù)生成最終的地址欄路徑
} from "./PathUtils"
import createTransitionManager from "./createTransitionManager" // 抽離的路由切換的公共方法
import {
  canUseDOM,  // 當(dāng)前是否可使用dom, 即window對(duì)象是否存在,是否是瀏覽器環(huán)境下
  addEventListener, // 兼容ie 監(jiān)聽(tīng)事件
  removeEventListener, // 解綁事件
  getConfirmation,   // 路由跳轉(zhuǎn)的comfirm 回調(diào),默認(rèn)使用window.confirm
  supportsHistory, // 當(dāng)前環(huán)境是否支持history的pushState方法
  supportsPopStateOnHashChange, // hashChange是否會(huì)觸發(fā)h5的popState方法,ie10、11并不會(huì)
  isExtraneousPopstateEvent // 判斷popState是否時(shí)真正有效的
} from "./DOMUtils"

const PopStateEvent = "popstate"  // 針對(duì)popstate事件的監(jiān)聽(tīng)
const HashChangeEvent = "hashchange" // 針對(duì)不支持history api的瀏覽器 啟動(dòng)hashchange監(jiān)聽(tīng)事件

// 返回history的state
const getHistoryState = () => {
  try {
    return window.history.state || {}
  } catch (e) {
    // IE 11 sometimes throws when accessing window.history.state
    // See https://github.com/ReactTraining/history/pull/289
    // IE11 下有時(shí)會(huì)拋出異常,此處保證state一定返回一個(gè)對(duì)象
    return {} 
  }
}

creareBrowserHistory的具體實(shí)現(xiàn)

const createBrowserHistory = (props = {}) => {
  // 當(dāng)不在瀏覽器環(huán)境下直接拋出錯(cuò)誤
  invariant(
    canUseDOM,
    "Browser history needs a DOM"
  )

  const globalHistory = window.history          // 使用window的history
  // 此處注意android 2. 和 4.0的版本并且ua的信息是 mobile safari 的history api是有bug且無(wú)法解決的
  const canUseHistory = supportsHistory()      
  // hashChange的時(shí)候是否會(huì)進(jìn)行popState操作,ie10、11不會(huì)進(jìn)行popState操作 
  const needsHashChangeListener = !supportsPopStateOnHashChange()

  const {
    forceRefresh = false,                     // 默認(rèn)切換路由不刷新
    getUserConfirmation = getConfirmation,    // 使用window.confirm
    keyLength = 6                             // 默認(rèn)6位長(zhǎng)度隨機(jī)key
  } = props
  // addLeadingSlash 添加basename頭部的斜杠
  // stripTrailingSlash 去掉 basename 尾部的斜杠
  // 如果basename存在的話,保證其格式為 ‘/xxx’
  const basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ""

  const getDOMLocation = (historyState) => {
       // 獲取history對(duì)象的key和state
    const { key, state } = (historyState || {})
     // 獲取當(dāng)前路徑下的pathname,search,hash等參數(shù)
    const { pathname, search, hash } = window.location 
      // 拼接一個(gè)完整的路徑
    let path = pathname + search + hash               

    // 當(dāng)傳遞了basename后,所有的pathname必須包含這個(gè)basename
    warning(
      (!basename || hasBasename(path, basename)),
      "You are attempting to use a basename on a page whose URL path does not begin " +
      "with the basename. Expected path "" + path + "" to begin with "" + basename + ""."
    )
    
    // 去掉path當(dāng)中的basename
    if (basename)
      path = stripBasename(path, basename)
    
    // 生成一個(gè)自定義的location對(duì)象
    return createLocation(path, state, key)
  }

  // 使用6位長(zhǎng)度的隨機(jī)key
  const createKey = () =>
    Math.random().toString(36).substr(2, keyLength)

  // transitionManager是history中最復(fù)雜的部分,復(fù)雜的原因是因?yàn)?  // 為了實(shí)現(xiàn)block方法,做了對(duì)路由攔截的hack,雖然能實(shí)現(xiàn)對(duì)路由切時(shí)的攔截功能
  // 比如Prompt組件,但同時(shí)也帶來(lái)了不可解決的bug,后面在討論
  // 這里返回一個(gè)對(duì)象包含 setPrompt、confirmTransitionTo、appendListener
  // notifyListeners 等四個(gè)方法
  const transitionManager = createTransitionManager()
  
  const setState = (nextState) => {
    // nextState包含最新的 action 和 location
    // 并將其更新到導(dǎo)出的 history 對(duì)象中,這樣Router組件相應(yīng)的也會(huì)得到更新
    // 可以理解為同react內(nèi)部所做的setState時(shí)相同的功能
    Object.assign(history, nextState)
    // 更新history的length, 實(shí)實(shí)保持和window.history.length 同步
    history.length = globalHistory.length
    // 通知subscribe進(jìn)行回調(diào)
    transitionManager.notifyListeners(
      history.location,
      history.action
    )
  }
  // 當(dāng)監(jiān)聽(tīng)到popState事件時(shí)進(jìn)行的處理
  const handlePopState = (event) => {
    // Ignore extraneous popstate events in WebKit.
    if (isExtraneousPopstateEvent(event))
      return 
    // 獲取當(dāng)前地址欄的history state并傳遞給getDOMLocation
    // 返回一個(gè)新的location對(duì)象
    handlePop(getDOMLocation(event.state))
  }

  const handleHashChange = () => {
      // 監(jiān)聽(tīng)到hashchange時(shí)進(jìn)行的處理,由于hashchange不會(huì)更改state
      // 故此處不需要更新location的state
    handlePop(getDOMLocation(getHistoryState()))
  }
   // 用來(lái)判斷路由是否需要強(qiáng)制
  let forceNextPop = false
   // handlePop是對(duì)使用go方法來(lái)回退或者前進(jìn)時(shí),對(duì)頁(yè)面進(jìn)行的更新,正常情況下來(lái)說(shuō)沒(méi)有問(wèn)題
   // 但是如果頁(yè)面使用Prompt,即路由攔截器。當(dāng)點(diǎn)擊回退或者前進(jìn)就會(huì)觸發(fā)histrory的api,改變了地址欄的路徑
   // 然后彈出需要用戶進(jìn)行確認(rèn)的提示框,如果用戶點(diǎn)擊確定,那么沒(méi)問(wèn)題因?yàn)榈刂窓诟淖兊牡刂肪褪菍⒁D(zhuǎn)到地址
   // 但是如果用戶選擇了取消,那么地址欄的路徑已經(jīng)變成了新的地址,但是頁(yè)面實(shí)際還停留再之前,這就產(chǎn)生了bug
   // 這也就是 revertPop 這個(gè)hack的由來(lái)。因?yàn)轫?yè)面的跳轉(zhuǎn)可以由程序控制,但是如果操作的本身是瀏覽器的前進(jìn)后退
   // 按鈕,那么是無(wú)法做到真正攔截的。
  const handlePop = (location) => {
    if (forceNextPop) {
      forceNextPop = false
      setState()
    } else {
      const action = "POP"

      transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
        if (ok) {
          setState({ action, location })
        } else {
            // 當(dāng)攔截器返回了false的時(shí)候,需要把地址欄的路徑重置為當(dāng)前頁(yè)面顯示的地址
          revertPop(location)
        }
      })
    }
  }
   // 這里是react-router的作者最頭疼的一個(gè)地方,因?yàn)殡m然用hack實(shí)現(xiàn)了表面上的路由攔截
   // ,但也會(huì)引起一些特殊情況下的bug。這里先說(shuō)一下如何做到的假裝攔截,因?yàn)楸旧韍tml5 history
   // api的特性,pushState 這些操作不會(huì)引起頁(yè)面的reload,所有做到攔截只需要不手懂調(diào)用setState頁(yè)面不進(jìn)行render即可
   // 當(dāng)用戶選擇了取消后,再將地址欄中的路徑變?yōu)楫?dāng)前頁(yè)面的顯示路徑即可,這也是revertPop實(shí)現(xiàn)的方式
   // 這里貼出一下對(duì)這個(gè)bug的討論:https://github.com/ReactTraining/history/issues/690
  const revertPop = (fromLocation) => {
      // fromLocation 當(dāng)前地址欄真正的路徑,而且這個(gè)路徑一定是存在于history歷史
      // 記錄當(dāng)中某個(gè)被訪問(wèn)過(guò)的路徑,因?yàn)槲覀冃枰獙⒌刂窓诘倪@個(gè)路徑重置為頁(yè)面正在顯示的路徑地址
      // 頁(yè)面顯示的這個(gè)路徑地址一定是還再history.location中的那個(gè)地址
      // fromLoaction 用戶原本想去但是后來(lái)又不去的那個(gè)地址,需要把他換位history.location當(dāng)中的那個(gè)地址      
    const toLocation = history.location

    // TODO: We could probably make this more reliable by
    // keeping a list of keys we"ve seen in sessionStorage.
    // Instead, we just default to 0 for keys we don"t know.
     // 取出toLocation地址再allKeys中的下標(biāo)位置
    let toIndex = allKeys.indexOf(toLocation.key)

    if (toIndex === -1)
      toIndex = 0
     // 取出formLoaction地址在allKeys中的下標(biāo)位置
    let fromIndex = allKeys.indexOf(fromLocation.key)

    if (fromIndex === -1)
      fromIndex = 0
     // 兩者進(jìn)行相減的值就是go操作需要回退或者前進(jìn)的次數(shù)
    const delta = toIndex - fromIndex
     // 如果delta不為0,則進(jìn)行地址欄的變更 將歷史記錄重定向到當(dāng)前頁(yè)面的路徑   
    if (delta) {
      forceNextPop = true // 將forceNextPop設(shè)置為true
      // 更改地址欄的路徑,又會(huì)觸發(fā)handlePop 方法,此時(shí)由于forceNextPop已經(jīng)為true則會(huì)執(zhí)行后面的
      // setState方法,對(duì)當(dāng)前頁(yè)面進(jìn)行rerender,注意setState是沒(méi)有傳遞參數(shù)的,這樣history當(dāng)中的
      // location對(duì)象依然是之前頁(yè)面存在的那個(gè)loaction,不會(huì)改變history的location數(shù)據(jù)
      go(delta) 
    }
  }

  // 返回一個(gè)location初始對(duì)象包含
  // pathname,search,hash,state,key key有可能是undefined
  const initialLocation = getDOMLocation(getHistoryState())
  let allKeys = [ initialLocation.key ]

  // Public interface

  // 拼接上basename
  const createHref = (location) =>
    basename + createPath(location)

  const push = (path, state) => {
    warning(
      !(typeof path === "object" && path.state !== undefined && state !== undefined),
      "You should avoid providing a 2nd state argument to push when the 1st " +
      "argument is a location-like object that already has state; it is ignored"
    )

    const action = "PUSH"
    const location = createLocation(path, state, createKey(), history.location)

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
      if (!ok)
        return

      const href = createHref(location)  // 拼接basename
      const { key, state } = location

      if (canUseHistory) {
        globalHistory.pushState({ key, state }, null, href) // 只是改變地址欄路徑 此時(shí)頁(yè)面不會(huì)改變

        if (forceRefresh) {
          window.location.href = href // 強(qiáng)制刷新
        } else {
          const prevIndex = allKeys.indexOf(history.location.key) // 上次訪問(wèn)的路徑的key
          const nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1)

          nextKeys.push(location.key) // 維護(hù)一個(gè)訪問(wèn)過(guò)的路徑的key的列表
          allKeys = nextKeys

          setState({ action, location }) // render頁(yè)面
        }
      } else {
        warning(
          state === undefined,
          "Browser history cannot push state in browsers that do not support HTML5 history"
        )

        window.location.href = href
      }
    })
  }

  const replace = (path, state) => {
    warning(
      !(typeof path === "object" && path.state !== undefined && state !== undefined),
      "You should avoid providing a 2nd state argument to replace when the 1st " +
      "argument is a location-like object that already has state; it is ignored"
    )

    const action = "REPLACE"
    const location = createLocation(path, state, createKey(), history.location)

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
      if (!ok)
        return

      const href = createHref(location)
      const { key, state } = location

      if (canUseHistory) {
        globalHistory.replaceState({ key, state }, null, href)

        if (forceRefresh) {
          window.location.replace(href)
        } else {
          const prevIndex = allKeys.indexOf(history.location.key)

          if (prevIndex !== -1)
            allKeys[prevIndex] = location.key

          setState({ action, location })
        }
      } else {
        warning(
          state === undefined,
          "Browser history cannot replace state in browsers that do not support HTML5 history"
        )

        window.location.replace(href)
      }
    })
  }

  const go = (n) => {
    globalHistory.go(n)
  }

  const goBack = () =>
    go(-1)

  const goForward = () =>
    go(1)

  let listenerCount = 0
   // 防止重復(fù)注冊(cè)監(jiān)聽(tīng),只有l(wèi)istenerCount == 1的時(shí)候才會(huì)進(jìn)行監(jiān)聽(tīng)事件
  const checkDOMListeners = (delta) => {
    listenerCount += delta

    if (listenerCount === 1) {
      addEventListener(window, PopStateEvent, handlePopState)

      if (needsHashChangeListener)
        addEventListener(window, HashChangeEvent, handleHashChange)
    } else if (listenerCount === 0) {
      removeEventListener(window, PopStateEvent, handlePopState)

      if (needsHashChangeListener)
        removeEventListener(window, HashChangeEvent, handleHashChange)
    }
  }
  // 默認(rèn)情況下不會(huì)阻止路由的跳轉(zhuǎn)
  let isBlocked = false
  // 這里的block方法專門(mén)為Prompt組件設(shè)計(jì),開(kāi)發(fā)者可以模擬對(duì)路由的攔截
  const block = (prompt = false) => {
      // prompt 默認(rèn)為false, prompt可以為string或者func
      // 將攔截器的開(kāi)關(guān)打開(kāi),并返回可關(guān)閉攔截器的方法
    const unblock = transitionManager.setPrompt(prompt)
      // 監(jiān)聽(tīng)事件只會(huì)當(dāng)攔截器開(kāi)啟時(shí)被注冊(cè),同時(shí)設(shè)置isBlock為true,防止多次注冊(cè)
    if (!isBlocked) {
      checkDOMListeners(1)
      isBlocked = true
    }
     // 返回關(guān)閉攔截器的方法
    return () => {
      if (isBlocked) {
        isBlocked = false
        checkDOMListeners(-1)
      }

      return unblock()
    }
  }

  const listen = (listener) => {
    const unlisten = transitionManager.appendListener(listener) // 添加訂閱者
    checkDOMListeners(1) // 監(jiān)聽(tīng)popState pushState 等事件

    return () => {
      checkDOMListeners(-1)
      unlisten()
    }
  }

  const history = {
    length: globalHistory.length,
    action: "POP",
    location: initialLocation,
    createHref,
    push,
    replace,
    go,
    goBack,
    goForward,
    block,
    listen
  }

  return history
}

由于篇幅過(guò)長(zhǎng),所以這里抽取push方法來(lái)梳理整套流程

  const push = (path, state) => {
      // push可接收兩個(gè)參數(shù),第一個(gè)參數(shù)path可以是字符串,或者對(duì)象,第二個(gè)參數(shù)是state對(duì)象
      // 里面是可以被瀏覽器緩存的數(shù)據(jù),當(dāng)path是一個(gè)對(duì)象并且path中的state存在,同時(shí)也傳遞了
      // 第二個(gè)參數(shù)state,那么這里就會(huì)給出警告,表示path中的state參數(shù)將會(huì)被忽略
      
    warning(
      !(typeof path === "object" && path.state !== undefined && state !== undefined),
      "You should avoid providing a 2nd state argument to push when the 1st " +
      "argument is a location-like object that already has state; it is ignored"
    )

     const action = "PUSH" // 動(dòng)作為push操作
     //將即將訪問(wèn)的路徑path, 被緩存的state,將要訪問(wèn)的路徑的隨機(jī)生成的6位隨機(jī)字符串,
     // 上次訪問(wèn)過(guò)的location對(duì)象也可以理解為當(dāng)前地址欄里路徑對(duì)象,  
     // 返回一個(gè)對(duì)象包含 pathname,search,hash,state,key
    const location = createLocation(path, state, createKey(), history.location)
     // 路由的切換,最后一個(gè)參數(shù)為回調(diào)函數(shù),只有返回true的時(shí)候才會(huì)進(jìn)行路由的切換
    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
      if (!ok)
        return
      
      const href = createHref(location)  // 拼接basename
      const { key, state } = location  // 獲取新的key和state

      if (canUseHistory) {
          // 當(dāng)可以使用history api時(shí)候,調(diào)用原生的pushState方法更改地址欄路徑
          // 此時(shí)只是改變地址欄路徑 頁(yè)面并不會(huì)發(fā)生變化 需要手動(dòng)setState從而rerender
        // pushState的三個(gè)參數(shù)分別為,1.可以被緩存的state對(duì)象,即刷新瀏覽器依然會(huì)保留
        // 2.頁(yè)面的title,可直接忽略 3.href即新的地址欄路徑,這是一個(gè)完整的路徑地址
        globalHistory.pushState({ key, state }, null, href) 
        
        if (forceRefresh) { 
          window.location.href = href // 強(qiáng)制刷新
        } else {
          // 獲取上次訪問(wèn)的路徑的key在記錄列表里的下標(biāo)
          const prevIndex = allKeys.indexOf(history.location.key)
          // 當(dāng)下標(biāo)存在時(shí),返回截取到當(dāng)前下標(biāo)的數(shù)組key列表的一個(gè)新引用,不存在則返回一個(gè)新的空數(shù)組
          // 這樣做的原因是什么?為什么不每次訪問(wèn)直接向allKeys列表中直接push要訪問(wèn)的key
          // 比如這樣的一種場(chǎng)景, 1-2-3-4 的頁(yè)面訪問(wèn)順序,這時(shí)候使用go(-2) 回退到2的頁(yè)面,假如在2
          // 的頁(yè)面我們選擇了push進(jìn)行跳轉(zhuǎn)到4頁(yè)面,如果只是簡(jiǎn)單的對(duì)allKeys進(jìn)行push操作那么順序就變成了
          // 1-2-3-4-4,這時(shí)候就會(huì)產(chǎn)生一悖論,從4頁(yè)面跳轉(zhuǎn)4頁(yè)面,這種邏輯是不通的,所以每當(dāng)push或者replace
          // 發(fā)生的時(shí)候,一定是用當(dāng)前地址欄中path的key去截取allKeys中對(duì)應(yīng)的訪問(wèn)記錄,來(lái)保證不會(huì)push
          // 連續(xù)相同的頁(yè)面
          const nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1)

          nextKeys.push(location.key) // 將新的key添加到allKeys中
          allKeys = nextKeys // 替換

          setState({ action, location }) // render頁(yè)面
        }
      } else {
        warning(
          state === undefined,
          "Browser history cannot push state in browsers that do not support HTML5 history"
        )

        window.location.href = href
      }
    })
  }

createLocation的源碼

export const createLocation = (path, state, key, currentLocation) => {
  let location
  if (typeof path === "string") {
    // Two-arg form: push(path, state)
    // 分解pathname,path,hash,search等,parsePath返回一個(gè)對(duì)象
    location = parsePath(path)
    location.state = state 
  } else {
    // One-arg form: push(location)
    location = { ...path }

    if (location.pathname === undefined)
      location.pathname = ""

    if (location.search) {
      if (location.search.charAt(0) !== "?")
        location.search = "?" + location.search
    } else {
      location.search = ""
    }

    if (location.hash) {
      if (location.hash.charAt(0) !== "#")
        location.hash = "#" + location.hash
    } else {
      location.hash = ""
    }

    if (state !== undefined && location.state === undefined)
      location.state = state
  }

  // 嘗試對(duì)pathname進(jìn)行decodeURI解碼操作,失敗時(shí)進(jìn)行提示
  try {
    location.pathname = decodeURI(location.pathname)
  } catch (e) {
    if (e instanceof URIError) {
      throw new URIError(
        "Pathname "" + location.pathname + "" could not be decoded. " +
        "This is likely caused by an invalid percent-encoding."
      )
    } else {
      throw e
    }
  }

  if (key)
    location.key = key

  if (currentLocation) {
    // Resolve incomplete/relative pathname relative to current location.
    if (!location.pathname) {
      location.pathname = currentLocation.pathname
    } else if (location.pathname.charAt(0) !== "/") {
      location.pathname = resolvePathname(location.pathname, currentLocation.pathname)
    }
  } else {
    // When there is no prior location and pathname is empty, set it to /
    // pathname 不存在的時(shí)候返回當(dāng)前路徑的根節(jié)點(diǎn)
    if (!location.pathname) {
      location.pathname = "/"
    }
  }

  // 返回一個(gè)location對(duì)象包含
  // pathname,search,hash,state,key
  return location
}

createTransitionManager.js的源碼

import warning from "warning"

const createTransitionManager = () => {
  // 這里使一個(gè)閉包環(huán)境,每次進(jìn)行路由切換的時(shí)候,都會(huì)先進(jìn)行對(duì)prompt的判斷
  // 當(dāng)prompt != null 的時(shí)候,表示路由的上次切換被阻止了,那么當(dāng)用戶confirm返回true
  // 的時(shí)候會(huì)直接進(jìn)行地址欄的更新和subscribe的回調(diào)
  let prompt = null // 提示符
  
  const setPrompt = (nextPrompt) => {
      // 提示prompt只能存在一個(gè)
    warning(
      prompt == null,
      "A history supports only one prompt at a time"
    )

    prompt = nextPrompt
     // 同時(shí)將解除block的方法返回
    return () => {
      if (prompt === nextPrompt)
        prompt = null
    }
  }
  // 
  const confirmTransitionTo = (location, action, getUserConfirmation, callback) => {
    // TODO: If another transition starts while we"re still confirming
    // the previous one, we may end up in a weird state. Figure out the
    // best way to handle this.
    if (prompt != null) {
      // prompt 可以是一個(gè)函數(shù),如果是一個(gè)函數(shù)返回執(zhí)行的結(jié)果
      const result = typeof prompt === "function" ? prompt(location, action) : prompt
       // 當(dāng)prompt為string類型時(shí) 基本上就是為了提示用戶即將要跳轉(zhuǎn)路由了,prompt就是提示信息
      if (typeof result === "string") {
          // 調(diào)用window.confirm來(lái)顯示提示信息
        if (typeof getUserConfirmation === "function") {
            // callback接收用戶 選擇了true或者false
          getUserConfirmation(result, callback)
        } else {
            // 提示開(kāi)發(fā)者 getUserConfirmatio應(yīng)該是一個(gè)function來(lái)展示阻止路由跳轉(zhuǎn)的提示
          warning(
            false,
            "A history needs a getUserConfirmation function in order to use a prompt message"
          )
          // 相當(dāng)于用戶選擇true 不進(jìn)行攔截
          callback(true)
        }
      } else {
        // Return false from a transition hook to cancel the transition.
        callback(result !== false)
      }
    } else {
        // 當(dāng)不存在prompt時(shí),直接執(zhí)行回調(diào)函數(shù),進(jìn)行路由的切換和rerender
      callback(true)
    }
  }
   // 被subscribe的列表,即在Router組件添加的setState方法,每次push replace 或者 go等操作都會(huì)觸發(fā)
  let listeners = []
  // 將回調(diào)函數(shù)添加到listeners,一個(gè)發(fā)布訂閱模式
  const appendListener = (fn) => {
    let isActive = true
     // 這里有個(gè)奇怪的地方既然訂閱事件可以被解綁就直接被從數(shù)組中刪除掉了,為什么這里還需要這個(gè)isActive
     // 再加一次判斷呢,其實(shí)是為了避免一種情況,比如注冊(cè)了多個(gè)listeners: a,b,c 但是在a函數(shù)中注銷(xiāo)了b函數(shù)
     // 理論上來(lái)說(shuō)b函數(shù)應(yīng)該不能在執(zhí)行了,但是注銷(xiāo)方法里使用的是數(shù)組的filter,每次返回的是一個(gè)新的listeners引用,
     // 故每次解綁如果不添加isActive這個(gè)開(kāi)關(guān),那么當(dāng)前循環(huán)還是會(huì)執(zhí)行b的事件。加上isActive后,原始的liteners中
     // 的閉包b函數(shù)的isActive會(huì)變?yōu)閒alse,從而阻止事件的執(zhí)行,當(dāng)循環(huán)結(jié)束后,原始的listeners也會(huì)被gc回收
    const listener = (...args) => {
      if (isActive)
        fn(...args)
    }

    listeners.push(listener)
     
    return () => {
      isActive = false
      listeners = listeners.filter(item => item !== listener)
    }
  }
  // 通知被訂閱的事件開(kāi)始執(zhí)行
  const notifyListeners = (...args) => {
    listeners.forEach(listener => listener(...args))
  }

  return {
    setPrompt,
    confirmTransitionTo,
    appendListener,
    notifyListeners
  }
}

export default createTransitionManager

由于篇幅太長(zhǎng),自己都看的蒙圈了,現(xiàn)在就簡(jiǎn)單做一下總結(jié),描述router工作的原理。
1.首先BrowserRouter通過(guò)history庫(kù)使用createBrowserHistory方法創(chuàng)建了一個(gè)history對(duì)象,并將此對(duì)象作為props傳遞給了Router組件
2.Router組件使用history對(duì)的的listen方法,注冊(cè)了組件自身的setState事件,這樣一樣來(lái),只要觸發(fā)了html5的popstate事件,組件就會(huì)執(zhí)行setState事件,完成整個(gè)應(yīng)用的rerender
3.history是一個(gè)對(duì)象,里面包含了操作頁(yè)面跳轉(zhuǎn)的方法,以及當(dāng)前地址欄對(duì)象的location的信息。首先當(dāng)創(chuàng)建一個(gè)history對(duì)象時(shí)候,會(huì)使用props當(dāng)中的四個(gè)參數(shù)信息,forceRefresh、basename、getUserComfirmation、keyLength 來(lái)生成一個(gè)初始化的history對(duì)象,四個(gè)參數(shù)均不是必傳項(xiàng)。首先會(huì)使用window.location對(duì)象獲取當(dāng)前路徑下的pathname、search、hash等參數(shù),同時(shí)如果頁(yè)面是經(jīng)過(guò)rerolad刷新過(guò)的頁(yè)面,那么也會(huì)保存之前向state添加過(guò)數(shù)據(jù),這里除了我們自己添加的state,還有history這個(gè)庫(kù)自己每次做push或者repalce操作的時(shí)候隨機(jī)生成的六位長(zhǎng)度的字符串key
拿到這個(gè)初始化的location對(duì)象后,history開(kāi)始封裝push、replace、go等這些api。
以push為例,可以接收兩個(gè)參數(shù)push(path, state)----我們常用的寫(xiě)法是push("/user/list"),只需要傳遞一個(gè)路徑不帶參數(shù),或者push({pathname: "/user", state: {id: "xxx"}, search: "?name=xxx", hash: "#list"})傳遞一個(gè)對(duì)象。任何對(duì)地址欄的更新都會(huì)經(jīng)過(guò)confirmTransitionTo 這個(gè)方法進(jìn)行驗(yàn)證,這個(gè)方法是為了支持prompt攔截器的功能。正常在攔截器關(guān)閉的情況下,每次調(diào)用push或者replace都會(huì)隨機(jī)生成一個(gè)key,代表這個(gè)路徑的唯一hash值,并將用戶傳遞的state和key作為state,注意這部分state會(huì)被保存到 瀏覽器 中是一個(gè)長(zhǎng)效的緩存,將拼接好的path作為傳遞給history的第三個(gè)參數(shù),調(diào)用history.pushState(state, null, path),這樣地址欄的地址就得到了更新。
地址欄地址得到更新后,頁(yè)面在不使用foreceRefrsh的情況下是不會(huì)自動(dòng)更新的。此時(shí)需要循環(huán)執(zhí)行在創(chuàng)建history對(duì)象時(shí),在內(nèi)存中的一個(gè)listeners監(jiān)聽(tīng)隊(duì)列,即在步驟2中在Router組件內(nèi)部注冊(cè)的回調(diào),來(lái)手動(dòng)完成頁(yè)面的setState,至此一個(gè)完整的更新流程就算走完了。
在history里有一個(gè)block的方法,這個(gè)方法的初衷是為了實(shí)現(xiàn)對(duì)路由跳轉(zhuǎn)的攔截。我們知道瀏覽器的回退和前進(jìn)操作按鈕是無(wú)法進(jìn)行攔截的,只能做hack,這也是history庫(kù)的做法。抽離出了一個(gè)路徑控制器,方法名稱叫做createTransitionManager,可以理解為路由操作器。這個(gè)方法在內(nèi)部維護(hù)了一個(gè)prompt的攔截器開(kāi)關(guān),每當(dāng)這個(gè)開(kāi)關(guān)打開(kāi)的時(shí)候,所有的路由在跳轉(zhuǎn)前都會(huì)被window.confirm所攔截。注意此攔截并非真正的攔截,雖然頁(yè)面沒(méi)有改變,但是地址欄的路徑已經(jīng)改變了。如果用戶沒(méi)有取消攔截,那么頁(yè)面依然會(huì)停留在當(dāng)前頁(yè)面,這樣和地址欄的路徑就產(chǎn)生了悖論,所以需要將地址欄的路徑再重置為當(dāng)前頁(yè)面真正渲染的頁(yè)面。為了實(shí)現(xiàn)這一功能,不得不創(chuàng)建了一個(gè)用隨機(jī)key值的來(lái)表示的訪問(wèn)過(guò)的路徑表allKeys。每次頁(yè)面被攔截后,都需要在allKeys的列表中找到當(dāng)前路徑下的key的下標(biāo),以及實(shí)際頁(yè)面顯示的location的key的下標(biāo),后者減前者的值就是頁(yè)面要被回退或者前進(jìn)的次數(shù),調(diào)用go方法后會(huì)再次觸發(fā)popstate事件,造成頁(yè)面的rerender。
正式因?yàn)橛辛薖rompt組件才會(huì)使history不得不增加了key列表,prompt開(kāi)關(guān),導(dǎo)致代碼的復(fù)雜度成倍增加,同時(shí)很多開(kāi)發(fā)者在開(kāi)發(fā)中對(duì)此組件的濫用也導(dǎo)致了一些特殊的bug,并且這些bug都是無(wú)法解決的,這也是作者為什么想要在下個(gè)版本中移除此api的緣由。討論地址在鏈接描述

。下篇將會(huì)進(jìn)行對(duì)Route Switch Link等其他組件的講解

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/53901.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ù)端等常見(jiàn)場(chǎng)景下的路由切換功能,react-router本身不具備切換和跳轉(zhuǎn)路由的功能,這些功能全部由react-router依賴的history庫(kù)完成,his...

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

    摘要:如果將添加到當(dāng)前組件,并且當(dāng)前組件由包裹,那么將引用最外層包裝組件的實(shí)例而并非我們期望的當(dāng)前組件,這也是在實(shí)際開(kāi)發(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é)合官方文...

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

    摘要:如果將添加到當(dāng)前組件,并且當(dāng)前組件由包裹,那么將引用最外層包裝組件的實(shí)例而并非我們期望的當(dāng)前組件,這也是在實(shí)際開(kāi)發(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元查看
<