從history api看主流框架的路由機制

前端路由庫的做用是改變地址欄,支持瀏覽器前進、後退,並同步路由對應的視圖,這裏以react-router及其依賴的history庫說一下路由機制

原文地址html

前提

首先簡單介紹一下前端路由機制所依賴的pushState、popstate事件、hash及對應的hashChange事件前端

  1. pushState,popstate
  • 對於支持html5 新增pushState、replaceState方法的瀏覽器,能夠經過設置pushState來在瀏覽器history棧中新增一條記錄
  • 設置pushState(),replaceState()時並不會觸發popstate事件,popstate事件只在點擊瀏覽器前進、後退按鈕或調用history.back()、history.forward()等時觸發
  • pushState()方法第一個參數能夠指定一個state對象,並經過history.state或popstate事件回調中event對象獲取
history.pushState(state,title,path)

console.log(history.state)

window.addEventListener('popstate',(e)=>{

    console.log(e.state)

})
  1. location.hash hashChange

對於不支持pushState方法的瀏覽器,能夠經過改變location.hash和藉助hashChange事件來實現路由功能html5

window.addEventListener('hashchange',e=>{

})

location.hash="test"
  1. 對比

經過設置history.pushState(state,title,path),能夠給對應路由設置一個state,這就給路由之間的數據傳遞提供了一種新途徑,而且,state對象是保存在本地的,刷新頁面依然存在,但經過hash方式實現的路由就無法使用,react-router v4版本也去除了<HashRouter>對state的模擬react

history庫介紹

history庫提供了三種不一樣的方法來建立history對象,這裏的history對象是對瀏覽器內置window.history方法的擴展,擴展了push,go,goBack,goForward等方法,並加入了location、listen字段,並對非瀏覽器環境實現polyfillgit

createBrowserHistory()
createHashHistory()
createMemoryHistory()

react-router的路由實現(BrowserRouter和createBrowserHistory)

react-router路由實現大致過程github

  1. 調用history.push跳轉路由時,內部執行window.history.pushState在瀏覽器history棧中新增一條記錄,改變url,執行<Router></Router>組件註冊的回調函數,
  2. createBrowserHistory中註冊popstate事件,用戶點擊瀏覽器前進、回退時,在popstate事件中獲取當前的event.state,從新組裝一個location,執行<Router></Router>組件註冊的回調函數
  3. history庫對外暴露createBrowserHistory方法,react-router中實例化createBrowserHistory方法對象,在<Router>組件中註冊history.listen()回調函數,當路由有變化時,<Route>組件中匹配location,同步UI

分別來看api

history.push

在react中,咱們能夠調用history.push(path,state)來跳轉路由,實際執行的就是createBrowserHistory中的push方法瀏覽器

在這個方法中主要作三件事react-router

  1. 根據傳遞的path,state參數建立一個location,不一樣於window.location,這裏的location只有這些屬性
location= {
      path:
      search:
      hash:
      state:
      key
    };
    const location = createLocation(path, state, createKey(), history.location);

這個location會在<Router><Route>組件中使用,來根據location中的值和<Route path='xxx'></Route>中的path匹配,匹配成功的Route組件渲染指定的componentdom

  1. 執行globalHistory.pushState({ key, state }, null, href);
  2. 執行Router中註冊的listener
const action = "PUSH"
setState({ action, location });

const setState = nextState => {
    Object.assign(history, nextState);

    history.length = globalHistory.length;

    transitionManager.notifyListeners(history.location, history.action);
};

history中對popstate事件的註冊

popstate事件觸發時,能夠獲得event.state,createBrowserHistory中會根據這個state和當前window.location從新生成一個location對象,執行Router組件註冊的listener,同步UI

const setState = nextState => {
    Object.assign(history, nextState);

    history.length = globalHistory.length;

    transitionManager.notifyListeners(history.location, history.action);
  };

const handlePop = location => {
    const action = "POP";
    setState({action,location)
}

<Router>與<Route>組件

BrowserRouter組件中會實例化一個createBrowserHistory對象,傳遞給Router組件

class BrowserRouter extends React.Component{

    history = createHistory(this.props);

    render() {
        return <Router history={this.history} children={this.props.children} />;
    }

}

在Router組件中要註冊history.listen()的一個監聽函數,而且保存一份子組件(Route)使用的數據

getChildContext() {
    return {
      router: {
        ...this.context.router,
        history: this.props.history,
        route: {
          location: this.props.history.location, //history中的location
          match: this.state.match
        }
      }
    };
  }
componentWillMount{
    this.unlisten = history.listen(() => {
        this.setState({
            match: this.computeMatch(history.location.pathname)
        });
    });
}

當調用history.push或觸發popstate事件時,這裏註冊的listener都會被createBrowserHistory執行,觸發setState,而後Router的子組件中匹配的<Route>會從新渲染,

<Router>
<Route path='/path1' compoent={}>
<Route path='/path2' compoent={}>
<Router>

在Route中有一個match狀態,在父組件props發生變化時會從新計算

state = {
    match: this.computeMatch(this.props, this.context.router)
  };

componentWillReceiveProps(nextProps, nextContext) {
    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    });
}
//computeMatch主要工做就是匹配當前組件上指定的path和當前瀏覽器的路徑是否一致,一致就渲染組件

render() {

    if (component) return match ? React.createElement(component, props) : null;

    if (render) return match ? render(props) : null;

}

總結

總結一下,react-router的路由機制就是

  1. 藉助history庫,history中實現了push,go,goBack等方法,註冊了popstate事件,當路由跳轉時,使用瀏覽器內置的history api 操做 history棧
  2. history庫對外暴露的history對象提供了listen方法,<Router></Router>組件會註冊一個listener
  3. 當調用hsitory.push或popstate事件觸發時,執行listener
  4. <Router></Router>註冊的監聽函數內部會setState更新狀態
  5. <Router></Router>的子組件<Route>的componentWillReceiveProps生命週期函數中能獲得Router中context,根據當前path和瀏覽器當前location來判斷當前route是否match,匹配就渲染component

雖然本文以react-router來介紹路由機制,但主流路由庫實現原理都差很少,藉助pushState或hash來更新url,在對應的事件處理函數中來作視圖的同步

參考

相關文章
相關標籤/搜索