MST

星途 面试题库

面试题:React useEffect Hook与服务器端渲染(SSR)的结合优化

在一个使用React进行服务器端渲染(SSR)的项目中,如何有效地结合useEffect Hook?请分析在SSR环境下使用useEffect Hook可能遇到的问题,例如数据获取的时机、副作用执行的一致性等,并阐述相应的解决方案和最佳实践。同时,说明如何通过代码结构和设计模式来确保SSR与客户端渲染之间的平滑过渡。
25.3万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

在 React SSR 项目中结合 useEffect Hook

  1. 数据获取时机
    • 问题:在 SSR 环境下,useEffect Hook 中的数据获取代码不会在服务器端运行,这可能导致首次渲染时页面缺少数据。例如,如果在 useEffect 中发起一个 API 调用获取用户信息,服务器端渲染时该 API 调用不会执行,渲染出的 HTML 中用户信息部分会是空的。
    • 解决方案:可以在服务器端使用类似于 getServerSideProps(在 Next.js 中)或 getDataFromTree(在 Gatsby 中)的方法来进行数据获取。这些方法允许在服务器端获取数据并将其传递给 React 组件。在客户端,仍然可以使用 useEffect 来处理数据更新,例如在用户状态发生变化时重新获取数据。
    • 示例代码(Next.js)
import React, { useEffect, useState } from'react';

export async function getServerSideProps(context) {
  const response = await fetch('https://example.com/api/user');
  const data = await response.json();
  return {
    props: {
      userData: data
    }
  };
}

const UserComponent = ({ userData }) => {
  const [localUserData, setLocalUserData] = useState(userData);

  useEffect(() => {
    // 客户端数据更新逻辑,例如检测到用户登录状态变化重新获取数据
    const fetchUserData = async () => {
      const response = await fetch('https://example.com/api/user');
      const data = await response.json();
      setLocalUserData(data);
    };
    fetchUserData();
  }, []);

  return (
    <div>
      <p>{localUserData.name}</p>
    </div>
  );
};

export default UserComponent;
  1. 副作用执行的一致性
    • 问题:由于 useEffect 在服务器端不执行,在客户端执行时可能会导致与服务器端渲染结果不一致。例如,在 useEffect 中操作 DOM(如添加事件监听器),服务器端渲染时没有这些操作,客户端渲染时添加了,可能会导致页面闪烁或行为异常。
    • 解决方案:避免在 useEffect 中执行会改变页面初始渲染结构的副作用。对于 DOM 操作,可以使用 useLayoutEffect 替代 useEffectuseLayoutEffect 在浏览器布局和绘制之前执行,并且在服务器端渲染和客户端渲染时执行顺序更一致。另外,可以使用条件判断确保某些副作用仅在客户端执行,例如:
import React, { useEffect, useLayoutEffect } from'react';

const MyComponent = () => {
  useLayoutEffect(() => {
    // 在这里进行 DOM 操作,如添加事件监听器
    const element = document.getElementById('my - element');
    if (element) {
      element.addEventListener('click', () => {
        console.log('Clicked!');
      });
    }
    return () => {
      if (element) {
        element.removeEventListener('click', () => {
          console.log('Clicked!');
        });
      }
    };
  }, []);

  useEffect(() => {
    // 仅在客户端执行的逻辑,如设置第三方脚本
    if (typeof window!== 'undefined') {
      // 加载第三方脚本
      const script = document.createElement('script');
      script.src = 'https://example.com/third - party - script.js';
      document.body.appendChild(script);
      return () => {
        document.body.removeChild(script);
      };
    }
  }, []);

  return <div id="my - element">Click me</div>;
};

export default MyComponent;
  1. 确保 SSR 与客户端渲染之间的平滑过渡
    • 代码结构:将业务逻辑提取到独立的函数或模块中,以便在服务器端和客户端共享。例如,数据获取逻辑可以封装在一个函数中,在 getServerSideProps 和 useEffect 中都可以调用这个函数。
    • 设计模式:采用单向数据流模式,例如 Redux 或 MobX。这样可以确保服务器端和客户端的数据状态管理方式一致。在服务器端,可以预先填充 Redux store 中的数据,然后在客户端 hydrate(注水)这个 store,使客户端渲染能够基于服务器端传递下来的初始数据继续运行,实现平滑过渡。
    • 示例代码(Redux + Next.js)
// store.js
import { createStore } from'redux';

const initialState = {
  user: null
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'SET_USER':
      return {
      ...state,
        user: action.payload
      };
    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;
// pages/_app.js
import React from'react';
import { Provider } from'react - redux';
import store from '../store';

function MyApp({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;
// pages/index.js
import React, { useEffect } from'react';
import { useDispatch } from'react - redux';

export async function getServerSideProps(context) {
  const response = await fetch('https://example.com/api/user');
  const data = await response.json();
  return {
    props: {
      userData: data
    }
  };
}

const HomePage = ({ userData }) => {
  const dispatch = useDispatch();

  useEffect(() => {
    if (userData) {
      dispatch({ type: 'SET_USER', payload: userData });
    }
  }, [userData, dispatch]);

  return (
    <div>
      {/* 页面内容 */}
    </div>
  );
};

export default HomePage;

通过以上方法,可以有效地在 React SSR 项目中结合 useEffect Hook,并确保 SSR 与客户端渲染之间的平滑过渡。