SuperAPI AI 工具集

canvas-data-fetching

前端与建站

查看项目

更新于 10-15 10:52

技能介绍

用途

canvas-data-fetching 用于在 Canvas 组件中获取并渲染 Drupal 内容,基于 JSON:API 与 SWR 模式。适用于构建内容列表、集成 SWR,或查询关联内容等场景。

主要能力

使用 SWR 进行数据获取

技能建议所有数据获取都使用 SWR,它提供缓存、重新验证以及简洁的基于 Hook 的 API。示例:

import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function Profile() {
  const { data, error, isLoading } = useSWR(
    "https://my-site.com/api/user",
    fetcher,
  );

  if (error) return "Failed to load";
  if (isLoading) return "Loading...";
  return `Hello, ${data.name}!`;
}

使用 JSON:API 获取 Drupal 内容

要从 Drupal 获取内容(如文章、活动或其他内容类型),使用 drupal-canvas 包中自动配置的 JsonApiClient,并结合 DrupalJsonApiParams 构建查询。

重要:不要在 Storybook 故事中 mock JSON:API 资源。获取数据的组件在 Storybook 中会显示其加载或空状态。

import { getNodePath, JsonApiClient } from "drupal-canvas";
import { DrupalJsonApiParams } from "drupal-jsonapi-params";
import useSWR from "swr";

const Articles = () => {
  const client = new JsonApiClient();
  const { data, error, isLoading } = useSWR(
    [
      "node--article",
      {
        queryString: new DrupalJsonApiParams()
          .addSort("created", "DESC")
          .getQueryString(),
      },
    ],
    ([type, options]) => client.getCollection(type, options),
  );

  if (error) return "An error has occurred.";
  if (isLoading) return "Loading...";
  return (
    <ul>
      {data.map((article) => (
        <li key={article.id}>
          <a href={getNodePath(article)}>{article.title}</a>
        </li>
      ))}
    </ul>
  );
};

export default Articles;

使用 addInclude 包含关联实体

当需要关联实体(如图片、分类术语)时,使用 addInclude 在单个请求中获取它们。

避免 JSON:API 响应中的循环引用。SWR 使用深度相等检查来比较缓存数据,当响应包含循环引用时会因 "too much recursion" 错误而失败。

不要包含自引用字段。引用正在查询的同一实体类型的字段(例如文章查询上的 field_related_articles)会产生循环引用:文章 A 引用文章 B,文章 B 又引用回文章 A。如果需要同类型的相关内容,请在单独的查询中获取。

使用 addFields 限制响应。始终只指定需要的字段。这能提升性能并有助于避免循环引用问题:

const params = new DrupalJsonApiParams();
params.addSort("created", "DESC");
params.addInclude(["field_category", "field_image"]);

// Limit fields for each entity type
params.addFields("node--article", [
  "title",
  "created",
  "field_category",
  "field_image",
]);
params.addFields("taxonomy_term--categories", ["name"]);
params.addFields("file--file", ["uri", "url"]);

使用方式

创建内容列表组件

构建显示内容项列表的组件(如新闻列表、活动日历或资源库)时,遵循以下工作流:

设置门禁(Setup gate)

在进行任何 JSON:API 发现或内容类型检查之前,先验证本地设置:

  1. 检查项目根目录是否存在 .env 文件。
  2. 如果 .env 存在,验证 CANVAS_SITE_URL 已设置。读取 CANVAS_JSONAPI_PREFIX(如果存在);否则使用 jsonapi。
  3. 向 {CANVAS_SITE_URL}/{CANVAS_JSONAPI_PREFIX} 发送 HTTP 请求。成功意味着 HTTP 200。
  4. 如果请求成功,继续 Drupal 数据获取。
  5. 如果请求不成功(或缺少必需的 .env 值),询问用户是否希望:
    • 立即配置 Drupal 连接,或
    • 继续使用静态内容而非 Drupal 获取。
  6. 如果用户选择配置连接,提供 .env 说明:
CANVAS_SITE_URL=<their Drupal site URL>
CANVAS_JSONAPI_PREFIX=jsonapi (optional; defaults to jsonapi)

然后等待用户确认已更新 .env,并再次测试请求。

如果用户选择不配置连接,则继续使用静态内容。

不要更新 Vite 配置(vite.config.*)来排查连接问题。连接问题必须通过正确的 .env 值和 Drupal 站点可用性来解决,而不是通过构建工具更改。

第 1 步:分析列表结构

检查设计以了解每个列表项需要哪些数据:

  • 显示哪些字段(标题、日期、图片、分类等)?
  • 项目如何排序(最新优先、字母顺序等)?
  • 是否有筛选器或分页?

第 2 步:识别或请求内容类型

在编写代码之前,验证 Drupal 中是否存在合适的内容类型:

  • 检查本地 Drupal 站点的 JSON:API 端点(通过 CANVAS_SITE_URL 和 CANVAS_JSONAPI_PREFIX 环境变量配置),找到与所需结构匹配的内容类型。在通过设置门禁后,使用普通 fetch 请求进行此检查。
  • 如果存在匹配的内容类型,使用它并记录哪些字段可用。
  • 如果不存在匹配的内容类型,停止并提示用户创建一个。提供:
    • 建议的内容类型名称
    • 基于列表设计所需的字段结构

第 3 步:构建组件

使用 JSON:API 获取内容来创建内容列表组件。只使用内容类型上实际存在的字段——不要在未验证的情况下假设字段存在。

处理筛选器

如果列表包含基于实体引用字段的筛选器(例如按分类筛选、按作者筛选):

  • 不要硬编码筛选选项。筛选选项应使用 JSON:API 动态获取。
  • 获取每个筛选器的可用选项(例如某个词汇表中的所有分类术语),并据此填充筛选器 UI。

这确保筛选器与 Drupal 中的实际内容保持同步,新选项会自动出现,无需更改代码。

适用场景

  • 构建内容列表组件,如新闻列表、活动日历、资源库。
  • 在 Canvas 组件中集成 SWR 进行数据获取。
  • 查询 Drupal 内容及其关联实体(图片、分类术语等)。
  • 需要动态筛选选项、与 Drupal 内容保持同步的列表界面。

限制

  • 不要在 Storybook 故事中 mock JSON:API 资源;获取数据的组件在 Storybook 中会显示加载或空状态。
  • 避免 JSON:API 响应中的循环引用,否则 SWR 的深度相等检查会因 "too much recursion" 错误失败。
  • 不要包含自引用字段;同类型相关内容应在单独查询中获取。
  • 不要更新 Vite 配置来排查连接问题;连接问题应通过 .env 值和 Drupal 站点可用性解决。
  • 只使用内容类型上实际存在的字段,不要假设字段存在。
  • 筛选选项不要硬编码,应动态获取。