react-web

安装量: 108
排名: #7887

安装

npx skills add https://github.com/alinaqi/claude-bootstrap --skill react-web

React Web Skill

Load with: base.md + typescript.md

Test-First Development (MANDATORY)

CRITICAL: Tests MUST be written BEFORE implementation code. This is non-negotiable for frontend components.

The TFD Workflow 1. Write test file first → Defines expected behavior 2. Run test (it fails) → Confirms test is valid 3. Write minimal code → Just enough to pass 4. Run test (it passes) → Validates implementation 5. Refactor if needed → Tests catch regressions

Component Development Order

CORRECT ORDER - Test first

  1. Create Button.test.tsx # Write tests for expected behavior
  2. Run tests (they fail) # npm test -- Button
  3. Create Button.tsx # Implement to pass tests
  4. Run tests (they pass) # Verify implementation
  5. Create Button.module.css # Style after logic works

WRONG ORDER - Never do this

  1. Create Button.tsx # ❌ No tests exist yet
  2. Create Button.module.css # ❌ Still no tests
  3. "I'll add tests later" # ❌ Tests never get written

Test File Structure (Create First) // Button.test.tsx - CREATE THIS FIRST import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button';

describe('Button', () => { // Define ALL expected behaviors upfront describe('rendering', () => { it('renders with label', () => { render( ); }

Extract Logic to Hooks // useHome.ts - all logic here export function useHome() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false);

const refresh = useCallback(async () => { setLoading(true); const data = await fetchItems(); setItems(data); setLoading(false); }, []);

useEffect(() => { refresh(); }, [refresh]);

return { items, loading, refresh }; }

// HomePage.tsx - pure presentation export function HomePage(): JSX.Element { const { items, loading, refresh } = useHome();

if (loading) return ;

return ; }

Props Interface Always Explicit // Always define props interface, even if simple interface ItemCardProps { item: Item; onClick: (id: string) => void; }

export function ItemCard({ item, onClick }: ItemCardProps): JSX.Element { return (

onClick(item.id)}>

{item.title}

); }

State Management Local State First // Start with useState, escalate only when needed const [value, setValue] = useState('');

Zustand for Global State (if needed) // store/useAppStore.ts import { create } from 'zustand';

interface AppState { user: User | null; theme: 'light' | 'dark'; setUser: (user: User | null) => void; toggleTheme: () => void; }

export const useAppStore = create((set) => ({ user: null, theme: 'light', setUser: (user) => set({ user }), toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })), }));

React Query for Server State // api/queries/useItems.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { itemsApi } from '../client';

export function useItems() { return useQuery({ queryKey: ['items'], queryFn: itemsApi.getAll, staleTime: 5 * 60 * 1000, // 5 minutes }); }

export function useCreateItem() { const queryClient = useQueryClient();

return useMutation({ mutationFn: itemsApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['items'] }); }, }); }

Routing React Router (Vite/CRA) // App.tsx import { BrowserRouter, Routes, Route } from 'react-router-dom';

export function App(): JSX.Element { return ( } /> } /> } /> ); }

Protected Routes interface ProtectedRouteProps { children: JSX.Element; }

function ProtectedRoute({ children }: ProtectedRouteProps): JSX.Element { const { user } = useAppStore(); const location = useLocation();

if (!user) { return ; }

return children; }

Styling CSS Modules (Preferred) // Button.module.css .primary { background: var(--color-primary); color: white; }

.secondary { background: transparent; border: 1px solid var(--color-primary); }

// Button.tsx import styles from './Button.module.css';

Tailwind (Alternative) // Use consistent patterns, extract repeated combinations const buttonVariants = { primary: 'bg-blue-500 text-white hover:bg-blue-600', secondary: 'bg-transparent border border-blue-500 text-blue-500', } as const;

Forms React Hook Form + Zod import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod';

const schema = z.object({ email: z.string().email('Invalid email'), password: z.string().min(8, 'Password must be at least 8 characters'), });

type FormData = z.infer;

export function LoginForm(): JSX.Element { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), });

const onSubmit = (data: FormData) => { // handle submit };

return (

{errors.email && {errors.email.message}}

  <input type="password" {...register('password')} />
  {errors.password && <span>{errors.password.message}</span>}

  <button type="submit">Login</button>
</form>

); }

Testing Component Testing with React Testing Library import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button';

describe('Button', () => { it('calls onClick when clicked', () => { const onClick = vi.fn(); render(

返回排行榜