Single Page Application
客户端/服务端渲染
客户端渲染CSR
数据有浏览器通过ajax动态取得,再通过js将数据填充到dom元素上最终展示到页面中。
eg: h5.ele.me/stie/
服务端渲染SSR
后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完成的html页面,在直接发送给浏览器,以便方便用户快速浏览。
eg: www.cnblogs.com/cate/design
对比
- 服务端渲染需要消耗更多的服务器端资源(CPU,内存等);
- 服务端渲染对SEO更友好。(搜索引擎优化,是对HTML的动态分析);
- 服务端渲染对爬虫友好(单纯spa需要等待更长时间的初始渲染,爬虫可能会将页面解析为空);
- 服务端渲染对客户端的体验好(加快首屏渲染时间);
- 服务端渲染使用node中间件,可维护性不错,可以解决cors;
- 客户端渲染可以分担服务器资源,并且可以将静态资源部署到cdn上,实现高并发;
- 客户端渲染,页面可维护性好;
- 客户端渲染,局部刷新体验好;
总结
各有优点,一般混合使用,eg:京东商品详情页。
react服务器端渲染的实现
react15的react-dom/server的两个方法
- renderToString: 将React Component转化为HTML字符串。生成的HTML的DOM会带有额外属性:各个DOM元素会有data-react-id属性,DOM根元素会有data-checksum属性。(data-react-id是唯一表示方便组件管理,data-checksum会根据数据改变而改变,用来判断组件是否改变,两个都是提高服务器端的渲染效率)
- renderToStaticMarkup:同样是将React Component转化为HTML字符串,但是生成HTML的DOM不会有额外属性,从而节省HTML字符串的大小。会重复渲染。
只负责HTML的渲染,不负责JS的处理,所以只是渲染展示层,不作业务行为处理。
react16去掉了data-react-id和data-checksum,加了data-reactroot,客户端渲染使用差异算法检查服务器端生成的节点的准确性。当react16的客户端渲染器检测到节点不匹配,仅仅是尝试修改不匹配的HTML子树,而不是修改整个HTML树。
seo
- 预渲染(一般返回无js处理的静态页面,或者使用无头浏览器渲染?浏览器第一次只负责呈现,后续有需要再渲染)
通过 webpack 的 prerender-spa-plugin 编译应用中的静态页面,并将其输出到对应的索引目录。
- nginx反向代理,判断用户请求的User-Agent是否是爬虫,如果是,增加爬页面的接收延时,保证异步渲染的接口数据返回
History和Hash路由
前端路由的优点就是组件切换不需要发送http请求,切换跳转快,用户体验好;缺点就是没有合理的利用缓存且不利于SEO。
先来看看location
的官方属性有哪些
属性 | 描述 |
---|---|
hash | 设置或返回从 # 开始的 URL (锚) |
host | 设置或返回主机名和当前 URL 的端口号 |
hostname | 设置或返回当前 URL 的主机名 |
href | 设置或返回完整的 URL |
pathname | 设置或返回当前 URL 的路径部分 |
port | 设置或返回当前 URL 的端口号 |
protocol | 设置或返回当前 URL 的协议 |
search | 设置或返回从 ? 开始的 URL 部分 |
单页面应用中不重新加载页面的两种方式:
-
Hash虽然在URL中,但是不包含在HTTP请求中,值变化不会向后台请求,但是URL不美观;
window.addEventListener("hashchange", function(){}, false)
-
History利用了HTML5 history interface中新增的两个方法pushState(), replaceState()。这两个方法应用于浏览器记录栈,在当前已有的back、forward、go基础上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了URL,但是捕获立即向后端发送请求。popState可以用来做url更新的监听。
特点是实现更加方便,可读性更强,同时因为没有了
#
,url也更加美观;它的劣势也比较明显,当用户刷新或直接输入地址时会向服务器发送一个请求,所以history模式需要服务端同学进行支持,将路由都重定向到根路由
if (this.mode === 'hash') {
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1)
})
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1)
})
} else {
window.addEventListener('load', () => {
this.history.current = location.pathname
})
window.addEventListener('popstate', () => {
this.history.current = location.pathname
})
}
history监听popState,hash监听hashChange。
Hash 模式和 History 模式对比
Hash 模式是使用 URL 的 Hash 来模拟一个完整的 URL,因此当 URL 改变的时候页面并不会重载。History 模式则会直接改变 URL,所以在路由跳转的时候会丢失一些地址信息,在刷新或直接访问路由地址的时候会匹配不到静态资源。因此需要在服务器上配置一些信息,让服务器增加一个覆盖所有情况的候选资源,比如跳转 index.html 什么的
hash路由 优缺点
- 优点
- 实现简单,兼容性好(兼容到
ie8
) - 绝大多数前端框架均提供了给予
hash
的路由实现 - 不需要服务器端进行任何设置和开发
- 除了资源加载和
ajax
请求以外,不会发起其他请求
- 实现简单,兼容性好(兼容到
- 缺点
- 对于部分需要重定向的操作,后端无法获取
hash
部分内容,导致后台无法取得url
中的数据,典型的例子就是微信公众号的oauth
验证 - 服务器端无法准确跟踪前端路由信息
- 对于需要锚点功能的需求会与目前路由机制冲突
- 对于部分需要重定向的操作,后端无法获取
History(browser)路由 优缺点
- 优点
- 对于重定向过程中不会丢失
url
中的参数。后端可以拿到这部分数据 - 绝大多数前段框架均提供了
browser
的路由实现 - 后端可以准确跟踪路由信息
- 可以使用
history.state
来获取当前url
对应的状态信息
- 对于重定向过程中不会丢失
- 缺点
- 兼容性不如
hash
路由(只兼容到IE10
) - 需要后端支持,每次返回
html
文档
- 兼容性不如