mcpField
S
solidjs-solidstart-expert

solidjs-solidstart-expert

github.com/modra40开发592 次安装33 周前更新
代码仓库

关于 solidjs-solidstart-expert

solidjs-solidstart-expert 是使用 TypeScript 开发 SolidJS 和 SolidStart 应用的技能,涵盖响应式状态、SSR、路由、数据处理与性能,并说明常见实现陷阱。

主要功能

  1. 01SolidJS 响应式与状态模式
  2. 02SolidStart 路由和服务端操作
  3. 03SSR 与水合指导
  4. 04用于数据、表格和表单的 TanStack 集成
  5. 05错误处理与懒加载
  6. 06性能和调试参考资料

适用场景

  1. 01规划 SolidStart 应用结构
  2. 02实现响应式状态与异步数据
  3. 03构建路由和服务端操作
  4. 04排查 SSR、响应式或性能问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/modra40/claude-codex-skills-directory --skill solidjs-solidstart-expert

安装后在 Claude Code 中输入/solidjs-solidstart-expert确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
debugging.md
patterns.md
performance.md
security.md
testing.md

详细介绍

SolidJS & SolidStart 专家开发技能

为构建采用细粒度响应性的生产级应用提供资深/首席工程师级指导。

核心理念(KISS,少即是多)

1. Signals are primitive. Don't wrap unnecessarily.
2. Derived values > effects. Let reactivity flow naturally.
3. Components are functions called ONCE. Closures handle updates.
4. SSR first, hydrate smart. SolidStart handles this elegantly.
5. Type everything. TypeScript is non-negotiable.

项目初始化

SolidStart(推荐用于 95% 的项目)
# Latest SolidStart with TypeScript
npm create solid@latest my-app
# Select: SolidStart, TypeScript, TailwindCSS

# Or with pnpm (recommended)
pnpm create solid@latest my-app
Vanilla SolidJS(仅客户端 SPA)
npx degit solidjs/templates/ts my-app

决策矩阵:除非你要构建可嵌入组件、微前端,或有严格的无服务端要求,否则请使用 SolidStart。

项目结构(可用于生产环境)

src/
├── routes/                 # File-based routing (SolidStart)
│   ├── index.tsx          # / route
│   ├── about.tsx          # /about route
│   ├── users/
│   │   ├── index.tsx      # /users
│   │   ├── [id].tsx       # /users/:id (dynamic)
│   │   └── [...all].tsx   # /users/* (catch-all)
│   └── api/               # API routes
│       └── users.ts       # /api/users endpoint
├── components/
│   ├── ui/                # Primitives (Button, Input, Modal)
│   ├── features/          # Feature-specific (UserCard, PostList)
│   └── layouts/           # Layout components (MainLayout, AuthLayout)
├── lib/
│   ├── api/               # API client, fetchers
│   ├── stores/            # Global stores (createStore)
│   ├── signals/           # Shared signals
│   └── utils/             # Pure utility functions
├── hooks/                 # Custom reactive primitives
├── types/                 # TypeScript types/interfaces
├── styles/                # Global styles, Tailwind config
└── entry-server.tsx       # Server entry (SolidStart)
└── entry-client.tsx       # Client entry (SolidStart)

响应性基础

Signals(原子状态)
import { createSignal, createEffect, createMemo } from 'solid-js';

// ✅ CORRECT: Simple, atomic state
const [count, setCount] = createSignal(0);
const [user, setUser] = createSignal<User | null>(null);

// ✅ Derived state with createMemo (NOT createEffect!)
const doubleCount = createMemo(() => count() * 2);
const isLoggedIn = createMemo(() => user() !== null);

// ✅ Effects for side effects ONLY
createEffect(() => {
  console.log('Count changed:', count());
  // Side effect: localStorage, analytics, DOM manipulation
});

// ❌ WRONG: Don't derive state in effects
createEffect(() => {
  setDoubleCount(count() * 2); // Anti-pattern!
});
Stores(复杂状态)
import { createStore, produce, reconcile } from 'solid-js/store';

interface AppState {
  user: User | null;
  todos: Todo[];
  settings: Settings;
}

const [state, setState] = createStore<AppState>({
  user: null,
  todos: [],
  settings: { theme: 'dark', lang: 'id' },
});

// ✅ Fine-grained updates with produce (Immer-like)
const addTodo = (todo: Todo) => {
  setState(produce((s) => {
    s.todos.push(todo);
  }));
};

// ✅ Path-based updates (more performant)
const updateTodo = (id: string, text: string) => {
  setState('todos', (t) => t.id === id, 'text', text);
};

// ✅ Replace entire array with reconcile (smart diffing)
const setTodos = (newTodos: Todo[]) => {
  setState('todos', reconcile(newTodos));
};

