阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 服务端渲染(SSR)中的设计模式考量

服务端渲染(SSR)中的设计模式考量

作者:陈川 阅读数:27840人阅读 分类: JavaScript

服务端渲染(SSR)中的设计模式考量

服务端渲染在现代Web开发中扮演着重要角色,它直接影响首屏性能、SEO和用户体验。在实现SSR时,合理运用设计模式能有效解决数据流管理、组件复用和状态同步等核心问题。

工厂模式在组件创建中的应用

服务端渲染环境需要创建大量组件实例,工厂模式能统一组件创建逻辑。特别是在需要根据运行环境区分客户端和服务器端组件时,这种模式表现出色。

class ComponentFactory {
  static createComponent(type, props) {
    if (typeof window === 'undefined') {
      return new ServerComponent(props);
    } else {
      return new ClientComponent(props);
    }
  }
}

// 使用示例
const component = ComponentFactory.createComponent('button', {
  text: 'Click me'
});

这种实现方式确保在服务器端和客户端使用不同的组件实现,同时保持一致的接口。对于同构组件,可以进一步扩展工厂方法:

class UniversalComponentFactory {
  static createComponent(type) {
    const components = {
      header: UniversalHeader,
      footer: UniversalFooter
    };
    
    if (!components[type]) {
      throw new Error(`Unknown component type: ${type}`);
    }
    
    return components[type];
  }
}

单例模式管理全局状态

SSR应用中,需要确保服务器端和客户端共享相同的应用状态。单例模式在这里能防止状态重复初始化,保持数据一致性。

class AppState {
  constructor() {
    if (AppState.instance) {
      return AppState.instance;
    }
    
    this.user = null;
    this.theme = 'light';
    AppState.instance = this;
  }
  
  static getInstance() {
    if (!AppState.instance) {
      AppState.instance = new AppState();
    }
    return AppState.instance;
  }
}

// 服务器端使用
const serverState = new AppState();
serverState.user = { id: 1, name: 'John' };

// 客户端获取相同实例
const clientState = AppState.getInstance();
console.log(clientState.user); // { id: 1, name: 'John' }

对于更复杂的场景,可以考虑结合序列化技术:

class SerializableState {
  // ...类似上面的实现...
  
  serialize() {
    return JSON.stringify(this);
  }
  
  static deserialize(json) {
    const data = JSON.parse(json);
    const instance = new SerializableState();
    Object.assign(instance, data);
    return instance;
  }
}

// 服务器端
const state = new SerializableState();
const serialized = state.serialize();

// 将serialized嵌入HTML
// 客户端
const restoredState = SerializableState.deserialize(window.__INITIAL_STATE__);

策略模式处理渲染差异

不同页面可能需要不同的渲染策略,策略模式允许动态切换渲染逻辑而不修改主流程代码。

class RenderStrategy {
  render() {
    throw new Error('Method not implemented');
  }
}

class SSGStrategy extends RenderStrategy {
  render(component) {
    // 静态生成逻辑
    return generateStaticHTML(component);
  }
}

class SSRStrategy extends RenderStrategy {
  render(component) {
    // 服务端渲染逻辑
    return renderToString(component);
  }
}

class RenderContext {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  
  executeRender(component) {
    return this.strategy.render(component);
  }
}

// 使用示例
const context = new RenderContext(new SSRStrategy());
const html = context.executeRender(App);

// 切换到静态生成
context.setStrategy(new SSGStrategy());

观察者模式实现数据预取

SSR需要预先获取数据再渲染,观察者模式能优雅地处理数据准备和渲染的依赖关系。

class DataObserver {
  constructor() {
    this.observers = [];
    this.data = null;
  }
  
  subscribe(fn) {
    this.observers.push(fn);
  }
  
  unsubscribe(fn) {
    this.observers = this.observers.filter(subscriber => subscriber !== fn);
  }
  
  async fetchData() {
    this.data = await fetch('/api/data');
    this.notify();
  }
  
  notify() {
    this.observers.forEach(observer => observer(this.data));
  }
}

// 组件中使用
const dataObserver = new DataObserver();

