react-router 原理來了

react-router 秉承一切皆元件,因此實現的核心就是BrowserRouter,Route,Link;

BrowserRouter:歷史記錄管理物件history初始化及向下傳遞,location變更監聽;

核心思想: 但凡你想把一個值從上往下傳遞;你就想建立一個上下文;但是建立上下文太麻煩;所以

穿建

一個父元件包圍起來

//建立上下文 相對獨立 Provider Consumerconst Context = React。createContext()//獲取Provider 和 comsumer 一切皆元件的思想const Provider = Context。Providerconst Consumer = Context。Consumer {value=>} {value=>} {value=>}

//工具函式 match (path) 返回match 物件import pathToRegexp from “path-to-regexp”;const cache = {};const cacheLimit = 10000;let cacheCount = 0;// 轉換path為正則和關鍵字陣列// pathToRegexp的作用 detail/:name <==>/detail/:name vue 同樣有function compilePath(path, options) { const cacheKey = `${options。end}${options。strict}${options。sensitive}`; const pathCache = cache[cacheKey] || (cache[cacheKey] = {}); if (pathCache[path]) return pathCache[path]; const keys = []; const regexp = pathToRegexp(path, keys, options); const result = { regexp, keys }; if (cacheCount < cacheLimit) { pathCache[path] = result; cacheCount++; } return result;}/** * 匹配pathname和path。 */function matchPath(pathname, options = {}) { if (typeof options === “string”) options = { path: options }; const { path, exact = false, strict = false, sensitive = false } = options; const paths = []。concat(path); // 轉換path為match return paths。reduce((matched, path) => { if (!path) return null; if (matched) return matched; // 轉換path為正則和佔位符陣列 const { regexp, keys } = compilePath(path, { end: exact, strict, sensitive }); // 獲得正則匹配陣列 const match = regexp。exec(pathname); if (!match) return null; // 結構出匹配url和值陣列 const [url, 。。。values] = match; const isExact = pathname === url; if (exact && !isExact) return null; //match 物件 return { path, // 待匹配path url: path === “/” && url === “” ? “/” : url, // url匹配部分 isExact, // 精確匹配 params: keys。reduce((memo, key, index) => { // 引數 memo[key。name] = values[index]; return memo; }, {}) }; }, null);}export default matchPath;

//很精妙 就考驗基本功import React, { Component } from ‘react’import {createBrowserHistory } from ‘history’import matchPath from ‘。/matchPath’//建立一個上下文儲存history,location 等 這個地方有點像store裡的state似的const RouterContext = React。createContext()//Router: 管理歷史記錄變更,location變更等等,並傳遞給後代class BrowserRouter extends Component { constructor(props){ super(props) // 建立瀏覽器history物件 this。history = createBrowserHistory(this。props) // 建立狀態 管理location this。state = { location:this。history。location } //開啟監聽 下面的所有子元件 都會更新 this。unlisten = this。history。listen(location=>{ this。setState({location}) }) } componentWillUnmount(){ //解除安裝的時候釋放監聽 if(this。unlisten){ this。unlisten() } } render(){ return ( ) }}

//根據傳參配置(path,render,componet,children 之間有競爭關係的) 渲染出元件class Route extends Component{ render(){ return ( {/* 這裡的context 就是 provider 的value */} {context=>{ const location = context。location //根據pathname和使用者傳遞的props獲得mach物件 const match = matchPath(location。pathname,this。props) //傳遞一些引數 const props = {。。。context,match}; //children > component > render //3者之間的競爭關係 // 如果 path 匹配url 的情況: component 和 render 都會執行 // 但是children 是匹不匹配 都會執行 let {children , component , render } = this。props if(children && typeof children === ‘function’){ children = children(props) } return ( // 提高上下文的優先順序;從裡往外找;把更新過得props三兄弟傳給Context上下文;只是Provider 的value,之前的value改變不了 { children // children 優先順序最高,無論匹配與否都執行 ?children :props。match // 後面的component和render必須匹配 ?component //若匹配首先查詢component ?React。createElement(component) // 若它存在渲染之 :render // 若render選項存在 ?render(props) // 按render渲染結果 :null :null } ) }} ) }}

//linkclass Link extends React。Component { handleClick(event, history) { event。preventDefault(); history。push(this。props。to); } render() { const { to, 。。。rest } = this。props; return ( {/* match location history */} {context => { return ( this。handleClick(event, context。history)} href={to} > {this。props。children} ); }} ); }}

export class MyRouterTest extends Component { render() { return ( foo bar mua

foo
}>
bar
}>
match。params。ns} /> ‘xxx’ }> ) }}