React路由原理
- 不同的路由渲染不同的组件
- 有两种实现方法
- HashRouter: 利用hash实现路由切换
- BrowserRouter:实现h5 Api实现路由的切换
HashRouter
hash地址切换的时候页面不会刷新,使用window.location.hash读写hash地址,可以给浏览器造成一个跳转的假象。
比如我在#/a跳到了#/b,页面没有刷新,但是点击浏览器的回退按钮可以回退到#/a页面。
实现:
通过监听hash改变去渲染不同的页面。
BrowserRouter
1 history
H5规范提供了一个history接口,包括两个方法:history.pushState(),
history.replaceState()还有一个事件,window.onpopState。
-
pushState
- history.pushState(stateObject, title, url)
- 第一个参数适用于存储该url对应的状态对象,可以在onpopState事件中获取,也可以在history对象中获取。
- 第二个参数是标题,目前浏览器暂未实现
- 第三个参数是设定的url
- pushState函数向浏览器的历史堆栈中压入一个url为设定值的记录,并将历时堆栈的当前指针指至栈顶
-
replaceState
- 该接口与pushState参数相同,含义相同
- 唯一的区别在于,replaceState是替换浏览器历时堆栈的当前历史记录为设定的Url,就是会代替当前的Url
- 并且replaceState不会改动浏览器历史堆栈的当前指针。
-
onpopstate
- window的属性
- 会在调用浏览器的的前进,后退,以及执行history.forward,history.back,history.go触发,这些操作都有一个特性,改变了浏览器的当前指针。
- 在不改变document的前提下,一旦当前指针改变就会触发onpopState事件。
-
浏览器对每个页面维护一个history栈,执行pushState函数可压入设定的url至栈顶,同时修改当前指针。
-
当执行back和forward操作的时候,history栈大小不会改变,仅仅移动当前的指针位置
-
若当前指针在history栈的中间位置,此时执行pushState会在指针当前位置添加此条目,并成为新的栈顶。(比如现在栈里面是,1,2,3。回退到2时,在pushState到4,此时栈变成124,3会被替代掉。)
实现:
重写onpushstate方法
pushState不会引起onpopstate改变,
监听pushstate方法,渲染不同的组件页面。
这就是路由的实现原理。因为单纯的pushState仅改变网址,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面
。是我们通过监听改变去渲染不同的页面
原理
react-router中的HashRouter和BrowserRouter像是react的一个借助context的包含了History的高阶组件。用来控制路有变化并渲染不同的组件
先看react-router-dom的使用
hashRouter
Router是容器,所有的Route必须放在里面。
Route是规则,表示渲染对应的组件。
每个组件都有三个对象,history,locaiton,match
它的原理Router下的每个Route都有三个对象,是采用context的做法。
实现基本路由
路由库其实有三个库
react-router-dom依赖react-router,react-router依赖History。react-router是所有平太共享的,react-router-dom是浏览器专属的。
实现原理:
其实实现原理很容易理解,Router是容器,他们传值是通过React.createContext。比如
在Router容器中间还有一层provider,借助他来进行值的传递。然后Router内部,会根据Loaction.path来获取当前浏览器的地址,并且跟Route的path做比对,一旦Route的Path比对上了,就会去渲染component这个组件。
实现原理就是这样。
我们基于这个原理既可以实现我们自己的react-router。
Router的实现
定义一个context上下文。
然后Router的实现就是包罗了一层Provider,location,history是从父组件传入的,哪里呢?
就是react-router-dom中的HashRouter,他会通过history这个库,创建一个History对象,这个history不是window.history。然后将History传入给Router,这样Router就可以拿到history对象了。注意我们监听了History的Listen方法,当路劲变化的时候就会触发这个方法。
这样Router就实现完毕了,是不是很简单。
Route的实现
Route的实现思路就是
- 通过Context获取Location和history,判断Props.path是不是跟当前的路劲一样,不是的话渲染null,是的话渲染component属性。
- 地址变化会触发我们在Router设定的listen方法,获取最新的location,然后重新渲染组件。到Route的时候继续做判断是否渲染即可。
BrowerRouter的实现
history路由的实现跟hash路由一样,
只不过创建的历史对象变成了createBrowerHistory而已。
让我们看看效果:
实现完毕。
总结
首先就是通过history这个库获取创建history对象的方法,再将history传入给Router,Router拿到这个方法后做一个监听,history.listen,他会监听地址的变化,然后重新渲染组件,将history以及Location作为context的value传下去。Route组件是包裹在Router下的,所以可以通过contextType拿到locaiton,再根据Location的pathname跟自己的Porps的Path比对,判断是否渲染component。
history库的实现
接着实现createHashRouter, createBrowserRouter的方法
createBrowserRouter
createBrowserRouter是一个工厂方法,用来返回history这个对象。
返回history。接着来实现里面的方法。go goBack goForward三个window.history都有实现,所以可以使用柯里化的函数形式来
接着是listen。listen是用来加入监听者的,当路劲改变的时候再告诉每个监听者。
通过数组存放监听者。
push方法的实现
pushState会改变当前的地址,但不会引起页面改变。所以下面接着对History的location进行更新,然后告诉监听者,路劲改变了,传入最新的Location。对应到上面我们的Router的listen监听的时候,会重新渲染组件,拿到最新的location,再去匹配对应的Route
这也是实现思路,前端框架监听路由的变化去实现不同组件的渲染。
效果:
createHashRouter的实现
因为hash没有像window.history这种,所以我们必须自己实现一个类似的历史条目栈,索引等等。实现的原理就是通过读写window.loction.hash然后监听hashchange去调用函数,更新History对象,渲染不同的组件。
先实现go,hash没有window.history.go,所以们得自己实现,
思路就是通过自己封装的历史条目站,通过索引拿到Location,重新写hash值去触发onhashChange。
先更新history这个对象,因为需要放入到栈中的。然后通知监听者改变。
push直接改变hash值就好,因为会触发OnhsahCHange在那边做处理。
listener跟createbrowserHistory一样。这样我们的createHashHIstory就实现完了。看效果:
正常实现。
总结:
先了解了history的原理和hash的原理,hash就是通过读写hash值,然后通过监听hashChange去渲染不同的组件。而history是通过window.history这个对象,通过go goBack这些实现基本的方法,通过监听popState去渲染不同的组件。通过pushState去完成Push方法,改变了地址后,然后执行监听函数,通知监听者改变。
Router的实现,基本就是通过React.context的借助history这个对象的一个高阶组件。在这个组件中去监听history的Listen方法,再重新触发组件更新,更新history,location值。然后Route就是通过context以及path component去匹配渲染对应的组件。
react-router-dom的HashRouter和BrowserRouter的主要目的就是创建history对象,并且将值传入Router组件。