学习React-router

学习React-router

Posted by wang chong on June 4, 2019

现在比较流行的单页面应用(SPA)由于只存在一个页面,想要做到切换就要使用到路由这个东西。React、Vue等MVVM库的路由原理大致相同,都是通过url的变化去做出相应的交互效果。下面来去探索一下路由的原理吧。

Hash

早期的前端路由是通过hash来实现的。改变url的hash值是不会刷新页面的,通过hash实现前端路由,从而实现页面无刷新的效果。

hash位于window.loaction对象中,可以通过以下代码来改变hash。

window.location.hash = "hash"

修改前

修改后

1559566193196

可以看出url中多了一个以#开头的hash值,赋值前和赋值后不会因为url的变化而导致页面刷新。

此外还有一个hashChange事件,可以监听hash的变化。

 window.addEventListener("hashchange",(e) => {
     console.log(e);
 })

由上图可见输出一个HashChangeEvent对象,有了监听事件就可以通过事件来监听hash的变化,在回调函数中执行展示或隐藏的UI效果,从而实现前端路由。

除了通过window.location.hash来改变当前页面的hash值外,还可以通过a标签的锚点功能来改变hash

<a href="#hash">

#### hash的缺点

1. 搜索引擎对带有hash的页面不友好 2. 带有hash的页面内难以追踪用户的行为。

History

HTML5提供了History接口,History是一个底层接口,不继承任何接口,History接口允许我们操作浏览器会话历史记录。

History的属性和方法

History的属性

  1. History.length:返回在会话历史中有多少条记录,包含了当前会话页面。如果打开一个新的tab,那么这个length值为1。
  2. History.state:保存了会出发popstate实事件所得到的状态对象。这个状态对象来自pushState和replactState的第一个参数。

History的方法

  1. History.back():返回浏览器会话历史中的上一页,和浏览器的回退按钮功能类似
  2. History.forward():指向浏览器会话历史中的下一页,和浏览器的前进按钮类似
  3. History.go(num):可以跳转浏览器会话历史中的某一个记录页,值为数值,可正可负,正值表示前进负值表示后退,num的值表示前进或后退的页数。
  4. History.pushState():pushState方法可以将给定的数据压入到浏览器会话历史栈中,该方法接受3个参数,分别是状态对象、标题、url。pushState方法后会改变当前页面url,但是不会伴随着刷新。
  5. History.replaceState():replactState方法是替换的意思,就是将当前的会话页面的url替换成指定的数据,参数和pushState相同,也会改变当前页面的url。也不会刷新页面。

pushState和replaceState的相同点

都会改变当前页面显示的url和保存状态对象,但都不会刷新页面。

pushState和repalceState的不同点

  1. pushState是把一条数据压入浏览器的会话历史栈中,会使得History.length+1
  2. replaceState是替换浏览器会话历史栈中的当前记录,因此不会使得History增加。

BOM对象的history

history是浏览器的BOM对象模型中重要属性,history完全的继承了History接口,因此history有History中所有的属性和入方法。

方法

forward

history向前跳转使用forward方法

window.history.forward();

back

history向后跳转使用back方法

window.history.back();

go

使用go(num)方法跳转到指定位置的某个点,num为当前位置的相对位置(当前位置为0)

window.history.go(-1);
window.history.go(1);

pushState

pushState方法可以向浏览器会话历史记录栈中添加一条记录条目,使用history.pushState方法会改变referrer,它在用户发送XMLHttpRequest请求时在HTTP头部使用,改变state后创建的XMLHttpRequest对象的referrer都会改变。因为referrer是标识创建XMLHTTPReuqest对象时this所代表的window对象中document的URL。

history.pushState({},"title","url")
pushState方法接受三个参数
  1. 状态对象–状态对象state是一个JavaScript对象,通过pushState()创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate事件就会出发,且该事件的state属性好就是包含改历史记录条目状态对象的副本。状态是任何可以被序列化的东西,原因是浏览器将状态对象保存在电脑的磁盘上,重启浏览器的时候也可以使用。浏览器规定了状态对象在序列化后的大小,640k的限制,如果pushState传入的状态对象序列化后大于640K,则会抛出异常。
  2. 标题title–浏览器目前忽略这个参数,可以转一个空字符串或者传一个短标题。
  3. URL–该参数定义了新的历史URL记录,调用pushState后浏览器并不会立即加载档当前URL(回退前进时或者重新打开浏览器时加载),新URL可以是相对路径也可以是绝对路径,相对路径就相对于当前URL来处理,绝对路径新URL必须与旧URL同源,否则抛出异常。不传就是当前URL.
