浏览器状态同步和路由-SSR和单页面应用的分析 【单页面应用和服务端渲染】

news2024/12/23 17:23:41

目录

单页面应用(优缺点)(Single Page Application)

优点:

SPA的缺点:

 服务端渲染(Server Side Rendering)

SSR示例(一个ssr小引擎)

SSR优缺点分析

总结


单页面应用(优缺点)(Single Page Application)

优点:

  1. 体验:流畅性
  2. 架构:组件化
  3. 生态:BaaS/FaaS 调用第三方服务
  4. 性能:读写速度,cpu占用率,节省I/O
  5. 安全:容错(数据没到位,加载不出来)

SPA的缺点:

  • CPU开销大(组件vs时间)(js加载页面,浏览器cpu开销大)
  • 流量开销大(一次加载多张页面)
  • 需要好的系统架构
  • 搜索引擎不友好

 服务端渲染(Server Side Rendering)

客户端dom直接就有对应事件

服务端需要重建对应事件

SSR示例(一个ssr小引擎)

  • 开发一个生成html节点并且管理事件的函数
  • render(meta, renderAs = dom | html(字符串) | rehydrate(事件水合))
  • 实现render函数的服务端渲染和客户端渲染能力

水合(hydrateRoot)
水合是指其他物质溶于水发生化学反应
此处水合是指后端数据达到前端后,js绑定事件,才能够响应用户的操作或者DOM的更新。
工作流程(将事件比作水)
组件在服务器拉取数据,并在服务端首次渲染。
脱水,对组件进行脱水,变成HTML字符串,脱去交互事件,成为风干标本快照。
注水,发送到客户端后,重新注入数据(交互的事件),重新变成可交互组件。
此过程类似于银耳,长成后晒干,然后加入水再泡发。

