Angular 客户端渲染时,从 ng-state 里读取 SSR 状态的具体逻辑

Angular 在客户端启动时,如何检测页面中的 ng-state 标签并从中读取 JSON 对象,进而还原应用的状态,是服务器端渲染(SSR)与客户端渲染(CSR)无缝衔接的核心环节之一。为了理解这个过程,我们可以结合 Angular 的源代码深入探讨这个过程,并通过实例说明它是如何工作的。

Angular 如何检测 ng-state

Angular 提供了 TransferState 服务来管理客户端和服务器之间的状态传递。这个服务允许在服务器端渲染时保存状态,并在客户端渲染时读取这些状态。这意味着,Angular 在服务器端已经生成并渲染好的数据可以通过 ng-state 标签传递给客户端。客户端在启动时,会检测页面中是否存在这个 ng-state 标签,并将其还原为应用的状态。

TransferState 的工作原理

TransferState 服务的工作流程非常关键,它的目的是从服务端传递状态给客户端。Angular 使用的机制是,通过 @angular/platform-browser 中的 TransferState 类和 makeStateKey 方法,将状态数据从服务端嵌入页面的 ng-state 脚本标签中,而客户端应用通过 TransferState 服务读取这些状态。

TransferState 服务的核心方法有两个:

  1. get :在客户端启动时,用来从 ng-state 标签中获取序列化的状态数据。
  2. set :在服务器端渲染时,将状态数据保存到 ng-state 标签中。
源代码分析

通过分析 Angular 的 TransferState 服务源代码,我们可以看到状态是如何在客户端被恢复的。关键代码如下:

typescript 复制代码
import { Injectable, InjectionToken } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable()
export class TransferState {
  private store: { [key: string]: any } = {};

  constructor(@Inject(DOCUMENT) private document: Document) {}

  get<T>(key: string, defaultValue: T): T {
    return this.store[key] || defaultValue;
  }

  set<T>(key: string, value: T): void {
    this.store[key] = value;
  }

  hasKey<T>(key: string): boolean {
    return this.store.hasOwnProperty(key);
  }

  initialize() {
    const script = this.document.getElementById('ng-state');
    if (script && script.textContent) {
      try {
        this.store = JSON.parse(script.textContent);
      } catch (e) {
        console.error('Could not parse ng-state', e);
      }
    }
  }
}

TransferState 服务在客户端启动时会调用 initialize 方法,这个方法的核心是通过 document.getElementById('ng-state') 查找 HTML 页面中的 ng-state 脚本标签,并解析其中的 JSON 数据。JSON.parse(script.textContent) 会将 ng-state 标签中的序列化数据解析为 JavaScript 对象,并存储在 store 中。

当客户端应用需要状态时,get 方法会从 store 中读取状态。这样,Angular 应用就能够在客户端启动时继续使用服务器端已经准备好的状态,避免了重复请求数据。

具体的例子

可以通过一个具体的案例来详细说明这个过程。例如,一个新闻网站的主页,服务器端会预先加载新闻列表,并将其嵌入到 ng-state 标签中。我们希望客户端应用能够直接读取这些新闻数据,而不必再次向服务器发起请求。

  1. 服务器端渲染新闻数据

在服务器端渲染过程中,Angular 通过 HttpClient 请求新闻 API,并将获取的新闻列表存储在 TransferState 中。这个过程通常在 ngOnInit 钩子中完成:

typescript 复制代码
export class NewsComponent implements OnInit {
  news: News[];

  constructor(private http: HttpClient, private transferState: TransferState) {}

  ngOnInit() {
    const newsKey = makeStateKey<News[]>('news');
    const newsFromState = this.transferState.get(newsKey, null);

    if (newsFromState) {
      this.news = newsFromState; // 从服务端状态中获取数据
    } else {
      this.http.get<News[]>('https://api.news.com/latest').subscribe((data) => {
        this.news = data;
        this.transferState.set(newsKey, data); // 设置服务端的状态
      });
    }
  }
}

在服务器端,TransferState 将新闻列表序列化并嵌入到 HTML 响应的 ng-state 标签中,生成如下的 HTML:

