SPA

238 阅读5分钟

Single Page Application

客户端/服务端渲染

客户端渲染CSR

数据有浏览器通过ajax动态取得,再通过js将数据填充到dom元素上最终展示到页面中。

eg: h5.ele.me/stie/

服务端渲染SSR

后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完成的html页面,在直接发送给浏览器,以便方便用户快速浏览。

eg: www.cnblogs.com/cate/design

对比

  1. 服务端渲染需要消耗更多的服务器端资源(CPU,内存等);
  2. 服务端渲染对SEO更友好。(搜索引擎优化,是对HTML的动态分析);
  3. 服务端渲染对爬虫友好(单纯spa需要等待更长时间的初始渲染,爬虫可能会将页面解析为空);
  4. 服务端渲染对客户端的体验好(加快首屏渲染时间);
  5. 服务端渲染使用node中间件,可维护性不错,可以解决cors;
  6. 客户端渲染可以分担服务器资源,并且可以将静态资源部署到cdn上,实现高并发;
  7. 客户端渲染,页面可维护性好;
  8. 客户端渲染,局部刷新体验好;

总结

各有优点,一般混合使用,eg:京东商品详情页。

github.com/amandakelak…

react服务器端渲染的实现

react15的react-dom/server的两个方法

  1. renderToString: 将React Component转化为HTML字符串。生成的HTML的DOM会带有额外属性:各个DOM元素会有data-react-id属性,DOM根元素会有data-checksum属性。(data-react-id是唯一表示方便组件管理,data-checksum会根据数据改变而改变,用来判断组件是否改变,两个都是提高服务器端的渲染效率)
  2. renderToStaticMarkup:同样是将React Component转化为HTML字符串,但是生成HTML的DOM不会有额外属性,从而节省HTML字符串的大小。会重复渲染。

只负责HTML的渲染,不负责JS的处理,所以只是渲染展示层,不作业务行为处理。

react16去掉了data-react-id和data-checksum,加了data-reactroot,客户端渲染使用差异算法检查服务器端生成的节点的准确性。当react16的客户端渲染器检测到节点不匹配,仅仅是尝试修改不匹配的HTML子树,而不是修改整个HTML树。

seo

  1. 预渲染(一般返回无js处理的静态页面,或者使用无头浏览器渲染?浏览器第一次只负责呈现,后续有需要再渲染)

​ 通过 webpack 的 prerender-spa-plugin 编译应用中的静态页面,并将其输出到对应的索引目录。

  1. nginx反向代理,判断用户请求的User-Agent是否是爬虫,如果是,增加爬页面的接收延时,保证异步渲染的接口数据返回

History和Hash路由

前端路由的优点就是组件切换不需要发送http请求,切换跳转快,用户体验好;缺点就是没有合理的利用缓存且不利于SEO

先来看看location的官方属性有哪些

属性 描述
hash 设置或返回从 # 开始的 URL (锚)
host 设置或返回主机名和当前 URL 的端口号
hostname 设置或返回当前 URL 的主机名
href 设置或返回完整的 URL
pathname 设置或返回当前 URL 的路径部分
port 设置或返回当前 URL 的端口号
protocol 设置或返回当前 URL 的协议
search 设置或返回从 ? 开始的 URL 部分

单页面应用中不重新加载页面的两种方式:

  1. Hash虽然在URL中,但是不包含在HTTP请求中,值变化不会向后台请求,但是URL不美观;

    window.addEventListener("hashchange", function(){}, false)
    
  2. 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文档