使用React開發稍微復雜一點的應用,React Router幾乎是路由管理的唯一選擇。雖然React Router經歷了4個大版本的更新,功能也越來越豐富,但無論怎么變,它的核心依賴history庫卻一直沒變。下面我們來了解下這個在github上有4k+星的庫到底提供了什么功能。
聊到history庫,是不是覺得這個單詞有點熟悉?不錯,HTML5規范里面,也新增了一個同名的history對象。下面我們來看下這個history對象用來解決什么問題。
在jQuery統治前端的年代,通過ajax請求無刷新更新頁面是當時相當流行的頁面處理方式,SPA的雛形就是那時候演化出來的。為了標示頁面發生的變化,方便刷新后依然能顯示正確的頁面元素,一般會通過改變url的hash值來唯一定位頁面。但這會帶來另一個問題:用戶無法使用前進/后退來切換頁面。
為了解決這個問題,history對象應運而生。當頁面的url或者hash發生變化的時候,瀏覽器會自動將新的url push到history對象中。history對象內部會維護一個state數組,記錄url的變化。在瀏覽器進行前進/后退操作的時候,實際上就是調用history對象的對應方法(forward
/back
),取出對應的state,從而進行頁面的切換。
除了操作url,history對象還提供2個不用通過操作url也能更新內部state的方法,分別是pushState
和replaceState
。還能將額外的數據存到state中,然后在onpopstate
事件中再通過event.state
取出來。如果希望對history對象作更深入的理解,可以參考 這里,和這里。
我們再回過頭來看history庫。它本質上做了以下4件事情:
借鑒HTML5 history對象的理念,在其基礎上又擴展了一些功能
提供3種類型的history:browserHistory,hashHistory,memoryHistory,并保持統一的api
支持發布/訂閱功能,當history發生改變的時候,可以自動觸發訂閱的函數
提供跳轉攔截、跳轉確認和basename等實用功能
再對比一些兩者api的異同。以下是history庫的:
const history = { length, // 屬性,history中記錄的state的數量 action, // 屬性,當前導航的action類型 location, // 屬性,location對象,封裝了pathname、search和hash等屬性 push, // 方法,導航到新的路由,并記錄在history中 replace, // 方法,替換掉當前記錄在history中的路由信息 go, // 方法,前進或后退n個記錄 goBack, // 方法,后退 goForward, // 方法,前進 canGo, // 方法,是否能前進或后退n個記錄 block, // 方法,跳轉前讓用戶確定是否要跳轉 listen // 方法,訂閱history變更事件 };
以下是HTML5 history對象的:
const history = { length, // 屬性,history中記錄的state的數量 state, // 屬性,pushState和replaceState時傳入的對象 back, // 方法,后退 forward, // 方法,前進 go, // 方法,前進或后退n個記錄 pushState, // 方法,導航到新的路由,并記錄在history中 replaceState // 方法,替換掉當前記錄在history中的路由信息 } // 訂閱history變更事件 window.onpopstate = function (event) { ... }
從對比中可以看出,兩者的關系是非常密切的,history庫可以說是history對象的超集,是功能更強大的history對象。
下面,我們以三種history類型中的一種,hashHistory為例,來分析下history的源碼,看看它都干了些什么。先看下它是怎么處理hash變更的。
// 構造hashHistory對象 const createHashHistory = (props = {}) => { ... const globalHistory = window.history; // 引用HTML5 history對象 ... // transitionManager負責控制是否進行跳轉,以及跳轉后要通知到的訂閱者,后面會詳細討論 const transitionManager = createTransitionManager(); ... // 注冊history變更回調的訂閱者 const listen = listener => { const unlisten = transitionManager.appendListener(listener); checkDOMListeners(1); return () => { checkDOMListeners(-1); unlisten(); }; }; // 監聽hashchange事件 const checkDOMListeners = delta => { listenerCount += delta; if (listenerCount === 1) { window.addEventListener(HashChangeEvent, handleHashChange); } else if (listenerCount === 0) { window.removeEventListener(HashChangeEvent, handleHashChange); } }; // hashchange事件回調 const handleHashChange = () => { ... // 構造內部使用的location對象,包含pathname、search和hash等屬性 const location = getDOMLocation(); ... handlePop(location); }; // 處理hash變更邏輯 const handlePop = location => { ... const action = "POP"; // 給用戶展示確認跳轉的信息(如果有的話),確認后通知訂閱者。如果用戶取消跳轉,則回退到之前狀態 transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => { if (ok) { setState({action, location}); // 確認后通知訂閱者 } else { revertPop(location); // 取消則回退到之前狀態 } }); }; // 更新action,location和length屬性,并通知訂閱者 const setState = nextState => { Object.assign(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; ... }
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com