class MyComponent {
  constructor() {
    dataObserver.subscribe(this.update.bind(this));
  }
  
  update(data) {
    this.render(data);
  }
  
  render(data) {
    // 使用数据渲染
  }
}

// 启动数据获取
dataObserver.fetchData();

代理模式处理API请求

在SSR环境中,服务器端和客户端的API请求方式可能不同,代理模式可以统一接口。

class ApiClient {
  request(endpoint) {
    // 基础请求实现
  }
}

class ServerApiProxy extends ApiClient {
  request(endpoint) {
    // 服务器端特定逻辑,如添加headers
    return super.request(endpoint);
  }
}

class ClientApiProxy extends ApiClient {
  request(endpoint) {
    // 客户端特定逻辑,如处理凭证
    return fetch(endpoint);
  }
}

// 根据环境创建代理
function createApiClient() {
  if (typeof window === 'undefined') {
    return new ServerApiProxy();
  } else {
    return new ClientApiProxy();
  }
}

const api = createApiClient();
api.request('/data');

装饰器模式增强组件功能

SSR场景下,组件可能需要额外的服务端能力,装饰器模式可以动态添加这些功能。

function withServerData(WrappedComponent) {
  return class extends React.Component {
    static async getInitialProps(ctx) {
      const data = await fetchServerData();
      return { ...data, ...(WrappedComponent.getInitialProps ? await WrappedComponent.getInitialProps(ctx) : {}) };
    }
    
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用装饰器
class MyComponent extends React.Component {
  // 组件实现
}

export default withServerData(MyComponent);

对于更复杂的装饰场景,可以组合多个装饰器:

function withLogger(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log('Component mounted', WrappedComponent.name);
    }
    
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 组合装饰
const EnhancedComponent = withLogger(withServerData(MyComponent));

模板方法模式统一渲染流程

SSR通常有固定的处理流程,模板方法模式可以确保步骤一致性。

abstract class RenderPipeline {
  async render() {
    await this.prepareData();
    const content = await this.renderContent();
    const html = this.wrapDocument(content);
    return html;
  }
  
  abstract prepareData();
  abstract renderContent();
  
  wrapDocument(content) {
    return `
      <!DOCTYPE html>
      <html>
        <head>
          <title>My App</title>
        </head>
        <body>
          <div id="app">${content}</div>
          <script src="/client.js"></script>
        </body>
      </html>
    `;
  }
}

class ProductPageRenderer extends RenderPipeline {
  async prepareData() {
    this.data = await fetchProductData();
  }
  
  async renderContent() {
    return renderToString(<ProductPage data={this.data} />);
  }
}

// 使用
const renderer = new ProductPageRenderer();
const html = await renderer.render();

状态模式管理渲染生命周期

SSR过程涉及多个状态转换,状态模式可以清晰管理这些状态。

class RenderState {
  constructor(context) {
    this.context = context;
  }
  
  start() {
    throw new Error('Method not implemented');
  }
  
  complete() {
    throw new Error('Method not implemented');
  }
  
  error() {
    throw new Error('Method not implemented');
  }
}

class InitialState extends RenderState {
  start() {
    console.log('Starting render process');
    this.context.setState(new DataFetchingState(this.context));
  }
}

class DataFetchingState extends RenderState {
  async start() {
    try {
      this.context.data = await fetchData();
      this.context.setState(new RenderingState(this.context));
      this.context.currentState.start();
    } catch (err) {
      this.context.setState(new ErrorState(this.context, err));
    }
  }
}

class RenderingState extends RenderState {
  start() {
    this.context.html = renderToString(this.context.app);
    this.context.setState(new CompletedState(this.context));
  }
}

class RenderContext {
  constructor() {
    this.setState(new InitialState(this));
    this.data = null;
    this.html = null;
  }
  
  setState(state) {
    this.currentState = state;
  }
  
  async render() {
    await this.currentState.start();
  }
}

// 使用
const context = new RenderContext();
await context.render();

组合模式构建页面结构

复杂页面通常由多个部分组成,组合模式可以统一处理整体和部分的关系。

class PageComponent {
  constructor(name) {
    this.name = name;
    this.children = [];
  }
  
  add(component) {
    this.children.push(component);
  }
  
  async render() {
    const childrenHtml = await Promise.all(
      this.children.map(child => child.render())
    );
    return `
      <section class="${this.name}">
        ${childrenHtml.join('')}
      </section>
    `;
  }
}

class HeaderComponent {
  async render() {
    return '<header>Header Content</header>';
  }
}

class FooterComponent {
  async render() {
    return '<footer>Footer Content</footer>';
  }
}

// 构建页面
const page = new PageComponent('app');
page.add(new HeaderComponent());
page.add(new PageComponent('main-content'));
page.add(new FooterComponent());

const html = await page.render();

适配器模式整合不同SSR框架

当需要整合多个SSR解决方案时,适配器模式可以提供统一接口。

class NextJSAdapter {
  constructor(nextApp) {
    this.nextApp = nextApp;
  }
  
  async render(req, res) {
    return this.nextApp.render(req, res);
  }
}

class NuxtJSAdapter {
  constructor(nuxt) {
    this.nuxt = nuxt;
  }
  
  async render(req, res) {
    return this.nuxt.render(req, res);
  }
}

class SSRGateway {
  constructor(adapter) {
    this.adapter = adapter;
  }
  
  setAdapter(adapter) {
    this.adapter = adapter;
  }
  
  handleRequest(req, res) {
    return this.adapter.render(req, res);
  }
}

// 使用
const nextApp = require('next')(...);
const adapter = new NextJSAdapter(nextApp);
const gateway = new SSRGateway(adapter);

// 处理请求
server.use((req, res) => gateway.handleRequest(req, res));

备忘录模式实现渲染快照

在SSR过程中,有时需要保存和恢复渲染状态,备忘录模式可以管理这些状态快照。

class RendererMemento {
  constructor(state) {
    this.state = JSON.parse(JSON.stringify(state));
  }
  
  getState() {
    return this.state;
  }
}

class SSRRenderer {
  constructor() {
    this.state = {
      data: null,
      html: '',
      error: null
    };
  }
  
  createMemento() {
    return new RendererMemento(this.state);
  }
  
  restoreMemento(memento) {
    this.state = memento.getState();
  }
  
  async render() {
    try {
      const memento = this.createMemento();
      
      this.state.data = await fetchData();
      this.state.html = renderToString(<App data={this.state.data} />);
      
      return this.state;
    } catch (error) {
      this.state.error = error;
      throw error;
    }
  }
}

// 使用
const renderer = new SSRRenderer();

// 渲染前保存状态
const beforeRender = renderer.createMemento();

try {
  await renderer.render();
} catch (error) {
  // 出错时恢复状态
  renderer.restoreMemento(beforeRender);
}

责任链模式处理渲染中间件

SSR流程通常需要经过多个处理步骤,责任链模式可以灵活组织这些步骤。

class RenderMiddleware {
  constructor() {
    this.nextMiddleware = null;
  }
  
  setNext(middleware) {
    this.nextMiddleware = middleware;
    return middleware;
  }
  
  async process(request, response) {
    if (this.nextMiddleware) {
      return await this.nextMiddleware.process(request, response);
    }
    return null;
  }
}

class DataFetchingMiddleware extends RenderMiddleware {
  async process(request, response) {
    request.data = await fetchData(request.url);
    return super.process(request, response);
  }
}

class RenderingMiddleware extends RenderMiddleware {
  async process(request, response) {
    request.html = renderToString(<App data={request.data} />);
    return super.process(request, response);
  }
}

class ResponseMiddleware extends RenderMiddleware {
  async process(request, response) {
    response.send(request.html);
    return true;
  }
}

// 构建责任链
const dataMiddleware = new DataFetchingMiddleware();
const renderMiddleware = new RenderingMiddleware();
const responseMiddleware = new ResponseMiddleware();

dataMiddleware.setNext(renderMiddleware).setNext(responseMiddleware);

// 处理请求
server.use(async (req, res) => {
  await dataMiddleware.process(req, res);
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