Chapter 08

异步与资源管理

createResource 让异步数据成为响应式 Signal,Suspense 优雅处理加载状态

1. createResource — 异步数据源

createResource 是 SolidJS 处理异步数据的核心原语。它将一个异步函数包装成响应式 Signal,提供加载状态、错误状态和数据本身。

import { createResource } from "solid-js";

interface User { id: number; name: string; email: string; }

// 异步获取函数
const fetchUser = async (id: string): Promise<User> => {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
};

function UserCard() {
  const [userId, setUserId] = createSignal("1");

  // createResource(source, fetcher)
  // source:依赖的 Signal,变化时自动重新请求
  // fetcher:接收 source 值,返回 Promise
  const [user, { refetch, mutate }] = createResource(userId, fetchUser);

  return (
    <div>
      {/* user() 的状态:undefined(初始)/ Promise 中 / 数据 / 错误 */}
      <p>loading: {String(user.loading)}</p>
      <p>error: {user.error?.message}</p>

      <Show when={!user.loading && !user.error}>
        <h2>{user()?.name}</h2>
        <p>{user()?.email}</p>
      </Show>

      {/* 切换用户 ID,自动重新请求 */}
      <button onClick={() => setUserId("2")}>查看用户2</button>
      {/* 手动重新请求 */}
      <button onClick={refetch}>刷新</button>
    </div>
  );
}

createResource 的状态属性

2. <Suspense> — 声明式加载状态

<Suspense> 配合 createResource 使用,当任意子组件有未完成的资源请求时,自动显示 fallback UI:

import { Suspense } from "solid-js";

function UserProfile() {
  const params = useParams();
  const [user] = createResource(() => params.id, fetchUser);
  const [posts] = createResource(() => params.id, fetchUserPosts);

  // 直接使用数据,不需要检查 loading 状态
  return (
    <div>
      <h1>{user().name}</h1>
      <For each={posts()}>
        {(post) => <p>{post.title}</p>}
      </For>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={
      <div class="skeleton">
        <div class="skeleton-avatar"></div>
        <div class="skeleton-text"></div>
      </div>
    }>
      <UserProfile />
    </Suspense>
  );
}

3. ErrorBoundary + Suspense 组合

import { ErrorBoundary, Suspense } from "solid-js";

function DataPage() {
  return (
    <ErrorBoundary
      fallback={(err, reset) => (
        <div class="error-state">
          <p>加载失败: {err.message}</p>
          <button onClick={reset}>重试</button>
        </div>
      )}
    >
      <Suspense fallback={<p>加载中...</p>}>
        <DataComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

4. 并发请求

function Dashboard() {
  // 多个 Resource 并行请求——不会互相等待
  const [stats] = createResource(fetchStats);
  const [users] = createResource(fetchUsers);
  const [orders] = createResource(fetchOrders);

  // Suspense 等待所有请求完成后统一显示
  return (
    <Suspense fallback={<p>加载仪表盘...</p>}>
      <StatsPanel data={stats()} />
      <UserTable data={users()} />
      <OrderList data={orders()} />
    </Suspense>
  );
}

5. 实战:带加载/错误状态的数据列表

import { createResource, createSignal } from "solid-js";
import { For, Show, Suspense, ErrorBoundary } from "solid-js";

interface Article { id: number; title: string; author: string; date: string; }

const fetchArticles = async (page: number): Promise<Article[]> => {
  const res = await fetch(`/api/articles?page=${page}`);
  return res.json();
};

function ArticleList() {
  const [page, setPage] = createSignal(1);
  const [articles, { refetch }] = createResource(page, fetchArticles);

  return (
    <div>
      <div class="toolbar">
        <button onClick={refetch}>刷新</button>
        <span>第 {page()} 页</span>
      </div>

      {/* state === "refreshing" 时显示刷新指示器 */}
      <Show when={articles.state === "refreshing"}>
        <div class="refresh-banner">正在更新...</div>
      </Show>

      <ErrorBoundary fallback={(err, reset) => (
        <div>
          <p>加载失败: {err.message}</p>
          <button onClick={reset}>重试</button>
        </div>
      )}>
        <Suspense fallback={<p>加载文章...</p>}>
          <For each={articles()} fallback={<p>暂无文章</p>}>
            {(article) => (
              <article>
                <h2>{article.title}</h2>
                <p>{article.author} · {article.date}</p>
              </article>
            )}
          </For>
        </Suspense>
      </ErrorBoundary>

      <div class="pagination">
        <button disabled={page() <= 1} onClick={() => setPage(p => p - 1)}>上一页</button>
        <button onClick={() => setPage(p => p + 1)}>下一页</button>
      </div>
    </div>
  );
}

本章小结:createResource 将异步请求封装为响应式 Signal,自动追踪 source 变化并重新请求,提供 loading/error/state 等状态属性。Suspense 配合 Resource 声明式处理加载状态,ErrorBoundary 捕获请求失败,refetch/mutate 支持手动控制。