// ✅ Nested path updates
setState('settings', 'theme', 'light');
Resources(异步数据)
import { createResource, Suspense, ErrorBoundary } from 'solid-js';

// ✅ Basic resource
const [user] = createResource(() => fetchUser(userId()));

// ✅ With source signal (refetches on change)
const [userId, setUserId] = createSignal('1');
const [user, { mutate, refetch }] = createResource(userId, fetchUser);

// ✅ Resource with initial value (SSR-friendly)
const [posts] = createResource(
  () => fetchPosts(),
  { initialValue: [] }
);

// ✅ Usage in components
function UserProfile() {
  return (
    <ErrorBoundary fallback={(err) => <ErrorDisplay error={err} />}>
      <Suspense fallback={<Skeleton />}>
        <Show when={user()} fallback={<NotFound />}>
          {(u) => <UserCard user={u()} />}
        </Show>
      </Suspense>
    </ErrorBoundary>
  );
}

TanStack 集成

TanStack Query(服务端状态)
// lib/query.ts
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,
      gcTime: 10 * 60 * 1000,
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

// hooks/useUsers.ts
import { createQuery, createMutation, useQueryClient } from '@tanstack/solid-query';

export function useUsers() {
  return createQuery(() => ({
    queryKey: ['users'],
    queryFn: () => api.getUsers(),
  }));
}

export function useUser(id: Accessor<string>) {
  return createQuery(() => ({
    queryKey: ['users', id()],
    queryFn: () => api.getUser(id()),
    enabled: !!id(),
  }));
}

export function useCreateUser() {
  const queryClient = useQueryClient();
  return createMutation(() => ({
    mutationFn: (data: CreateUserDTO) => api.createUser(data),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
    onError: (error) => toast.error(error.message),
  }));
}

// ✅ Optimistic updates
export function useUpdateUser() {
  const queryClient = useQueryClient();
  return createMutation(() => ({
    mutationFn: ({ id, data }: { id: string; data: UpdateUserDTO }) =>
      api.updateUser(id, data),
    onMutate: async ({ id, data }) => {
      await queryClient.cancelQueries({ queryKey: ['users', id] });
      const previous = queryClient.getQueryData(['users', id]);
      queryClient.setQueryData(['users', id], (old: User) => ({ ...old, ...data }));
      return { previous };
    },
    onError: (_err, { id }, context) => {
      queryClient.setQueryData(['users', id], context?.previous);
    },
    onSettled: (_, __, { id }) => {
      queryClient.invalidateQueries({ queryKey: ['users', id] });
    },
  }));
}
TanStack Table
import {
  createSolidTable, getCoreRowModel, getSortedRowModel,
  getFilteredRowModel, getPaginationRowModel, flexRender,
} from '@tanstack/solid-table';

function UsersTable() {
  const [sorting, setSorting] = createSignal<SortingState>([]);
  const [globalFilter, setGlobalFilter] = createSignal('');

  const table = createSolidTable({
    get data() { return users() ?? []; },
    columns,
    state: {
      get sorting() { return sorting(); },
      get globalFilter() { return globalFilter(); },
    },
    onSortingChange: setSorting,
    onGlobalFilterChange: setGlobalFilter,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  });

  return (
    <table>
      <thead>
        <For each={table.getHeaderGroups()}>
          {(headerGroup) => (
            <tr>
              <For each={headerGroup.headers}>
                {(header) => (
                  <th onClick={header.column.getToggleSortingHandler()}>
                    {flexRender(header.column.columnDef.header, header.getContext())}
                  </th>
                )}
              </For>
            </tr>
          )}
        </For>
      </thead>
      <tbody>
        <For each={table.getRowModel().rows}>
          {(row) => (
            <tr>
              <For each={row.getVisibleCells()}>
                {(cell) => <td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>}
              </For>
            </tr>
          )}
        </For>
      </tbody>
    </table>
  );
}
TanStack Form
import { createForm } from '@tanstack/solid-form';
import { zodValidator } from '@tanstack/zod-form-adapter';
import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

function UserForm() {
  const form = createForm(() => ({
    defaultValues: { name: '', email: '' },
    onSubmit: async ({ value }) => await api.createUser(value),
    validatorAdapter: zodValidator(),
    validators: { onChange: userSchema },
  }));

  return (
    <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
      <form.Field name="name">
        {(field) => (
          <div>
            <input
              value={field().state.value}
              onInput={(e) => field().handleChange(e.currentTarget.value)}
            />
            <Show when={field().state.meta.errors.length}>
              <span class="error">{field().state.meta.errors.join(', ')}</span>
            </Show>
          </div>
        )}
      </form.Field>
      <form.Subscribe selector={(s) => [s.canSubmit, s.isSubmitting]}>
        {([canSubmit, isSubmitting]) => (
          <button disabled={!canSubmit() || isSubmitting()}>
            {isSubmitting() ? 'Saving...' : 'Save'}
          </button>
        )}
      </form.Subscribe>
    </form>
  );
}