pushState和window.location的区别
  1. pushState添加新的历史记录的URL可以是与当前URL同源的任意URL,而window.location添加新的历史记录仅仅改变hash是才保持同源(window.location可以跳转到不同源网址)。

  2. pushState可以在新的历史记录中关联640K一下的数据,而window.location是基于hash的方式,如果要关联数据必须把数据放在url的字符串中。

  3. 如果以后title属性可以用到,那么这个属性是可以使用,hash则没有。

repalceState

history的repalceState和pushState类似,是修改当前会话历史记录栈中的历史记录项而不是新建一个。

注意:当前修改并不会阻止在全局浏览器历史记录中创建一个新的历史记录项。

history.pushState({},"",'a.html')       //先push一个a.html
history.pushState({},"",'b.html')       //再push一个b.html
history.replaceState({},"",'c.html')    //将当前历史记录项的url修改成c.html
history.pushState({},"",'d.html')       //再push一个d.html
history.go(-1)                          //回退一次       c.html
history.go(-1)                          //再回退一次    a.html

由此可见b.html这条历史记录项被修改了,回退的时候直接跳过这条记录。

popstate

window.popstate是popstate事件在window对象上的事件处理程序。当处于激活状态的历史记录项发生改变时,popstate事件就会在对应的window对象上触发。

如果处于激活状态的历史记录项是由pushState和replaceState创建的,那么popstate事件对象上的state属性就是这个历史记录项的state对象的一个拷贝。

调用pushState方法和popstate方法并不会触发popstate事件

触发popstate的方法有:

  1. history.back()
  2. history.forward()
  3. history.fo()

当网页加载时,各浏览器对popstate事件是否触发有不同的表现,Chrome 和 Safari会触发popstate事件, 而Firefox不会.

history实现路由的优点

  1. 对搜索引擎友好
  2. 方便统计用户行为

history实现路由的缺点

  1. 兼容性不如hash

  2. 需要前后端做响应的配置,否则直接访问子页面会出现404错误。

react-route

React-router4.0的代码库中,根据使用场景包含了一下几个独立的包

  1. react-router:react-router4.0核心代码

  2. react-router-dom:构建网页应用,存在DOM对象场景下的核心包

  3. react-router-native:适用于构建react-native应用的包

  4. react-router-config:配置静态路由的包

  5. react-router-redux:结合redux来配置路由,已废弃。

react-router所有提供的API都是以组件的形式给出的。

BrowserRouter

组件包裹整个App系统后,就是通过H5的historyAPI来实现无刷新条件下的前端路由。

BrowserRouter有一下属性:

  1. basename:String,为当前的url再增加一个名为basename的子目录。
    <BrowserRouter basename="test"></BrowserRouter>

如果设置了basename属性,那么此时127.0.0.1:8080和127.0.0.1:8080/test表示同一个地址,渲染的内容相同。

  1. getUserConfirmation: function 用于确认导航的功能。默认使用window.confirm

  2. forceRefresh:bool 默认为false,表示改变路由的时候页面不会重新刷新,如果设置为true,那么每次改变url都会重新刷新整个页面。

  3. keyLength:number 表示location的key属性的长度,在react-router中每一个url下都有一个location与其对应,并且每一个url的location的key值都不相同,这个属性一般使用默认值。

  4. children:React Element : 必须是一个ReactNode节点,表示唯一渲染的元素。

HashRouter使用url中的hash属性来保证不重复刷新的情况下同时渲染页面。

Route

Route组件做的事情就是匹配相应location中的地址,匹配成功后渲染对应的组件。

Route组件的路由匹配属性:

  1. path:当location中的url改变后,会与Route中的path属性进行匹配,path决定了与路由或url相关的渲染效果。

  2. exact:严格下的匹配规则,如果有exact属性,则只有当url地址与path完全相同时,才会命中,如果没有exact属性,即使不完全相同也可以命中。

    • 当exact不存在的时候
<Route path="/"/>

<Route path="/home"/>


上面这种当url为/home的时候/路由也会匹配成功。

  • 当exact存在的时候
<Route exact path="/"/>

<Route path="/home"/>

当url为/home的时候只能匹配到/home路由,而/路由只能在url为/的时候匹配成功。

  1. strict:是对exact做的补充,strict规则可以说是更加严格,严格限制了/

​ 如果设置了strict

<Route exact path="/"/>

<Route path="/home"/>

当url为/home/的时候并不会匹配到/home路由,必须严格的/home才可以匹配到。

Route组件的组件渲染属性

