使用React-Router创建一个单页应用
时间:2023-03-13 23:00:33
科技观察
最近在业余时间学习React。借助Redux和React-Router,我在慢慢开发一个小工具moviemaster来管理硬盘中的电影剧集。在单页面应用开发中,redux不是必须的,所以今天只讲前端路由系统和React-Router的简单使用。什么是路由以下摘自维基百科::路由是通过互连网络将信息从源地址传输到目的地址的活动。路由发生在OSI网络参考模型的第三层,即网络层。路由引导数据包在经过一些中间节点后转发到最终目的地。这是网络工程中的一个术语。对于大家来说,最熟悉的应该就是家里的路由器了。路由是指路由器从一个接口接收数据包,根据数据包的目的地址进行定向,转发到另一个接口的过程。在Web上,url就像路由器中的路由表。每个url对应不同的页面或内容,就像路由表中的IP对应不同的网络一样。先来看看熟悉的套路:在传统的web应用架构中,客户端只是一个展示层,通过url访问服务器,服务器根据自己的“路由表”分发相应的页面给客户端。但是在这种模式下,异步加载ajax的内容是无法通过url记录的。无论你对页面进行多少操作,异步请求多少数据,每次重新访问同一个url,服务器返回给客户端的内容都是完全一样的。如果前端有自己专属的“路由表”来分配页面的不同状态,那不就可以了吗?Hash和pushState据我了解,目前构建前端路由系统的方式有两种:#在url和HTML5中的historyAPI。原理如下:防止标签的默认跳转动作。ajax或Fetch请求内容。将返回的内容添加到页面中。使用hash或pushState修改url。经典的Hash#表示网页中的位置。后面的字符是位置的标识符。比如https://zhanglun.github.io/index.html#body代表网页index.html的body位置。浏览器读取到这个URL后,会自动将body位置滚动到可见区域。有两种指定标识符的方法。1.使用锚点2.使用id属性#用来指向文档的内容,属于浏览器的行为,与服务器无关。在HTTP请求中不会携带#和后面的内容。对于服务端,http://www.baidu.com和http://www.baidu.com#action=fuckbaidu返回给客户端的都是以前内容分发的,但是可以通过Window对象上的location.hash来操作在浏览器中。因此,在浏览器中,可以通过hash来记录页面的状态,构建一个“路由表”。当页面状态发生变化时,hash也会随之变化,重新加载时,可以通过url中携带的hash直接将页面设置为对应的状态。例如:http://www.example.com/http://www.examplt.com/#edithttp://www.examplt.com/#settings访问/时,显示主页。点击页面上的编辑按钮,页面会显示对应的内容进行编辑。直接通过url访问时,检查hash是否匹配edit。如果匹配,则执行代码以加载编辑的内容。点击页面上的设置按钮,页面会显示相应的内容。直接通过url访问时,检查hash是否与设置匹配,如果匹配则执行代码加载编辑后的内容。以下为伪代码:;}}window.onload=()=>{hashHandler();}window.onhashchange=()=>{hashHandler();}HTML5中的pushStatepushState是HistoryAPI中的一个方法,它的文档可以在这里找到MDN历史。它的功能很简单:修改url,添加历史。比如/blogs和settings对应两个页面。如果只是点击页面上的tab进行切换,那么只需要做以下操作:发送修改页面内容的请求,调用pushState方法修改url。问题来了。对于前端来说,需要看成是同一个页面,但实际上这两个url是对服务器的两次不同的请求,所以这里需要服务器的配合。我的做法是:对应的url返回相同的页面,然后浏览器接受后检查前端定义的路由系统,执行响应代码。这种方法可能会导致页面无缘无故的加入一小段延迟,但是影响不大。React-Router的使用目前,任何路由系统库或框架,虽然写法不同,但都是在上述两种方法的基础上实现的。让我耳目一新的是:使用路由嵌套的概念来定义嵌套的视图集合,当调用给定的URL时,将渲染整个集合(最好的部分)。importReactfrom'react';import{render}from'react-dom';import{Router,Route,IndexRoute,hashHistory}from'react-router';importAppfrom'./containers/App';importMovieContainerfrom'./containers/Movies';importDetailfrom'./containers/Detail';letrootElement=document.getElementById('app');render(,rootElement);在入口文件中,引入React-Router并作为组件在render中使用。上面的代码配置结果如下:URLcomponent/App/aboutApp->About/inboxApp->Inbox/inbox/messages/:idApp->Inbox->Message在路由中,组件对应的子组件可以通过这个.props.children在父组件中渲染classAppextendComponent{constructor(props){super(props)}render(){Hello,world!
{this.props.children}
}}当URL为/时,App中没有渲染任何组件,render中的this.props.children仍然是undefined。这时候可以使用IndexRoute设置一个默认页面。render(
{/*RenderWelcomewhenurlis/*/>,rootElement);URL组件/App->Welcome/aboutApp->About/inboxApp->Inbox/inbox/messages/:idApp->Inbox->Message此时匹配的路由为:/posts,/posts/usres/:userid和/posts/users/:userid/messages/:messageid,可见嵌套的
匹配的url是包裹它的路径的“总和”。但是问题又来了。嵌套的好处是路由之间的结构清晰直观,但也会导致url不美观。试想一下,/posts/users/:userid/messages/:messageid这么长的路由,真的很累。React-Router的配置提供了一个选项:将Route的路径设置为绝对路径。同时可以使用将修改为绝对路径的路由重定向到之前设置的URLcomponent/postsApp->Post/user/:useridApp->Post->User/messages/:messageidApp->Post->User->Message的基本配置完成后,可以通过
自动执行路由跳转,也可以通过browserHistory和hashHistory手动执行。