日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

react封装函数_React-Router源码解读

發(fā)布時間:2024/9/19 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 react封装函数_React-Router源码解读 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

起因

目前負責的項目中有一個微信網頁,用的是react技術棧。在該項目中增加了一個微信分享功能后,線上ios出現了問題,經排查,定位到了react的路由系統(tǒng)。

這次線上bug,讓我決定,先從react-router-dom開始,看看它內部實現了什么。

前端目前用到的就是react-router-dom這個庫,它提供了兩個高級路由器,分別是BrowserRouter和HashRouter,它兩的區(qū)別就是一個用的history API ,一個是使用URL的hash部分,接下來我以BrowserRouter為例,做一個解讀。

簡易過程圖

解讀(只摘取核心代碼進行展示)

首先看看整個react-router-dom提供了點啥?

export {MemoryRouter,Prompt,Redirect,Route,Router,StaticRouter,Switch,generatePath,matchPath,withRouter,useHistory,useLocation,useParams,useRouteMatch } from "react-router";export { default as BrowserRouter } from "./BrowserRouter.js"; export { default as HashRouter } from "./HashRouter.js"; export { default as Link } from "./Link.js"; export { default as NavLink } from "./NavLink.js";

除了下面它自己實現的四個組件外,其余的都是將react-router提供的組件做了一個引入再導出,那看來底層核心的東西還是在react-router上。

1.先從一個簡單demo開始

import { BrowserRouter, Route, Switch, Link } from "react-router-dom"function App() {return (<BrowserRouter><div>主菜單</div><Link to="/home">home</Link><br /><Link to="/search">search</Link><hr /><Switch> <Route path="/home" component={Home} /><Route path="/search" component={Search} /></Switch></BrowserRouter>) }ReactDOM.render(<App />, document.getElementById('root'));

需要通過路由跳轉來實現UI變化的組件,要用BrowserRouter作為一個根組件來包裹起來,Route用來盛放頁面級的組件。

那按照這種層級關系,我們先來看下BrowersRouter里實現了什么功能。

2. BrowersRouter

import { Router } from "react-router"; import { createBrowserHistory as createHistory } from "history";class BrowserRouter extends React.Component {history = createHistory(this.props);render() {return <Router history={this.history} children={this.props.children} />;} }

非常少量的幾行代碼,很清晰的看到,核心點是history這個庫所提供的函數。組件在render前執(zhí)行了createHistory這個函數,然后它會返回一個history的對象實例,然后通過props傳給Router這個路由器,另外其中包裹的所有子組件,統(tǒng)統(tǒng)傳給Router。

這里其實官網上已經說的很清楚,大家用的時候可以多留意下。

那么思路就很清楚,重點放在Router和history庫上,看看Router是怎么用這個history對象的,以及這個history對象里又包含了啥,和window.history有什么區(qū)別?讓我們接著往下走。

3. Router

import HistoryContext from "./HistoryContext.js"; import RouterContext from "./RouterContext.js";

Router是核心的路由器,上面我們已經看到BrowsRouter傳遞給它了一個history對象。

首先引入了兩個context,這里其實就是創(chuàng)建的普通context,只不過擁有特定的名稱而已。

它的內部實現是這樣

const createNamedContext = name => {const context = createContext();context.displayName = name;return context; };// 上述的引用就相當于 HistoryContext = createNamedContext("Router-History")

引入了這兩個context后,在來看它的構造函數。

constructor(props) {super(props);this.state = {location: props.history.location};this.unlisten = props.history.listen(location => {this.setState({ location });});}

Router組件維護了一個內部狀態(tài)location對象,初始值為上面提到的在BrowsRouter中創(chuàng)建的history提供的。

之后,執(zhí)行了history對象提供的listen函數,這個函數需要一個回調函數作為入參,傳入的回調函數的功能就是來更新當前Router內部狀態(tài)中的location的,關于什么時候會執(zhí)行這個回調,以及l(fā)isten函數,后面會詳細剖析。

componentWillUnmount() {if (this.unlisten) {this.unlisten();}}

等這個Router組件將要卸載時,就取消對history的監(jiān)聽。

render() {return (<RouterContext.Providervalue={{history: this.props.history,location: this.state.location,match: Router.computeRootMatch(this.state.location.pathname),staticContext: this.props.staticContext}}> <HistoryContext.Providerchildren={this.props.children || null}value={this.props.history}/> </RouterContext.Provider>);}

最后生成的react樹,就是由最開始引入的context組成的,然后傳入history、location這些值。

總結就是整個Router就是一個傳入了history、locaiton和其它一些數據的context的提供者,然后它的子組件作為消費者就可以共享使用這些數據,來完成后面的路由跳轉、UI更新等動作。