SolidStart 功能

基于文件的路由
// routes/users/[id].tsx
import { useParams } from '@solidjs/router';
import { createAsync, cache } from '@solidjs/router';

const getUser = cache(async (id: string) => {
  'use server';
  return db.user.findUnique({ where: { id } });
}, 'user');

export const route = {
  preload: ({ params }) => getUser(params.id),
};

export default function UserPage() {
  const params = useParams<{ id: string }>();
  const user = createAsync(() => getUser(params.id));

  return (
    <Show when={user()} fallback={<Loading />}>
      {(u) => <UserProfile user={u()} />}
    </Show>
  );
}
API 路由
// routes/api/users.ts
import { json } from '@solidjs/router';
import type { APIEvent } from '@solidjs/start/server';

export async function GET(event: APIEvent) {
  const users = await db.user.findMany();
  return json(users);
}

export async function POST(event: APIEvent) {
  const body = await event.request.json();
  const parsed = userSchema.safeParse(body);
  if (!parsed.success) {
    return json({ error: parsed.error.flatten() }, { status: 400 });
  }
  const user = await db.user.create({ data: parsed.data });
  return json(user, { status: 201 });
}
服务端操作
import { action, redirect } from '@solidjs/router';

export const createUserAction = action(async (formData: FormData) => {
  'use server';
  const data = {
    name: formData.get('name') as string,
    email: formData.get('email') as string,
  };
  const parsed = userSchema.safeParse(data);
  if (!parsed.success) return { error: parsed.error.flatten() };
  await db.user.create({ data: parsed.data });
  throw redirect('/users');
});

错误处理

import { ErrorBoundary } from 'solid-js';

function AppErrorBoundary(props: ParentProps) {
  return (
    <ErrorBoundary
      fallback={(err, reset) => (
        <div class="error">
          <h2>Something went wrong</h2>
          <pre>{err.message}</pre>
          <button onClick={reset}>Try again</button>
        </div>
      )}
    >
      {props.children}
    </ErrorBoundary>
  );
}

// Type-Safe Result Pattern
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User, ApiError>> {
  try {
    const user = await api.get(`/users/${id}`);
    return { ok: true, value: user };
  } catch (e) {
    return { ok: false, error: e as ApiError };
  }
}

性能优化

延迟加载
import { lazy, Suspense } from 'solid-js';

const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings = lazy(() => import('./routes/Settings'));
避免重新渲染
// ❌ Creates new object every render
<UserCard user={{ name: name(), email: email() }} />

// ✅ Pass signals directly
<UserCard name={name} email={email} />

// Use <Index> when items don't change, only values
<Index each={items()}>{(item, index) => <Item item={item()} />}</Index>

// Use <For> when items can be reordered
<For each={items()}>{(item) => <Item item={item} />}</For>

常见问题

响应性丢失
// ❌ Destructuring loses reactivity
const { name, email } = props; // BROKEN!

// ✅ Access props directly
<span>{props.name}</span>

// ✅ Or use splitProps
const [local, others] = splitProps(props, ['name', 'email']);
内存泄漏
// ❌ Not cleaning up subscriptions
createEffect(() => {
  const ws = new WebSocket(url);
  ws.onmessage = handleMessage;
});

// ✅ Proper cleanup
createEffect(() => {
  const ws = new WebSocket(url);
  ws.onmessage = handleMessage;
  onCleanup(() => ws.close());
});
SSR 水合
// ❌ Client-only code runs on server
const windowWidth = window.innerWidth; // Error!

// ✅ Use isServer check
import { isServer } from 'solid-js/web';
const [width, setWidth] = createSignal(isServer ? 1024 : window.innerWidth);
onMount(() => setWidth(window.innerWidth));

推荐的库

类别库原因
表单@tanstack/solid-form类型安全的表单
数据@tanstack/solid-query服务端状态缓存
路由器@solidjs/router官方支持,适配 SSR
UIKobalte无障碍基础组件
动画solid-motionone高性能
验证Zod类型安全的模式
图标unplugin-icons支持 Tree-shaking
HTTPky现代化的 fetch

更多参考资料

  • references/patterns.md - 高级设计模式与反模式
  • references/debugging.md - 调试技巧与开发者工具
  • references/performance.md - 包体积优化与性能分析
  • references/security.md - 安全最佳实践与身份验证模式
  • references/testing.md - 全面的测试策略

评论

支持 Markdown