惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
博客园 - 聂微东
小众软件
小众软件
P
Proofpoint News Feed
Security Archives - TechRepublic
Security Archives - TechRepublic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
TaoSecurity Blog
TaoSecurity Blog
博客园 - 司徒正美
罗磊的独立博客
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
月光博客
月光博客
S
Secure Thoughts
P
Proofpoint News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
W
WeLiveSecurity
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
L
LangChain Blog
T
The Blog of Author Tim Ferriss
NISL@THU
NISL@THU
Google DeepMind News
Google DeepMind News
Cloudbric
Cloudbric
H
Hacker News: Front Page
The Last Watchdog
The Last Watchdog
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
博客园 - 三生石上(FineUI控件)
博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
Project Zero
Project Zero
SecWiki News
SecWiki News
爱范儿
爱范儿
The Register - Security
The Register - Security
AI
AI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
L
Lohrmann on Cybersecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
P
Privacy International News Feed
J
Java Code Geeks
S
Securelist
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog

BAIYUN'S BLOG

AI Coding 从小工到专家 - BAI YUN 电信 IPTV 内网融合教程:在局域网任意设备观看 IPTV - BAI YUN 美国住宅 IP 提供商体验对比 - BAI YUN Node.js 程序以 Keyless 模式从 1Password 读取密钥 - BAI YUN 高效优雅的观影指南 - BAI YUN 基于 Surge 的 DNS 优化指南 - BAI YUN RIME 鼠须管输入法简明使用教程 - BAI YUN 自动给不同 Git 项目设置不同的用户名邮箱 - BAI YUN Web3 新手如何选择加密货币钱包 - BAI YUN Web3 到底是什么? - BAI YUN 谨慎开启 Safari 的阻止跨站跟踪功能 - BAI YUN iOS Surge 开启后无法使用部分国产 APP 或网络出错的解决办法 - BAI YUN 屏蔽 Telegram 垃圾私信的有效办法 - BAI YUN 每个人都应该读一遍《1984》和《动物庄园》 - BAI YUN iOS 给常见机构号码增加联系人头像 - BAI YUN 在国内如何购买比特币和投资加密货币 - BAI YUN WEB 性能优化:如何让你的网站页面秒开(方法论) - BAI YUN 加密货币投资这件事 - BAI YUN 国内手机号注册推特教程 - BAI YUN 国内注册币安账号并通过 KYC 教程 - BAI YUN 使用一套助记词创建多个以太坊钱包账户 - BAI YUN Apple Watch 的几个用途 - BAI YUN 钱包安全指南:如何保护你的 NFT 和加密资产不被盗 - BAI YUN 我在 macOS 上高频使用的应用 - BAI YUN Web3 时代:如何使用 MetaMask 钱包实现一键登录 - BAI YUN 2022 年了 Safari 无法渲染渐变文字的 BUG 依旧存在 - BAI YUN 境外手机卡:澳门电信大湾区预付卡使用体验 - BAI YUN 解决 iCloud Drive 上传文件卡住的问题 - BAI YUN Electron 应用如何获取系统代理配置 - BAI YUN 解决 iOS 上 Google 账号登录会卡在加载状态的问题 - BAI YUN 记录 Github Copilot 惊讶到我的时刻 - BAI YUN 解决 Xbox 在国内下载速度慢 - BAI YUN 开始使用 IndexNow - BAI YUN Surge iOS 和 AmpliFi 的兼容性问题 - BAI YUN 自建图床指南 - BAI YUN 使用 TypeScript 提升可维护性 - BAI YUN 在 JavaScript 应用中正确的实现时区切换 - BAI YUN 从 Loading 动画出发改进 React 应用的用户体验 - BAI YUN Node.js Tips - BAI YUN 使用 Dart Sass 代替 Node Sass - BAI YUN WSL 踩的坑和解决方法 - BAI YUN NPM & Yarn Tips - BAI YUN Git Tips - BAI YUN macOS 命令行 Tips - BAI YUN JavaScript 防劫持 - BAI YUN Dota2 - BAI YUN macOS 好用的系统维护工具 - BAI YUN 在 Linux 服务器上使用 Puppeteer 生成 PDF - BAI YUN The Golden Circle 黄金圆环法则 - BAI YUN CSS 写法进化史 - BAI YUN 一个 Web 开发者的 Windows10 开发环境设置心得 - BAI YUN Chrome DevTools Tips - BAI YUN React 设计模式和思想 - BAI YUN Linux & Unix 命令行环境的一些 Tips - BAI YUN Webpack 动态导入 - BAI YUN JavaScript 中的异步 - BAI YUN 关于我的博客 - BAI YUN
从传统 MVC 到同构:Web 发展历程 - BAI YUN
baiyun · 2017-12-22 · via BAIYUN'S BLOG

Web 技术一直在不断发展和演进,从最早的静态页面到如今的复杂单页应用,这其中经历了许多阶段和技术的迭代。本文将从最传统的 MVC 模式一直介绍到目前常用的同构模式,探讨各种技术的优缺点,帮助读者更好地理解 Web 技术的发展历程。无论是 Web 开发初学者,还是有一定经验的开发者,都能从中获得一些启示和思考。

最传统的 MVC 模式

浏览器发出请求,服务器收到后渲染出完整的 HTML 页面返回给浏览器,在这个阶段里大部分业务逻辑都包含在服务端端代码里,如果想要获取新的页面内容,需要重新发送请求,服务器再渲染出完整的页面返回给浏览器,即使实际改变的内容只有一个字也需要服务器重新渲染完整的页面。

