MST
星途 面试题库

面试题:React Router在服务端渲染中处理动态路由数据加载

在React服务端渲染场景下,对于包含动态参数的路由,如 /user/:id ,如何通过React Router实现服务端和客户端都能正确加载该用户对应的详细数据?描述实现思路,并说明可能遇到的问题及解决方案。
20.2万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 服务端渲染(SSR)部分
    • 在服务端,使用 react - router - dom 中的 StaticRouter。它可以根据请求的URL匹配对应的路由。例如:
    import { StaticRouter } from'react - router - dom';
    import React from'react';
    import { renderToString } from'react - dom/server';
    import App from './App';
    
    export default (req, res) => {
      const context = {};
      const html = renderToString(
        <StaticRouter location={req.url} context={context}>
          <App />
        </StaticRouter>
      );
      // 如果是动态路由匹配失败,context.url 会被赋值新的URL
      if (context.url) {
        res.writeHead(301, { Location: context.url });
        res.end();
      } else {
        res.send(`
          <!DOCTYPE html>
          <html>
            <head>
              <title>SSR with React Router</title>
            </head>
            <body>
              <div id="root">${html}</div>
              <script src="/client - bundle.js"></script>
            </body>
          </html>
        `);
      }
    };
    
    • 在组件中,通过 useParams 钩子获取动态参数 id,然后使用这个 id 去请求用户详细数据。例如:
    import React, { useEffect, useState } from'react';
    import { useParams } from'react - router - dom';
    
    const UserDetails = () => {
      const { id } = useParams();
      const [userData, setUserData] = useState(null);
    
      useEffect(() => {
        const fetchUserData = async () => {
          const response = await fetch(`/api/users/${id}`);
          const data = await response.json();
          setUserData(data);
        };
        fetchUserData();
      }, [id]);
    
      if (!userData) {
        return <div>Loading...</div>;
      }
    
      return (
        <div>
          <h1>{userData.name}</h1>
          <p>{userData.bio}</p>
        </div>
      );
    };
    
    export default UserDetails;
    
  2. 客户端渲染部分
    • 在客户端,使用 BrowserRouter 来管理路由。例如:
    import React from'react';
    import ReactDOM from'react - dom';
    import { BrowserRouter } from'react - router - dom';
    import App from './App';
    
    ReactDOM.hydrate(
      <BrowserRouter>
        <App />
      </BrowserRouter>,
      document.getElementById('root')
    );
    
    • 同样在组件中使用 useParams 获取参数并加载数据,逻辑与服务端渲染时类似。

可能遇到的问题及解决方案

  1. 数据预取一致性问题
    • 问题:服务端和客户端可能因为数据获取时机不同,导致渲染结果不一致。例如,服务端渲染时获取到的数据,在客户端重新获取时可能已经发生了变化。
    • 解决方案:可以在服务端渲染时,将获取到的数据作为初始数据传递给客户端。在服务端,将数据添加到HTML的一个全局变量中,例如:
    const userData = await fetchUserData(id);
    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>SSR with React Router</title>
        </head>
        <body>
          <div id="root">${html}</div>
          <script>
            window.__INITIAL_USER_DATA__ = ${JSON.stringify(userData)};
          </script>
          <script src="/client - bundle.js"></script>
        </body>
      </html>
    `);
    
    在客户端,组件挂载时优先使用这个初始数据,然后再进行数据更新。例如:
    const UserDetails = () => {
      const { id } = useParams();
      const [userData, setUserData] = useState(window.__INITIAL_USER_DATA__);
    
      useEffect(() => {
        const fetchUserData = async () => {
          const response = await fetch(`/api/users/${id}`);
          const data = await response.json();
          setUserData(data);
        };
        if (!userData) {
          fetchUserData();
        }
      }, [id, userData]);
    
      if (!userData) {
        return <div>Loading...</div>;
      }
    
      return (
        <div>
          <h1>{userData.name}</h1>
          <p>{userData.bio}</p>
        </div>
      );
    };
    
  2. 路由匹配问题
    • 问题:在服务端渲染时,可能出现路由匹配失败的情况,导致返回错误页面或者无法正确渲染。
    • 解决方案:通过 StaticRoutercontext 对象来处理重定向或错误。如上面服务端渲染代码中,如果 context.url 被赋值,说明路由匹配失败,需要进行重定向。
  3. SEO 相关问题
    • 问题:搜索引擎爬虫可能无法正确解析JavaScript渲染的数据,影响SEO。
    • 解决方案:确保服务端渲染时已经将关键数据渲染到HTML中,这样搜索引擎爬虫可以直接获取到这些数据。同时,可以使用 next - head 等库来优化页面的元数据,提高SEO效果。例如:
    import React from'react';
    import { useParams } from'react - router - dom';
    import Head from 'next - head';
    
    const UserDetails = () => {
      const { id } = useParams();
      const [userData, setUserData] = useState(null);
    
      useEffect(() => {
        const fetchUserData = async () => {
          const response = await fetch(`/api/users/${id}`);
          const data = await response.json();
          setUserData(data);
        };
        fetchUserData();
      }, [id]);
    
      if (!userData) {
        return <div>Loading...</div>;
      }
    
      return (
        <div>
          <Head>
            <title>{userData.name} - User Details</title>
            <meta name="description" content={userData.bio} />
          </Head>
          <h1>{userData.name}</h1>
          <p>{userData.bio}</p>
        </div>
      );
    };
    
    export default UserDetails;