用途
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 发现或内容类型检查之前,先验证本地设置:
- 检查项目根目录是否存在 .env 文件。
- 如果 .env 存在,验证 CANVAS_SITE_URL 已设置。读取 CANVAS_JSONAPI_PREFIX(如果存在);否则使用 jsonapi。
- 向 {CANVAS_SITE_URL}/{CANVAS_JSONAPI_PREFIX} 发送 HTTP 请求。成功意味着 HTTP 200。
- 如果请求成功,继续 Drupal 数据获取。
- 如果请求不成功(或缺少必需的 .env 值),询问用户是否希望:
- 立即配置 Drupal 连接,或
- 继续使用静态内容而非 Drupal 获取。
- 如果用户选择配置连接,提供 .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 站点可用性解决。
- 只使用内容类型上实际存在的字段,不要假设字段存在。
- 筛选选项不要硬编码,应动态获取。