前端路由庫的做用是改變地址欄,支持瀏覽器前進、後退,並同步路由對應的視圖,這裏以react-router及其依賴的history庫說一下路由機制
原文地址html
首先簡單介紹一下前端路由機制所依賴的pushState、popstate事件、hash及對應的hashChange事件前端
history.pushState(state,title,path) console.log(history.state) window.addEventListener('popstate',(e)=>{ console.log(e.state) })
對於不支持pushState方法的瀏覽器,能夠經過改變location.hash和藉助hashChange事件來實現路由功能html5
window.addEventListener('hashchange',e=>{ }) location.hash="test"
經過設置history.pushState(state,title,path),能夠給對應路由設置一個state,這就給路由之間的數據傳遞提供了一種新途徑,而且,state對象是保存在本地的,刷新頁面依然存在,但經過hash方式實現的路由就無法使用,react-router v4版本也去除了<HashRouter>對state的模擬react
history庫提供了三種不一樣的方法來建立history對象,這裏的history對象是對瀏覽器內置window.history方法的擴展,擴展了push,go,goBack,goForward等方法,並加入了location、listen字段
,並對非瀏覽器環境實現polyfillgit
createBrowserHistory() createHashHistory() createMemoryHistory()
react-router路由實現大致過程github
<Router></Router>
組件註冊的回調函數,<Router></Router>
組件註冊的回調函數<Router>
組件中註冊history.listen()回調函數,當路由有變化時,<Route>
組件中匹配location,同步UI分別來看api
在react中,咱們能夠調用history.push(path,state)來跳轉路由,實際執行的就是createBrowserHistory中的push方法瀏覽器
在這個方法中主要作三件事react-router
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
const action = "PUSH" setState({ action, location }); const setState = nextState => { Object.assign(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); };
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的路由機制就是
<Router></Router>
組件會註冊一個listener<Router></Router>
註冊的監聽函數內部會setState更新狀態<Router></Router>
的子組件<Route>
的componentWillReceiveProps生命週期函數中能獲得Router中context,根據當前path和瀏覽器當前location來判斷當前route是否match,匹配就渲染component雖然本文以react-router來介紹路由機制,但主流路由庫實現原理都差很少,藉助pushState或hash來更新url
,在對應的事件處理函數中來作視圖的同步
參考