网页的本质就是超级文本标记语言,通过结合使用其他的Web技术(如:脚本语言、公共网关接口、组件等),可以创造出功能强大的网页。因而,超级文本标记语言是万维网(Web)编程的基础,也就是说万维网是建立在超文本基础之上的。超级文本标记语言之所以称为超文本标记语言,是因为文本中包含了所谓“超级链接”点。
本篇文章给大家带来的内容是关于从路由开始深入理解react-router 4.0源码,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
react-router等前端路由的原理大致相同,可以实现无刷新的条件下切换显示不同的页面。路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。通过前端路由可以实现单页(SPA)应用,本文首先从前端路由的原理出发,详细介绍了前端路由原理的变迁。接着从react-router4.0的源码出发,深入理解react-router4.0是如何实现前端路由的。
通过Hash实现前端路由
通过H5的history实现前端路由
React-router4.0的使用
React-router4.0源码分析
一、通过Hash实现前端路由
1、hash的原理
??早期的前端路由是通过hash来实现的:
改变url的hash值是不会刷新页面的。
??因此可以通过hash来实现前端路由,从而实现无刷新的效果。hash属性位于location对象中,在当前页面中,可以通过:
window.location.hash='edit'
来实现改变当前url的hash值。执行上述的hash赋值后,页面的url发生改变。
赋值前:http://localhost:3000
赋值后:http://localhost:3000/#edit
在url中多了以#结尾的hash值,但是赋值前后虽然页面的hash值改变导致页面完整的url发生了改变,但是页面是不会刷新的。此外,还有一个名为hashchange的事件,可以监听hash的变化,我们可以通过下面两种方式来监听hash的变化:
window.onhashchange=function(event){
console.log(event);
}
window.addEventListener('hashchange',function(event){
console.log(event);
})当hash值改变时,输出一个HashChangeEvent。该HashChangeEvent的具体值为:
{isTrusted: true, oldURL: "http://localhost:3000/", newURL: "http://localhost:3000/#teg", type: "hashchange".....}??有了监听事件,且改变hash页面不刷新,这样我们就可以在监听事件的回调函数中,执行我们展示和隐藏不同UI显示的功能,从而实现前端路由。
此外,除了可以通过window.location.hash来改变当前页面的hash值外,还可以通过html的a标签来实现:
<a href="#edit">edit</a>2、hash的缺点
hash的兼容性较好,因此在早期的前端路由中大量的采用,但是使用hash也有很多缺点。
搜索引擎对带有hash的页面不友好
带有hash的页面内难以追踪用户行为
二、通过history实现前端路由
HTML5的History接口,History对象是一个底层接口,不继承于任何的接口。History接口允许我们操作浏览器会话历史记录。
(1)History的属性和方法
History提供了一些属性和方法。
History的属性:
保存了会出发popState事件的方法,所传递过来的属性对象(后面会在pushState和replaceState方法中详细的介绍)
History方法:
History.back(): 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
History.forward():指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
History.go(): 可以跳转到浏览器会话历史中的指定的某一个记录页
History.pushState():pushState可以将给定的数据压入到浏览器会话历史栈中,该方法接收3个参数,对象,title和一串url。pushState后会改变当前页面url,但是不会伴随着刷新
History.replaceState():replaceState将当前的会话页面的url替换成指定的数据,replaceState后也会改变当前页面的url,但是也不会刷新页面。
上面的方法中,pushState和repalce的相同点:
就是都会改变当前页面显示的url,但都不会刷新页面。
不同点:
pushState是压入浏览器的会话历史栈中,会使得History.length加1,而replaceState是替换当前的这条会话历史,因此不会增加History.length.
(2)BOM对象history
history在浏览器的BOM对象模型中的重要属性,history完全继承了History接口,因此拥有History中的所有的属性和方法。
这里我们主要来看看history.length属性以及history.pushState、history.replaceState方法。
history.pushState(stateObj,title,url) or history.replaceState(stateObj,title,url)
pushState和replaceState接受3个参数,分别为state对象,title标题,改变的url。
window.history.pushState({foo:'bar'}, "page 2", "bar.html");
此时,当前的url变为:
执行上述方法前:http://localhost:3000
执行上述方法后:http://localhost:3000/bar.html
如果我们输出window.history.state:
console.log(window.history.state);
// {foo:'bar'}
window.history.state就是我们pushState的第一个对象参数。
history.replaceState()方法不会改变hitroy的长度
console.log(window.history.length);
window.history.replaceState({foo:'bar'}, "page 2", "bar.html");
console.log(window.history.length);
上述前后两次输出的window.history.length是相等的。
此外。
每次触发history.back()或者浏览器的后退按钮等,会触发一个popstate事件,这个事件在后退或者前进的时候发生:
window.onpopstate=function(event){
}注意:
history.pushState和history.replaceState方法并不会触发popstate事件。
如果用history做为路由的基础,那么需要用到的是history.pushState和history.replaceState,在不刷新的情况下可以改变url的地址,且如果页面发生回退back或者forward时,会触发popstate事件。
hisory为依据来实现路由的优点:
缺点:
三、React-router4.0的使用
了解了前端路由实现的原理之后,下面来介绍一下React-router4.0。在React-router4.0的代码库中,根据使用场景包含了以下几个独立的包:
react-router : react-router4.0的核心代码
react-router-dom : 构建网页应用,存在DOM对象场景下的核心包
react-router-native : 适用于构建react-native应用
react-router-config : 配置静态路由
react-router-redux : 结合redux来配置路由,已废弃,不推荐使用。
在react-router4.0中,遵循Just Component的设计理念:
所提供的API都是以组件的形式给出。
比如BrowserRouter、Router、Link、Switch等API都是以组件的形式来使用。
1、React-router-dom常用的组件API
下面我们以React-router4.0中的React-router-dom包来介绍常用的BrowserRouter、HashRouter、Link和Router等。
(1) <BrowserRouter>
用<BrowserRouter> 组件包裹整个App系统后,就是通过html5的history来实现无刷新条件下的前端路由。
<BrowserRouter>组件具有以下几个属性:
如果设置了basename属性,那么此时的:
http://localhost:3000 和 http://localhost:3000/test 表示的是同一个地址,渲染的内容相同。
getUserConfirmation: func 这个属性,用于确认导航的功能。默认使用window.confirm
forceRefresh: bool 默认为false,表示改变路由的时候页面不会重新刷新,如果当前浏览器不支持history,那么当forceRefresh设置为true的时候,此时每次去改变url都会重新刷新整个页面。
keyLength: number 表示location的key属性的长度,在react-router中每个url下都有为一个location与其对应,并且每一个url的location的key值都不相同,这个属性一般都使用默认值,设置的意义不大。
children: node children的属性必须是一个ReactNode节点,表示唯一渲染一个元素。
与<BrowserRouter>对应的是<HashRouter>,<HashRouter>使用url中的hash属性来保证不重新刷新的情况下同时渲染页面。
(2) <Route>
<Route> 组件十分重要,<Route> 做的事情就是匹配相应的location中的地址,匹配成功后渲染对应的组件。下面我们来看<Route>中的属性。
首先来看如何执行匹配,决定<Route>地址匹配的属性:
举例来说,当exact不设置时:
<Route path='/home' component={Home}/>
<Route path='/home/first' component={First}/>此时url地址为:http://localhost:3000/home/first 的时候,不仅仅会匹配到 path='/home/first'时的组件First,同时还会匹配到path='home'时候的Router。
如果设置了exact:
<Route path='/home' component={Home}/>只有http://localhost:3000/home/first 不会匹配Home组件,只有url地址完全与path相同,只有http://localhost:3000/home才能匹配Home组件成功。
举例来说,当不设置strict的时候:
<Route path='/home/' component={Home}/>此时http://localhost:3000/home 和 http://localhost:3000/home/
都能匹配到组件Home。匹配对于斜线“/”比较宽松。如果设置了strict属性:
<Route path='/home/' component={Home}/>那么此时严格匹配斜线是否存在,http://localhost:3000/home 将无法匹配到Home组件。
当Route组件与某一url匹配成功后,就会继续去渲染。那么什么属性决定去渲染哪个组件或者样式呢,Route的component、render、children决定渲染的内容。
component:该属性接受一个React组件,当url匹配成功,就会渲染该组件
render:func 该属性接受一个返回React Element的函数,当url匹配成功,渲染覆该返回的元素
children:与render相似,接受一个返回React Element的函数,但是不同点是,无论url与当前的Route的path匹配与否,children的内容始终会被渲染出来。
并且这3个属性所接受的方法或者组件,都会有location,match和history这3个参数。如果组件,那么组件的props中会存在从Link传递过来的location,match以及history。
(3) <Link>
<Route>定义了匹配规则和渲染规则,而<Link> 决定的是如何在页面内改变url,从而与相应的<Route>匹配。<Link>类似于html中的a标签,此外<Link>在改变url的时候,可以将一些属性传递给匹配成功的Route,供相应的组件渲染的时候使用。
to属性的值可以为一个字符串,跟html中的a标签的href一样,即使to属性的值是一个字符串,点击Link标签跳转从而匹配相应path的Route,也会将history,location,match这3个对象传递给Route所对应的组件的props中。
举例来说:
<Link to='/home'>Home</Link>如上所示,当to接受一个string,跳转到url为'/home'所匹配的Route,并渲染其关联的组件内接受3个对象history,location,match。
这3个对象会在下一小节会详细介绍。
to属性的值也可以是一个对象,该对象可以包含一下几个属性:pathname、seacth、hash和state,其中前3个参数与如何改变url有关,最后一个state参数是给相应的改变url时,传递一个对象参数。
举例来说:
<Link to={{pathname:'/home',search:'?sort=name',hash:'#edit',state:{a:1}}}>Home</Link>在上个例子中,to为一个对象,点击Link标签跳转后,改变后的url为:'/home?sort=name#edit'。 但是在与相应的Route匹配时,只匹配path为'/home'的组件,'/home?sort=name#edit'。在'/home'后所带的参数不作为匹配标准,仅仅是做为参数传递到所匹配到的组件中,此外,state={a:1}也同样做为参数传递到新渲染的组件中。
(4) React-router中传递给组件props的history对象
介绍了 <BrowserRouter> 、 <Route> 和 <Link> 之后,使用这3个组件API就可以构建一个简单的React-router应用。这里我们之前说,每当点击Link标签跳转或者在js中使用React-router的方法跳转,从当前渲染的组件,进入新组件。在新组件被渲染的时候,会接受一个从旧组件传递过来的参数。
我们前面提到,Route匹配到相应的改变后的url,会渲染新组件,该新组件中的props中有history、location、match3个对象属性,其中hisotry对象属性最为关键。
同样以下面的例子来说明:
<Link to={{pathname:'/home',search:'?sort=name',hash:'#edit',state:{a:1}}}>Home</Link>
<Route exact path='/home' component={Home}/>我们使用了<BrowserRouter>,该组件利用了window.history对象,当点击Link标签跳转后,会渲染新的组件Home,我们可以在Home组件中输出props中的history:
// props中的history
action: "PUSH"
block: ? block()
createHref: ? createHref(location)
go: ? go(n)
goBack: ? goBack()
goForward: ? goForward()
length: 12
listen: ? listen(listener)
location: {pathname: "/home", search: "?sort=name", hash: "#edit", state: {…}, key: "uxs9r5"}
push: ? push(path, state)
replace: ? replace(path, state)从上面的属性明细中:
push:f 这个方法用于在js中改变url,之前在Link组件中可以类似于HTML标签的形式改变url。push方法映射于window.history中的pushState方法。
replace: f 这个方法也是用于在js中改变url,replace方法映射于window.history中的replaceState方法。
block:f 这个方法也很有用,比如当用户离开当前页面的时候,给用户一个文字提示,就可以采用history.block("你确定要离开当前页吗?")这样的提示。
go / goBack / goForward
在组件props中history的go、goBack、goForward方法,分别window.history.go、window.history.back、window.history.forward对应。