WEB前端原理

【随缘翻译】单页应用工作方式[1]

2019-03-06  本文已影响0人  钟志弘

简单讲述如何使用浏览器API创建单页面应用。

单页面应用(SPA)是指可以不请求服务器获取HTML(例如 双击一个链接),而使用导航动作的回应重新渲染自身内容的网站。

单页面应用实现方式多种多样,大多数是基于相同的浏览器行为以及浏览器本地API实现其核心功能的。理解这些东西是了解单页面应用工作的关键。

我们要讲的是什么类型的SPA ?

单页面应用可以使用外部资源的状态(例如 URL location),或者内部轨迹状态。这篇文章会主要围绕基于location的单页面应用。为什么?

使用内部状态的SPA是非常受限的,因为他们只有一个"入口"。单入口意味着你进入app的以后只能停留在根路径下。当你进入“内部状态"app的时候, 里面没有任何的外部呈现。你想要分享一些网站的信息(网站的作用就是分享信息),加载你的APP的用户会一直停留在根页面,所以你必须要对他们解释,如何才能获取他们想要的内容。
图释:


内部状态app实现方式

当然在基于location的SPA中,你可以分享链接并且能够保证任何打开这个链接的人会看到和你预想中的数据一致,因为location随着你的导航不断更新(在此之前请确保用户有权限访问当前内容)
图解:


基于location的SPA

Location 的构成

当URL 在地址栏的时候正如用户看到的那样,SPA 应用使用 window.location属性。这个属性允许你与URL交互而不用而外的解析它。


window.location属性值观的描绘了URL。

当然只有3个location对象的属性是对SPA应用重要的:pathname, hash, 和 search(一般称之为 query string).对于单页面应用来讲,任何外部地址都会被浏览器作常规化处理(例如,点击一个锚点让浏览器去处理它),所以hostname和protocol属性是没意义的。

pathname严格来讲在这三个有用的属性中是最重要的, 因为他用于检测什么情况下该干什么事。而search和hash 在为app提供而外数据的时候用得多一点。例如, 请看URL /images?of=moutains, /images 这个pathname会指定哪个图片页面应该被渲染,当然 ?of=moutains 这个search 指定那种类型的图片应该被渲染到该页面中。

路由匹配

单页面应用一般来讲维护一个路由器(router)。

路由器是由多个路由(route)组成,一般来讲路由(route) 中保存着他们的匹配路径。 路径可以为静态路径(/about)和动态路径(/album/:id),这里的:id可以为任何可能的数字。

path-to-regexp包是非常热门的创建路径的解决方案。

const routes = [
  { path: '/' },
  { path: '/about' },
  { path: '/album/:id' }
];

路由匹配的规则是,对比本地路径和路由器中的路由路径,以查找出匹配的路径值。

在匹配完成后, 路由器会触发一个重渲染方法。具体的实现方案很大程度上取决于路由器。例如,一个路由器可能使用的是observer pattern,该方案的实现是:你给路由器一个如何触发重渲染的规则,如果路由器匹配到对应的路由则会调用该方法。

应用程序基于匹配到当前路径的路由进行渲染

App 内导航

的确,导航是一个更有趣的问题。当你点击一个锚点的时候,浏览器本地行为会发送触发导航的事件。然而,你同样可以发送你自定义的click驯兽员(哈哈哈),并且覆盖浏览器本地行为(在现代浏览器中使用 event.preventDefault())。当然没有了本地行为,location就不会发生改变,所以是否触发导航取决于click 处理器(函数)。

在我们理解click 处理机如何触发导航之前,我们需要了解一点关于浏览器默认处理器导航的原理。

浏览器是如何捕获到Locaions的?

每一个浏览器的tab 都有一个 "浏览上下文( browsing context)"。 这个浏览上下文维护了 “会话历史(session history)”,会话历史本质上是一个location入口的数组。这个入口保存了location的信息:URL,关联的文档,序列化后的状态,以及一些乱七八糟的属性。入口的索引指定其在会话历史数组中的位置。那么浏览器上下文对象同样保存了当前激活的入口的轨迹。


会话历史有很多个入口组成

什么是文档

当浏览器发起导航,他会发送一个请求(request)给服务器,并且浏览器会使用服务器传送回的数据(response)去创建文档对象。文档对象描述了一个页面(DOM) 并且提供了很多与其互动的方法。window.document 就是一个浏览器当前激活的Document对象。


从Response中创建文档对象

会话历史

当你点击一个link,并且导航到指定页的时候,浏览器会创建一个会话历史。每个导航都会初航舰一个请求给服务器,并且创建一个新的入口点(包括一个新的文档)


每个导航行为都会追加新的入口点给会话历史
注意: 上图的入口点图片中每种颜色都表示不同的文档。

如果你点击浏览器的返回按钮,浏览器会使用当前入口去决定“新的” 当前入口点(current.index -1)。而"旧的"当前入口点的文档(Document)将会被卸载同时新的当前入口点的文档对象(Document)将会被加载。


点击返回按钮选择入口点(和文档)。前进按钮拥有同样的行为

当你点击一个链接,而当前入口点后面有多个入口点时,后面的入口点将会被浏览器抛弃。


点击一个链接时当前入口点后面的入口点将会被抛弃

不管怎样,你导航到与当前位置相同的位置的时候, 当前入口点会被覆盖而不会影响随后的左右两边的入口点。


点击一个与当前地址相同的地址时,地址会覆盖当前地址而不会影响其他入口点

这便是本地导航行为的工作方式,但是单页应用的关键便是阻止发送请求给服务器。那么问题来了,SPA 做了什么去阻止这个行为呢?

历史对象API (History)

早前的单页面应用依赖于以下工作方式: 你可以改变location对象的hash,而浏览器会新建一个location入口(上面提到的)而不发送请求给web服务器。这是可以的,但是并不是一个好主意,进而历史对象API被开发出来,并加入到单页面应用实现方式的第一梯队。

相比起为每个位置(location)创建一个新的Document,History API 会更新活动的document对象给新的location,以重用Document。

History API 又3个核心函数,分别是:

pushState();
replaceState();
go();

这些API(当然还有其他API)储存在windows.history对象中

使用History API导航这一行为叫做重用激活文档(active document)

关键: 疑惑哪些浏览器支持History API? 放心所有主流的“现代”浏览器都支持它。当然IE <=9 是不支持的,但是这些浏览器已经不再被维护了。还有 Opera Mini比那个不支持在客户端运行JavaScript,当然也不可能支持History API。

pushState() 和 replaceState()

pushState 和 replaceState 都有相同的函数特征。

  1. 第一个参数是一个 state。 如果你不想传入任何state, 那么直接传入null即可。 这个行为可能会尝试保持应用程序的state,我们稍后会详细讨论它。

  2. 第二个参数 title string, 已经没有浏览器会去是使用它了。

3.第三个参数是你想导航到的路径,它可以是完整的URL, 一个绝对路径, 又或者是一个相对路径,但是这个路径绝对不能跨域(即相同的协议(protoc0l)和主机名(hostname)),如果不是的话,那么会抛出一个 DOMException 异常对象。

history.pushState(null,'', '/next-location');
location.replaceState(null,'','replace-location');
// 将状态传入 入口对象
history.pushState({msg: 'Hi!'},'','/greeting');

// 当 在 medium.com 时
history.pushState(null, '', 'https://google.com');
// 抛出 DOMException

to be continued...

上一篇 下一篇

猜你喜欢

热点阅读