3. histroy庫

在Router組件可以看到已經用到了createBrowserHistory函數返回的history實例了,如:history.location和history.listen,這個庫里的封裝的函數那是相當多了,細節(jié)也很多,我仍然挑最重要的解讀。

首先是咱們這個出鏡率較高的history提供了哪些屬性和方法

看起來都是些熟悉的東西,如push、replace、go這些,都是window對象屬性history所提供的。但有些屬性其實是重寫了的,如push、replace,其它的是做了一個簡單封裝。

function goBack() {go(-1);}function goForward() {go(1);}

Router內部狀態(tài)location的初始數據,是使用window.location與window.history.state做的重組。

路由系統(tǒng)最為重要的兩個切換頁面動作,一個是push,一個是replace,我們平時只用Link組件的話,并沒有確切的感受,其中Link接受一個props屬性,to :string 或者to : object

<link to='/course'>跳轉</link>

此時點擊它時,調用的就是props.history中重寫的push方法。

<Link to='/course' replace>跳轉</Link>

如果增加replace屬性,則用的就是replace方法

這兩個方法主要用的是pushState和replaceState這兩個API,它們提供的能力就是可以增加新的window.history中的歷史記錄和瀏覽器地址欄上的url,但是又不會發(fā)起真正的網絡請求。

這是實現單頁面應用的關鍵點。

然后讓我們看一下這兩個路由跳轉方法

精簡后,代碼還是不少,我解讀下。

push中的入參path,是接下來準備要跳轉的路由地址。createLocation方法先將這個path,與當前的location做一個合并,返回一個更新的loation。

然后就是重頭戲,transitionManager這個對象,讓我們先關注下成功回調里面的內容。

通過更新后的location,創(chuàng)建出將要跳轉的href,然后調用pushState方法,來更新window.history中的歷史記錄。

如果你在BrowserRouter中傳了forceRefresh這個屬性,那么之后就會直接修改window.lcoation.href,來實現頁面跳轉,但這樣就相當于要重新刷新來進行網絡請求你的文件資源了。

如果沒有傳的話,就是調用setState這個函數,注意這個setState并不是react提供的那個,而是history庫自己實現的。

function setState(nextState) {history.length = globalHistory.length;transitionManager.notifyListeners(history.location, history.action);}

還是用到了transitionManager對象的一個方法。

另外當我們執(zhí)行了pushState后,接下來所獲取到的window.history都是已經更新的了。

接下來就剩transitionManager這最后的一個點了。

transitionManager是通過createTransitionManager這個函數實例出的一個對象

function createTransitionManager() {var listeners = [];function appendListener(fn) {var isActive = true;function listener() {if (isActive) fn.apply(void 0, arguments);}listeners.push(listener);return function () {isActive = false;listeners = listeners.filter(function (item) {return item !== listener;});};}function notifyListeners() {for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key];}listeners.forEach(function (listener) {return listener.apply(void 0, args);});}return { appendListener: appendListener, notifyListeners: notifyListeners };}

還記的開始時我們在Router組件中已經用過一個history.listen方法,其中內部實現就是用的transitionManager.appendListener方法

function listen(listener) {var unlisten = transitionManager.appendListener(listener);checkDOMListeners(1);return function () {checkDOMListeners(-1);unlisten();};}

當時我們給listen傳入了一個回調函數,這個回調函數是用來通過React的setState來更新組件內部狀態(tài)的locaton數據,然后又因為這個lcoation傳入了Router-context的value中,所以當它發(fā)生變化時,所有的消費組件,都會重新render,以此來達到更新UI的目的。

listen的執(zhí)行細節(jié)是,把它的入參函數(這里指更新Rrouter的state.location的函數)會傳入到appendListener中。

執(zhí)行appendListener后,appendListener將這個入參函數推到listeners這個數組中,保存起來。然后返回一個函數用來刪除掉推進該數組的那個函數,以此來實現取消監(jiān)聽的功能。

所以當我們使用push,切換路由時,它會執(zhí)行notifyListeners并傳入更新的location。

然后就是遍歷listeners,執(zhí)行我們在listen傳入的回調,此時就是最終的去更新Router的location的過程了。

后面的流程,簡單說下,Router里面的Route組件通過匹配pathname 和 更新的location ,來決定是否渲染該頁面組件,到此整個的路由跳轉的過程就結束了。

總結

第一次閱讀源碼,盡管刪減了很多,但還是寫了不少。

希望大家可以沿著這個思路,自己也去看看,還是有很多細節(jié)值得推敲的。

總結

以上是生活随笔為你收集整理的react封装函数_React-Router源码解读的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。