这种模式最大的缺点在于客户端无法在不刷新页面的情况下动态更改页面内容,可以很明显看出性能的巨大浪费,以及用户体验的差劲

AJAX

在这个阶段,借助浏览器提供的 XMLHttpRequest 对象,可以实现在不刷新页面的情况下向服务器请求新的数据,然后使用 JS 动态渲染到页面中,但是点击链接的时候还是需要刷新整个页面,无法做到像原生 APP 一样的体验。

SPA (Single page application)

这个阶段和之前的大不相同,浏览器请求页面只会获得一个静态的 HTML 入口文件,例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
  <script src="/assets/js/app.js"></script>
</body>
</html>

页面中的实际内容完全由浏览器里的 JS 渲染出来,客户端会维护自己的路由、控制器和模型,与服务器之间完全通过 API 来交流数据,此时服务端代码不再处理视图层相关逻辑,可以专注于数据。

整个流程通常是这样的:

  1. 浏览器发出请求
  2. 服务端返回 HTML 入口文件
  3. 浏览器下载并执行 HTML 入口文件里包含的 JS 文件
  4. JS 通过 API 向服务器请求初始数据
  5. 服务器返回数据后 JS 会结合数据渲染出实际的内容填充到页面中

这种模式可以在切换页面时避免刷新整个页面,做到类似原生 APP 的用户体验,但是也有两个缺点:

  1. 爬虫只能爬到 HTML 入口文件,SEO 不友好
  2. 首屏呈现时间比较慢,也就是第一次打开时,用户会看到比较久的白屏或者加载动画

对于 SEO 不友好的问题,有一种解决方法是对爬虫返回使用 Headless 浏览器预渲染页面,这种方法需要额外维护预渲染相关的逻辑,投入回报是个问题

对于首屏呈现时间慢的问题,通常是在 HTML 入口页面中返回首屏需要的初始数据,例如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
  <script>
    window.__INITIAL_STATE__ = { data: '...' }
  </script>
  <script src="/assets/js/app.js"></script>
</body>
</html>

JS 可以直接读取 window.__INITIAL_STATE__ 中的初始数据,这样可以省下通过 Ajax 获取首屏数据的时间,效果比较明显,但是在 JS 代码下载、parsing,执行之前用户还是会看到白屏或者加载动画,而 JS 代码的下载和 parsing 又是比较耗时的,所以在移动网络下这个问题会尤其明显。

同构

所谓同构就是指,JS 代码可以同时运行在服务端和浏览器,凭借虚拟 DOM,现在服务器也可以渲染出 HTML 字符串了,例如 React 或者 Vue 这些使用了虚拟 DOM 的库和框架,它们可以运行的服务端和浏览器,同样的代码在浏览器中渲染出真实 DOM,在服务器中渲染出 HTML 字符串。

如果一个 SPA 使用 React 或者 Vue 构建而成,那么它可以很容易转换为同构代码,在同构代码中通常有以下几个注意点:

隔离环境特定代码

例如在 Node 环境中没有windowdocument 之类的仅存在于浏览器环境的对象,所以在我们写同构代码时要注意尽量避免副作用,例如:

// moduleA.js
const list = document.querySelectorAll('ul li')
export default list

上面的代码就不能称为同构代码,它只能在浏览器环境运行,如果想让它变为同构代码通常有两种办法:

  1. 将副作用封装到函数里
// moduleA.js
const getList = () => document.querySelectorAll('ul li')
export default getList
  1. 只在浏览器运行,例如在 React 中可以在 componentDidMount 中执行
class App extends React.Component {
  state = { list: [] }
  componentDidMount() {
    this.setState({ list: document.querySelectorAll('ul li') })
  }
}

共享状态

在浏览器环境很多状态天然就是单独的,但在服务端它们就是共享状态了,如果我们有一个 API 可以获取用户收藏帖子,如下:

import axios from 'axios'

const getFavoritePosts = () => axios('/api/posts').then((res) => res.data)

export default getPosts

上面的代码在浏览器中使用是没有问题的,浏览器会自动发送用户的 cookie 给服务器,但是在服务器环境中需要将用户的 cookie 手动传给 API 服务器,我们修改代码为如下:

import axios from 'axios'

const getFavoritePosts = (axiosInstance) => {
  const ins = axiosInstance || axios
  return ins('/api/posts').then((res) => res.data)
}

export default getPosts

此时,我们在服务端调用 API 时需要传入 axios 实例

import getFavoritePosts from './api'

class IndexPage extends React.Component {
  static getInitialProps({ axios }) {
    return getFavoritePosts(axios)
  }
  render() {
    return this.props.posts.map((post) => (
      <h3 key={post.id}>
        <a href={post.url}>{post.title}</a>
      </h3>
    ))
  }
}

这种做法固然解决了问题,但是每次使用比较繁琐,更好的办法是创建一个通用的 http 请求函数,由它负责动态适配当前运行环境,同时服务端的每个请求都需要携带当前请求的 cookie 和必要的 header 信息。

function request(requestConfig, req) {
  if (isServer) {
    // 从 req 读取 cookie 和 header
  } else {
    // 浏览器环境
  }
}

class IndexPage extends React.Component {
  static getInitialProps({ req }) {
    return request({ url: '/api/xxx' }, req)
  }
  render() {
    return this.props.posts.map((post) => (
      <h3 key={post.id}>
        <a href={post.url}>{post.title}</a>
      </h3>
    ))
  }
}

本文原载于:baiyun.me

原文链接:https://baiyun.me/web-development-from-mvc-to-isomorphic