当Route的path属性匹配成功时,将会去渲染对应组件,有三种渲染方式。

  1. component:接受一个React组件,url匹配成功就渲染组件。

  2. render:接受一个返回React组件的函数,当url匹配成功,渲染返回的React组件

  3. children:与render蕾西,接受一个返回React组件的函数,但是不同点是,无论url与当前路由是否匹配children的内容始终被渲染出来。

这三个渲染属性所接受的方法或者组件,都会有location、match、history三个参数。

Link组件

Route组件定义了匹配规则和渲染规则,而Link决定是如何在页面内改变url,从而与Route组件匹配。Link组件类似于html中的a标签。此外当Link组件改变url的时候,可以将一些属性传递给匹配成功的Route组件,供响应的组件渲染的时候使用。

Link的属性:

to:to属性的值可以是一个字符串,也可以是一个对象。

当to的之为string 的时候,跟html中a标签的href相同,点击Link标签跳转从而匹配响应的path的Route,同时也会将history、location、match这三个对象传递给Route所对应的组件的props中。

<Link *to*="/home">Home</Link>

to的值也可以是一个对象,该对象可以包含一下几个属性:

  1. pathname

  2. search

  3. hash

  4. state

前三个参数与如何改变url有关,最后一个state参数是改变url,传递一个对象参数。

<Link to=}></Link>

在上面例子中,to为一个对象,点击Link标签跳转后,改变后的url为:/home?sort=name#edit

当Route组件匹配的时候只匹配/home,/home后面的参数会忽略掉。state属性也会最为参数传递到Route匹配的组件中去。

// 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)
//location中的属性
hash: "#edit"
key: "uxs9r5"
pathname: "/home"
search: "?sort=name"
state: {a:1}

react-router源码探究

react-router中基础的History API全部来自history这个库,先看一下这个库

npm install history –save

找到createBrowserHistory这个方法

function createBrowserHistory(props = {}) {
  var globalHistory = window.history; 
  //判断当前的浏览器对于window.history的兼容性
  var canUseHistory = supportsHistory();
  //判断当前浏览器是否是Trident内核的,IE浏览器的内核
  var needsHashChangeListener = !supportsPopStateOnHashChange();

  var   {
    forceRefresh =  false,
    getUserConfirmation=  function getConfirmation(message, callback) {
      callback(window.confirm(message));
    },
    keyLength = 6,
    basename = ""
  } = props;
//根据当前history.state的值生成新的location对象,
  function getDOMLocation(historyState) {}
  //用于创建与history中每一个url记录相关联的指定位数的唯一标识key,默认keyLength长度为6位。
  function createKey() {}
  //返回一个集成对象
  // return {
  //   setPrompt: setPrompt,      用于设置url跳转时弹出的文字提示
  //   confirmTransitionTo: confirmTransitionTo,   会将当前生成新的history对象中的location,action,callback等参数传递进去,作用就是在回调函数中,根据需要改变传入的location和action对象。
  //   appendListener: appendListener,  关于url的相关的监听函数数组。 返回值函数可取消监听。
  //   notifyListeners: notifyListeners  通过notifyListeners遍历执行listener中的所有监听函数。
  // };
  var transitionManager = createTransitionManager();
  //当history的url和action发生改变的时候调用setState方法,更新history对象中的属性,history.length + 1,并且遍历执行所有的isteners中的监听函数
  function setState(nextState) {}

//用于处理POP事件,
  function handlePop(location) {}
  //重置Pop,当本次更新的key和上次相同的话就会go(0),本网页跳转一下
  function revertPop(fromLocation) {}
  //getHistoryState 返回window.history.state
  var initialLocation = getDOMLocation(getHistoryState());
  //全局的key
  var allKeys = [initialLocation.key];
  //当pushState操作的时候将action改为PUSH,执行操作。
  function push(path, state) {}
  //当replaceState操作的时候,将action该为REPLACE,执行操作。
  function replace(path, state) {}
//go方法,调用原生history的go方法
  function go(n) {
    globalHistory.go(n);
  }
  //回退
  function goBack() {
    go(-1);
  }
//前进
  function goForward() {
    go(1);
  }

  //检查DOM的事件函数,如果listenerCount监听列表为0的话移除在popstate和hashchange上的监听事件。并且只定义一个监听事件。
  function checkDOMListeners(delta) {}

  function block(prompt) {}
  //定义监听
  function listen(listener) {}
  var history = {
    length: globalHistory.length,
    action: 'POP',
    location: initialLocation,
    createHref: createHref,
    push: push,
    replace: replace,
    go: go,
    goBack: goBack,
    goForward: goForward,
    block: block,
    listen: listen
};
  //返回history
  return history;
}