html 复制代码
<script id="ng-state" type="application/json">
{
  "news": [
    { "id": 1, "title": "新闻标题1", "content": "内容1" },
    { "id": 2, "title": "新闻标题2", "content": "内容2" }
  ]
}
</script>
  1. 客户端读取状态

客户端 Angular 应用在启动时,会自动通过 TransferState 服务检测并解析 ng-state 中的 JSON 数据。代码如下:

typescript 复制代码
export class NewsComponent implements OnInit {
  news: News[];

  constructor(private http: HttpClient, private transferState: TransferState) {}

  ngOnInit() {
    const newsKey = makeStateKey<News[]>('news');
    const newsFromState = this.transferState.get(newsKey, null);

    if (newsFromState) {
      this.news = newsFromState; // 从 ng-state 中恢复数据
    } else {
      this.http.get<News[]>('https://api.news.com/latest').subscribe((data) => {
        this.news = data;
      });
    }
  }
}

当客户端启动时,TransferStateinitialize 方法会读取 ng-state 标签中的 JSON 数据,并将其存储在 store 对象中。当 NewsComponent 初始化时,transferState.get(newsKey, null) 会尝试从 store 中读取新闻列表数据。如果数据存在,组件就直接使用这些数据,而不需要再发起 API 请求。

优势和应用场景

通过这种机制,Angular 能够在客户端启动时直接使用服务器端渲染的状态数据,大大提高了应用的加载速度,并减少了网络请求。对于那些数据量较大的应用,尤其是用户首屏展示需要大量数据时,这个机制显著提升了性能和用户体验。

1. 加速首屏渲染

在实际项目中,用户通常希望网站能够快速加载并显示内容。通过 ng-state 标签,Angular 在服务器端已经加载好的数据会立即显示给用户,避免了页面白屏或内容延迟加载的问题。

2. 减少不必要的 API 请求

在传统的客户端渲染中,应用启动时可能会发起多个 API 请求,导致额外的网络流量。而通过 ng-state,这些请求只需在服务器端完成,客户端只需读取已经存在的数据,减少了不必要的重复请求。

状态还原的挑战

虽然 ng-stateTransferState 提供了极大的便利,但在实际开发中,也可能面临一些挑战。比如,如何管理状态的一致性以及如何优化大数据量的传输。

状态一致性问题

在客户端和服务器端的应用状态可能存在不一致的情况,尤其是在用户交互过程中。如果服务器端渲染的状态与客户端应用的逻辑不一致,可能会导致页面错乱或者数据冲突。因此,在开发过程中,保持状态的一致性至关重要。

大数据量优化

对于某些需要处理大量数据的应用,例如包含复杂表格或者图表的分析工具,序列化的大数据量可能会导致 HTML 响应体过大,影响页面的加载速度。为了解决这个问题,开发者可以采取以下优化措施:

  • 分页加载数据:仅传递当前页面所需的数据,减少一次性传递的大数据量。
  • 懒加载:使用懒加载策略,在客户端应用启动后再动态加载次要数据,减轻服务器端渲染的压力。

总结

Angular 在客户端启动时通过 TransferState 服务从 ng-state 标签中读取状态的机制,是 SSR 与 CSR 平滑衔接的关键。通过这一机制,应用可以避免重复请求数据,提升用户体验。结合源代码分析和实际应用案例,我们深入了解了 Angular 的状态传递与还原过程。

相关推荐
老章学编程i2 分钟前
Vue工程化开发
开发语言·前端·javascript·vue.js·前端框架
思考的橙子22 分钟前
前端初识之一
前端
什么鬼昵称1 小时前
Pikachu-PHP反序列化
开发语言·javascript·php
tanxiaomi1 小时前
vue 不是spa 单页面应用吗? 配置路由工作模式为history 后 ,为什么配置Nginx的 try_files 可以根据url 找到对应的文件?
前端·vue.js·nginx
果子切克now1 小时前
vue3导入本地图片2种实现方法
前端·javascript·vue.js
谢尔登2 小时前
【移动端】事件基础
前端·javascript·html
敲啊敲95272 小时前
uni-app之旅-day02-分类页面
前端·javascript·uni-app
谢尔登3 小时前
【移动端】Viewport 视口
前端·javascript·html
哇咔咔哇咔3 小时前
【科普】什么是npm和pip?它们之间有什么异同?
前端·npm·pip