You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中基于HATEOAS的REST API加载关联角色及页面无内容问题

问题描述

我正在用Spring Data Rest Repository构建React网站,User和Role是一对一关系。我需要在用户页面展示所有用户及其角色信息,最初通过useEffect钩子加载用户数据并能显示用户名,但不知道如何加载关联角色并填充角色列。参考官方示例修改代码后,页面却无任何内容显示。

API响应示例

用户接口响应

{
  "_embedded" : {
    "users" : [ {
      "firstName" : "Adam",
      "lastName" : "Thomas",
      "email" : "adma.thomas@example.com",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/users/1"
        },
        "user" : {
          "href" : "http://localhost:8080/api/users/1"
        },
        "role" : {
          "href" : "http://localhost:8080/api/users/1/role"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/users"
    }
  }
}

角色接口响应

{
  "_embedded" : {
    "users" : [ {
      "name" : "Admin",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/roles/1"
        },
        "user" : {
          "href" : "http://localhost:8080/api/roles/1"
        }
      }
    },{
      "name" : "Developer",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/roles/2"
        },
        "user" : {
          "href" : "http://localhost:8080/api/roles/2"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/roles"
    }
  }
}

初始实现(仅加载用户)

useEffect(() => {
    axios.get("http://localhost:8080/api/users")
        .then(response => setUsers(response.data._embedded.users))            
});

修改后的代码(尝试加载角色但页面空白)

export default function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    follow(client, API_ROOT, [{ rel: "users" }])
      .then((userCollection) => {
        return userCollection.entity._embedded.users.map(
          (user) =>
            client({
              method: "GET",
              path: user._links.self.href,
            })
        );
      })
      .then((userPromises) => {
        return when.all(userPromises);
      })
      .then((users) => {
        setUsers(users.map((user) => user.entity));

        return users.map((user) => {
          return client({
            method: "GET",
            path: user.entity._links.role.href,
          });
        });
      })
      .then((rolePromises) => {
        return when.all(rolePromises);
      })
      .then((roles) => {
        const usersWithRole = users.map((user, index) => 
        ({
          ...user,
          userRole: roles[index].entity.name,
        })
        );
        setUsers(usersWithRole);
      });
  }, []);

  const userList = users.map((user) => {
    return (
      <tr key={user._links.self.href}>
        <td className="center">{user.firstName} {user.lastName}</td>
        <td>{user.userRole}</td>
      </tr>
    );
  });

  return (
    <table>
      <caption>Users:</caption>
      <thead>
        <tr>
          <th>Name</th>
          <th>Role</th>
        </tr>
      </thead>
      <tbody>{userList}</tbody>
    </table>
  );
}

解决方案

问题分析

修改后的代码存在两个核心问题:

  1. 状态闭包问题:在处理角色数据时,引用的users是初始空数组(React闭包特性导致),拼接用户和角色信息时完全错误。
  2. 无错误处理机制:API请求若失败会直接中断流程,且没有任何反馈,无法排查问题。
  3. 冗余请求:重复请求用户详情接口,用户列表接口已返回足够的基础信息。

修复后的代码

import { useState, useEffect } from 'react';
import axios from 'axios';

export default function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsersWithRoles = async () => {
      try {
        // 1. 获取用户列表
        const userResponse = await axios.get("http://localhost:8080/api/users");
        const rawUsers = userResponse.data._embedded.users;

        // 2. 并行请求所有用户的角色信息
        const roleRequests = rawUsers.map(user => axios.get(user._links.role.href));
        const roleResponses = await Promise.all(roleRequests);

        // 3. 拼接用户与角色数据
        const usersWithRoles = rawUsers.map((user, index) => ({
          ...user,
          userRole: roleResponses[index].data.name
        }));

        setUsers(usersWithRoles);
      } catch (err) {
        setError('加载用户数据失败,请稍后重试');
        console.error('请求出错:', err);
      } finally {
        setLoading(false);
      }
    };

    fetchUsersWithRoles();
  }, []);

  // 加载状态反馈
  if (loading) return <div>加载中...</div>;
  // 错误状态反馈
  if (error) return <div style={{color: 'red'}}>{error}</div>;

  return (
    <table>
      <caption>用户列表</caption>
      <thead>
        <tr>
          <th>姓名</th>
          <th>角色</th>
        </tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user._links.self.href}>
            <td>{user.firstName} {user.lastName}</td>
            <td>{user.userRole}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

关键优化点

  • 用async/await简化异步流程:相比Promise链式调用,代码更易读且便于调试。
  • 并行请求角色数据:通过Promise.all同时发起所有角色请求,提升加载效率。
  • 修复闭包问题:在同一作用域内处理用户和角色数据,确保引用的是最新的用户列表。
  • 增加状态反馈:添加加载中和错误状态,给用户明确的提示,同时便于开发排查问题。
  • 移除冗余请求:直接使用用户列表接口返回的数据,无需重复请求用户详情。

额外优化建议

如果希望减少前端请求次数,可以在Spring Data Rest中配置投影(Projection),让用户列表接口直接返回角色信息:

  1. 创建投影接口:
@Projection(name = "withRole", types = User.class)
public interface UserWithRoleProjection {
    String getFirstName();
    String getLastName();
    String getRoleName(); // 直接返回角色名称,需在User实体中添加对应的getter方法
}
  1. 修改用户Repository,默认使用该投影:
@RepositoryRestResource(excerptProjection = UserWithRoleProjection.class)
public interface UserRepository extends JpaRepository<User, Long> {
}

配置完成后,用户列表接口会直接包含角色名称,前端无需额外请求角色接口,进一步提升性能。


内容的提问来源于stack exchange,提问作者Kiran Kyle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:37:01