MST

星途 面试题库

面试题:React 服务端渲染下 React 生命周期与同构应用状态管理的深度结合

在构建一个大型同构 React 应用(同时支持服务端渲染和客户端渲染)时,如何将 React 生命周期函数与状态管理方案(如 Redux 或 MobX)深度结合,以确保服务端和客户端状态的一致性,避免出现水合(Hydration)问题,并优化渲染性能。请详细阐述你的设计思路和实现要点。
43.5万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 统一状态管理:使用 Redux 或 MobX 作为状态管理方案,确保服务端和客户端共享相同的状态逻辑。这意味着在服务端渲染(SSR)期间,初始化状态并将其序列化传递到客户端,客户端在挂载时可以直接使用这些状态。
  2. 生命周期钩子的合理使用:在 React 组件的生命周期函数中,结合状态管理方案进行数据获取、状态更新等操作。例如,在 componentDidMount(客户端)或 getInitialProps(Next.js 等 SSR 框架特定)中发起异步数据请求,并将结果更新到状态管理库中。
  3. 避免水合问题:确保服务端渲染生成的 HTML 与客户端渲染的初始状态一致。这可以通过在服务端预填充状态,并在客户端避免重复获取已经在服务端获取的数据来实现。
  4. 优化渲染性能:利用状态管理库的特性,如 Redux 的 shouldComponentUpdate 或 MobX 的响应式编程,减少不必要的重新渲染。同时,使用 memoization 技术,如 React.memo 或 MobX 的 computed,缓存计算结果。

实现要点

  1. Redux 结合实现
    • 服务端
      • 在服务端渲染时,创建 Redux store 并使用中间件(如 redux-thunkredux-saga)处理异步操作。
      • 例如,在处理请求时,根据请求参数获取数据并更新 store 状态。
      import { createStore, applyMiddleware } from'redux';
      import thunk from'redux-thunk';
      import rootReducer from './reducers';
      
      const serverStore = createStore(rootReducer, applyMiddleware(thunk));
      
      // 假设存在一个异步 action 获取用户数据
      serverStore.dispatch(fetchUserData());
      
      • 将 store 的状态序列化并传递到客户端。
    • 客户端
      • 在客户端创建相同的 Redux store,并使用服务端传递过来的初始状态进行初始化。
      const preloadedState = window.__PRELOADED_STATE__;
      const clientStore = createStore(rootReducer, preloadedState, applyMiddleware(thunk));
      
      • 在 React 组件中,使用 react - reduxconnectuseSelector/useDispatch hooks 来连接组件与 Redux store。
      • componentDidMount 中,避免重复获取已经在服务端获取的数据。例如,可以通过判断状态是否已经存在来决定是否发起请求。
      import React, { Component } from'react';
      import { connect } from'react - redux';
      import { fetchUserData } from './actions';
      
      class UserComponent extends Component {
          componentDidMount() {
              if (!this.props.user) {
                  this.props.fetchUserData();
              }
          }
          render() {
              return <div>{this.props.user.name}</div>;
          }
      }
      
      const mapStateToProps = state => ({
          user: state.user
      });
      
      const mapDispatchToProps = {
          fetchUserData
      };
      
      export default connect(mapStateToProps, mapDispatchToProps)(UserComponent);
      
  2. MobX 结合实现
    • 服务端
      • 创建 MobX store 实例,并在服务端处理请求时更新 store 状态。
      import { makeObservable, observable, action } from'mobx';
      
      class UserStore {
          user = null;
      
          constructor() {
              makeObservable(this, {
                  user: observable,
                  fetchUserData: action
              });
          }
      
          async fetchUserData() {
              const response = await fetch('/api/user');
              const data = await response.json();
              this.user = data;
          }
      }
      
      const serverUserStore = new UserStore();
      serverUserStore.fetchUserData();
      
      • 将 store 的状态序列化并传递到客户端。
    • 客户端
      • 在客户端创建相同的 MobX store 实例,并使用服务端传递过来的初始状态进行初始化。
      const preloadedState = window.__PRELOADED_STATE__;
      const clientUserStore = new UserStore();
      clientUserStore.user = preloadedState.user;
      
      • 在 React 组件中,使用 mobx - reactobserver 函数将组件转换为响应式组件。
      import React from'react';
      import { observer } from'mobx - react';
      import UserStore from './UserStore';
      
      const UserComponent = observer(() => {
          return <div>{UserStore.user.name}</div>;
      });
      
      export default UserComponent;
      
  3. 避免水合问题
    • 数据预取一致性:确保服务端和客户端使用相同的数据预取逻辑。例如,在 Redux 中,使用相同的 action creator 和 reducer 逻辑。
    • 状态同步:在客户端渲染开始时,立即将服务端传递过来的状态应用到客户端状态管理库中,避免客户端再次获取相同数据。
  4. 优化渲染性能
    • Redux
      • 使用 shouldComponentUpdate 生命周期函数或 React.memo 来控制组件的重新渲染。例如,通过比较前后 props 中的状态数据来决定是否重新渲染。
      class MyComponent extends Component {
          shouldComponentUpdate(nextProps) {
              return nextProps.user!== this.props.user;
          }
          render() {
              return <div>{this.props.user.name}</div>;
          }
      }
      
    • MobX
      • 使用 computed 来缓存计算结果,避免重复计算。例如,计算用户的全名。
      class UserStore {
          firstname = 'John';
          lastname = 'Doe';
      
          constructor() {
              makeObservable(this, {
                  firstname: observable,
                  lastname: observable,
                  fullname: computed
              });
          }
      
          get fullname() {
              return `${this.firstname} ${this.lastname}`;
          }
      }
      
      • 利用 MobX 的响应式编程特性,只有当依赖的 observable 数据发生变化时,组件才会重新渲染。