在使用 Angular Universal 进行服务器端渲染(SSR)时,防御性编程的核心思路是隔离平台相关代码,避免直接依赖浏览器特有的 API(如 window、document),同时保持业务逻辑的通用性。以下是具体策略和实现方法:
1. 避免平台相关代码的直接调用// window-service.ts(抽象基类)@Injectable()export abstract class WindowService { abstract getWidth(): number;}// browser-window.service.ts(浏览器实现)@Injectable()export class BrowserWindowService extends WindowService { getWidth(): number { return window.innerWidth; // 仅浏览器环境调用 }}// server-window.service.ts(服务器实现)@Injectable()export class ServerWindowService extends WindowService { getWidth(): number { return 0; // 服务器端返回默认值 }}// app-server.module.ts@NgModule({ providers: [{ provide: WindowService, useClass: ServerWindowService, // 服务器端注入替代实现 }],})export class AppServerModule {}2. 分离平台特定模块// 通用组件(app.module.ts)@NgModule({ declarations: [ExampleComponent], // 仅包含通用逻辑})export class AppModule {}// 浏览器模块(browser-app.module.ts)@NgModule({ imports: [AppModule, LibraryModule], // 引入实际第三方库})export class BrowserAppModule {}// 服务器模块(server-app.module.ts)@NgModule({ imports: [AppModule], declarations: [LibraryShimComponent], // 用空组件替代库组件})export class ServerAppModule {}// library-shim.component.ts@Component({ selector: 'library-component', template: '', // 空模板避免报错})export class LibraryShimComponent {}3. 避免 isPlatformBrowser/isPlatformServer 的滥用4. 处理第三方库的兼容性// app.module.tsexport function loadPolyfills() { if (isPlatformBrowser(platformId)) { return import('intersection-observer'); // 仅浏览器加载 } return Promise.resolve();}@NgModule({ providers: [{ provide: APP_INITIALIZER, useFactory: loadPolyfills, multi: true, }],})export class AppModule {}5. 防御性编程的最佳实践- 最小化平台相关代码:将浏览器 API 调用限制在特定服务中。
- 统一错误处理:在服务中捕获异常(如 window 未定义),返回默认值或抛出可控错误。
- 测试覆盖:确保 SSR 测试覆盖所有平台特定路径。
- 文档标记:明确标注哪些代码仅适用于浏览器或服务器。
总结通过服务抽象、模块分离和Shim 组件,可以避免 Angular Universal 中的平台相关错误,同时保持代码的可维护性。核心原则是:业务逻辑通用化,平台差异隔离化。