// {
//   name: "div",
//   props : {
//     onClick : () => {},
//     style : {
//       backgroundColor : 'blue'
//     }
//   },
//   children : [
//     {
//       name : 'div',
//       ...
//     }
//   ],
//   children : "hello"
// }
function render(node, renderAs = 'dom', path = []) {
  const { name, props, children } = node
  if (renderAs === 'dom') { // 客户端渲染
    const element = document.createElement(name)
    if (props && props.onClick) {
      element.addEventListener('click', props.onClick)
    }
    if (typeof children === 'string') {
      element.innerHTML = children
    } else if (Array.isArray(children)) {
      children.forEach((child, i) => {
        element.appendChild(
          render(child, renderAs, path.concat(i)) // 第三个参数,递归函数节点路径
        )
      })
    }
    return element
  } else if (renderAs === 'html') { // 服务端渲染
    let childrenStr = ''
    if (typeof children === 'string') {
      childrenStr = children
    } else {
      childrenStr = children
        .map((child, i) => {
          return render(child, renderAs, path.concat(i))
        })
        .join("") // 衔接字符串
    }
    return `<${name} id='node-${path.join('-')}'>${childrenStr}</${name}>`
  } else if (renderAs === 'rehydrate') { // 服务端渲染之后,客户端做水合
    if (props && props.onClick) {
      document.getElementById(`node-${path.join('-')}`) // 获取对应节点
        .addEventListener('click', props.onClick) // 水合绑定事件
    }
    if (Array.isArray(children)) { // 数组
      children.forEach((child, i) => { 
        render(child, renderAs, path.concat(i)) // 递归
      })
    }
  } else {
    throw "not supported renderAs type"
  }
}
module.exports = render
// 主程序
const app = require('express')()
const render = require('./lib/render')
const path = require('path')
// 要渲染的html
const html = render({
  name : 'div',
  props : {
    onClick : () => {
      window.alert('123')
    },
  },
  children : [{
    name : 'ul',
    children : [
      {
        name : 'li',
        children : 'Apple'
      },
      {
        name : 'li',
        children : 'Alibaba'
      },
    ]
  }]
}, 'html') // 服务端渲染的html字符串
app.get('/page-ssr.js', (req, res) => {
  res.sendFile(path.resolve(__dirname, 'lib/page-ssr.js'))
})
app.get('/page.js', (req, res) => {
  res.sendFile(path.resolve(__dirname, 'lib/page.js'))
})
app.get('/', (req, res) => {
  res.send(`<html>
    <body>
    <div id="root">
    </div>
    <script src='/page.js'></script>
  </body>`)
})
app.get('/ssr', (req, res) => {
  res.send(`<html>
    <body>
      <div id="root">
      ${html}
      </div>
    <script src='/page-ssr.js'></script>
  </body>`)
})
app.listen(3000)
// page-ssr.js // 服务端 水合绑定事件
function render(node, renderAs = 'dom', path =[] ){
  const {name, props, style, children} = node
  if(renderAs === 'dom') {
    const element = document.createElement(name)
    if(props.onClick) {
      element.addEventListener('click', props.onClick)
    }
    if(style) {
      Object.keys(style).forEach(key => {
        element.style[key] = style[key]
      }) 
    }
    if(typeof children === 'string') {
      element.innerHTML = children
    } else if(Array.isArray(children)){
      children.forEach( (child, i) => {
        element.appendChild(
          render(child,  renderAs, path.concat(i))
        )
      })
    } else {
      throw "invalid children"
    }
    return element
  } else if(renderAs === 'html') {
    let styles = [] 
    if(style) {
      styles = Object.keys(style)
        .map(key => {
          const k = key.replace(/([A-Z])/, m => '-' + m.toLowerCase())
          const val = style[key]
          return `${k}=${val}`
        })
    }
    const styleString = styles.join(';')
    let childrenStr = ''
    if(typeof children === 'string') {
      childrenStr = children
    } else {
      childrenStr = children
        .map((child, i) =>
          render(child, renderAs, path.concat(i) )
        )
        .join("")
    }
    return `<${name} id='node-${path.join('-')}' style='${styleString}'>${childrenStr}</${name}>`
  } else if(renderAs === 'rehydrate') {
    if(props && props.onClick){
      document.getElementById('node-' + path.join('-'))     
        .addEventListener('click', props.onClick)
    }
    if(Array.isArray(children)) {
      children.forEach( (child, i) => {
        render(child,  renderAs, path.concat(i))
      })
    }
  }
}
render({
  name : 'div',
  props : {
    onClick : () => {
      window.alert('123')
    },
  },
  children : [{
    name : 'ul',
    children : [
      {
        name : 'li',
        children : 'Apple'
      },
      {
        name : 'li',
        children : 'Alibaba'
      },
    ]
  }]
}, 'rehydrate')
// page.js render 出一个dom,客户端 ,前端渲染
function render(node, renderAs = 'dom', path =[] ){
  const {name, props, style, children} = node
  if(renderAs === 'dom') {
    const element = document.createElement(name)
    if(props && props.onClick) {
      element.addEventListener('click', props.onClick)
    }
    if(style) {
      Object.keys(style).forEach(key => {
        element.style[key] = style[key]
      }) 
    }
    if(typeof children === 'string') {
      element.innerHTML = children
    } else if(Array.isArray(children)){
      children.forEach( (child, i) => {
        element.appendChild(
          render(child,  renderAs, path.concat(i))
        )
      })
    } else {
      throw "invalid children"
    }
    return element
  } else if(renderAs === 'html') {
    let styles = [] 
    if(style) {
      styles = Object.keys(style)
        .map(key => {
          const k = key.replace(/([A-Z])/, m => '-' + m.toLowerCase())
          const val = style[key]
          return `${k}=${val}`
        })
    }
    const styleString = styles.join(';')
    let childrenStr = ''
    if(typeof children === 'string') {
      childrenStr = children
    } else {
      childrenStr = children
        .map((child, i) =>
          render(child, renderAs, path.concat(i) )
        )
        .join("")
    }
    return `<${name} id='node-${path.join('-')}' style='${styleString}'>${childrenStr}</${name}>`
  } else if(renderAs === 'rehydrate') {
    if(props && props.onClick){
      document.getElementById('node-' + path.join('-'))     
        .addEventListener('click', props.onClick)
    }
    if(Array.isArray(children)) {
      children.forEach( (child, i) => {
        render(child,  renderAs, path.concat(i))
      })
    }
  }
}
const element = render({
  name : 'div',
  props : {
    onClick : () => {
      window.alert('123')
    },
  },
  children : [{
    name : 'ul',
    children : [
      {
        name : 'li',
        children : 'Apple'
      },
      {
        name : 'li',
        children : 'Alibaba'
      },
    ]
  }]
}, 'dom')
document.getElementById('root').appendChild(element)

SSR优缺点分析

优点

  • 显著减小TTI(Time To interactive)减小浏览器执行时间;加重服务端开销,减轻客户端开销,减少js执行时间,让客户可以更快的用上
  • 单页面应用SEO解决方案
  • 内网数据组装

缺点

  • 服务器CPU压力大
  • CDN不好支持
  • 开发工作量大(三套代码,水合,服务端,客户端)
  • 全局对象失效

总结

  • 思考:少量页面开发用单页面还是多页面?看开发人员水平,水平够了,写一个好的系统架构,就推荐用单页面应用,因为用户体验好,多个页面用异步加载,
  • 思考:如果SSR目的仅仅是SEO,该如何做?用户访问量高的话,任何一点加速,都是很大的提升

服务端做分流,发现是百度的爬虫就返回SSR后的页面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Opencv+Python笔记(八)轮廓检测

目录 一、轮廓的检测和绘制1.读入图像2.将读入图像转化为灰度图3.对灰度图进行二值化 [图像的阈值化处理](https://blog.csdn.net/Ggs5s_/article/details/130301816?spm1001.2014.3001.5501)4.进行轮廓检测5.在原图中显示轮廓 二、轮廓层级关系1.RET_LIST2.RETR_EXTERNAL3. R…

座椅内饰如何「跟上」智能电动?这款智能概念座舱看到未来

进入智能电动汽车时代&#xff0c;理想的车内空间应该是怎样的&#xff1f;作为“内饰空间创造者”、全球三大汽车座椅及内饰厂商之一&#xff0c;丰田纺织在2023上海车展上给出了一系列解决方案。 4月19日&#xff0c;丰田纺织携诸多产品亮相本次上海车展&#xff0c;包括面向…

【速卖通】 AliExpress(速卖通)关键词搜索结果采集

采集场景 在AliExpress(速卖通) 首页中 http://www.aliexpress.com 中输入关键词&#xff0c;采集关键词搜索后得到的商品列表信息。 采集字段 关键词、标题、商品id、商品图片地址、商品详情链接、价格、免费退送货、星级、已出售数量、店铺名 采集结果 采集结果可导出为E…

C语言入门篇——函数篇

1、什么是函数 首先&#xff0c;什么是函数&#xff1f;函数(function)是完成特定任务的独立程序代码。单元语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、子程序、过程作用相同&#xff0c;但是细节上略有不同。 为什么使用函数&#xff1f; 首先…

刷题训练2之AcWing第 96 场周赛

竞赛 - AcWing 一、完美数 4876. 完美数 - AcWing题库 1、题目 如果一个正整数能够被 2520 整除&#xff0c;则称该数为完美数。 给定一个正整数 n&#xff0c;请你计算 [1,n]范围内有多少个完美数。 输入格式 一个整数 n。 输出格式 一个整数&#xff0c;表示 [1,n] 范…

【社区图书馆】操作系统的经典书籍

操作系统的经典书籍 一、引言二、书籍的选择三、优缺点3.1、《操作系统》3.2、《计算机操作系统》 小结 一、引言 《操作系统》罗宇和《计算机操作系统》汤小丹这两本书都是关于操作系统的经典书籍&#xff0c;各有优势。 二、书籍的选择 首先&#xff0c;从内容深度上&…

倾斜摄影超大场景的三维模型的顶层合并常见的问题分析

倾斜摄影超大场景的三维模型的顶层合并常见的问题分析 倾斜摄影超大场景的三维模型顶层合并是将多个局部区域的点云或网格数据进行融合&#xff0c;生成一个整体的三维模型的过程。在这个过程中&#xff0c;常见的问题包括&#xff1a; 1、数据不一致。由于数据采集时间、空间…

SAP SM30表格维护生成器隐藏记录日志字段

1.背景 在表格维护生成器中往往会隐藏记录日志字段&#xff0c;不让用户直接查看&#xff0c;而供运维或者开发部门使用&#xff0c;如下所示&#xff1a; 2.实现 2.1 SM30逻辑流和屏幕元素中删除日志记录字段 2.2 创建事件&#xff0c;写入记录日志代码 2.2.1 记录日志方式…

Node.js使用CORS解决跨域问题的三种方法

目录 1、通过CORS中间键解决2、设置响应头3、app.all解决4、解决跨域问题案例 现如今&#xff0c;实现跨域数据请求&#xff0c;最主要的两种解决方案&#xff0c;分别是JSONP和CORS. JSONP:出现的早&#xff0c;兼容性好&#xff08;兼容低版本IE&#xff09;。是前端程序员为…

m1下利用dockerdesktop安装ELK

一、背景&#xff1a;公司有一个需求&#xff0c;就是将txt中的数据加载到es中&#xff0c;之前没用过es&#xff0c;想着先在本地安装一个&#xff0c;然后再做测试。 二、安装docker desktop 打开docker的官网&#xff0c;下载苹果芯片的docker 网址&#xff1a;https://ww…

当DevOps遇见AI,智能运维的黄金时代开启

文章目录 1. 当DevOps遇见AI&#xff0c;智能运维的黄金时代2. 什么是DevOpts&#xff1f;改变开发格局&#xff1a;测开、运开必然趋势3. 什么是Docker容器化&#xff0c;它会替代掉VM虚拟机吗&#xff1f;4. 运维的终点是开发5. 实际项目的部署案例6. 誉天程序员课程 1. 当De…

顶象推出应用隐私合规检测服务

为帮助开发者更高效地进行App隐私合规检测&#xff0c;顶象推出应用隐私合规检测服务&#xff0c;快速发现App可能存在的各类隐私安全漏洞&#xff0c;并提供详细的检测报告&#xff0c;给出专业的合规整改建议。该服务可应用于上架前和合规检测&#xff0c;通过个人信息保护分…

map<int,int>和map<int,int>::iterator

map< int,int >和map< int,int >::iterator 一、map<int,int>、map<string, string>的含义二、map<int,int>::iterator的作用三、map<int,XXX>的自动升序特点 一、map<int,int>、map<string, string>的含义 map容器是CSTL的一…

学成在线笔记+踩坑(7)——绑定媒资

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线牛客面试题 目录 8 绑定媒资 8.1 需求分析 8.1.1 业务流程 8.1.2 数据模型 8.2 接口定义 8.2.1 抽取模型类 8.2.2 定义接口层 8.3 接口开发 8.3.1…

23.4.21总结

正则表达式 正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串&#xff0c;通常被用来检索、替换那些符合某个模式&#xff08;规则&#xff09;的文本。 正则表达式是一种对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些…

【Java 数据结构】十大排序 (动图解析)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

微搭低代码学习之销售员销售目标采集系统开发

四月二十三&#xff0c;春意渐浓&#xff0c; 草木抽出新枝&#xff0c;鸟儿唱响欢融。 花开满园&#xff0c;香气袭人&#xff0c; 阳光明媚&#xff0c;人们心情舒畅。 愿这美好的日子&#xff0c;伴随你一生。 ——使用Notion AI提问“写一首关于4月23日的诗” 文章目录 前言…

【深度学习入门系列】 pytorch实现多层感知机(MLP)(内含分类、回归任务实例)

文章目录 0. BP和MLP1 分类1.0 数据集1.1 网络架构1.2 代码1.3 结果 2 回归2.0 数据集2.1 网络架构2.2 代码2.3 结果 3 代码&#xff08;可直接食用&#xff09; 众所周知&#xff0c;sklearn提供了MLP函数。个人认为这个东西虽然蛮好用的——有的时候比你自己写的效果都好&…

Linux应用程序开发:进程的一些事儿

目录 一、进程的简介1、什么是进程&#xff0c;进程的概念2、进程状态3、什么是进程号4、进程间的通信方法&#xff08;IPC&#xff09; 二、 fork()创建子进程三、父、 子进程间的文件共享1、实验12、实验2 四、使用execl函数执行新程序五、关于终端上对进程的一些指令操作六、…

AI绘图风格对照表/画风样稿详细研究记录及经验总结(分析Midjourney和Stable Diffusion风格提示词实际使用情况)不断更新中...

Midjourney和Stable Diffusion都可以通过输入文本生成出令人惊叹的AI图像。 Midjourney是一个收费的在线服务&#xff0c;通过discord对话的形式来生图&#xff0c;局限性较大&#xff0c;但由于后台官方模型做得好&#xff0c;因此出图效果非常完美&#xff1b; Stable Diffus…