feat(dev-tools): adiciona plugin dev-tools (agents, skills, knowledge e metadados do projeto)
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
---
|
||||
name: react-patterns
|
||||
description: Padroes modernos React (18+/19) -- hooks, context, composicao, memoizacao, lazy loading e Server Components.
|
||||
layer: wiki
|
||||
---
|
||||
|
||||
# React Patterns
|
||||
|
||||
Skill para desenvolvimento React seguindo padroes modernos (React 18+/19).
|
||||
|
||||
## Contexto NotebookLM
|
||||
|
||||
ANTES de executar, consultar notebooks para contexto especializado:
|
||||
|
||||
| Notebook | ID | Consultar quando |
|
||||
|----------|-----|-----------------|
|
||||
| Programacao | 24947ffa-0019-448a-a340-2f4a275d2eb1 | Sempre |
|
||||
|
||||
```
|
||||
mcp__notebooklm__notebook_query({
|
||||
notebook_id: "24947ffa-0019-448a-a340-2f4a275d2eb1",
|
||||
query: "<adaptar ao contexto do pedido do utilizador>"
|
||||
})
|
||||
```
|
||||
|
||||
> **Regra #48:** Projectos React devem ser desenvolvidos no **container dev** (`server:"dev"`, path `/root/Dev/<projecto>`). Sincroniza via Syncthing.
|
||||
|
||||
## Quando Usar
|
||||
|
||||
- Desenvolver componentes React
|
||||
- Implementar state management
|
||||
- Optimizar performance (memoization, lazy loading)
|
||||
- Migrar de class components para hooks
|
||||
- Usar Server Components (Next.js App Router)
|
||||
|
||||
## Hooks Fundamentais
|
||||
|
||||
```jsx
|
||||
// useState - estado simples
|
||||
const [count, setCount] = useState(0)
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
|
||||
// useEffect - side effects
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
return () => cleanup()
|
||||
}, [dependency])
|
||||
|
||||
// useCallback - memoizar funcoes
|
||||
const handleClick = useCallback(() => { doSomething(id) }, [id])
|
||||
|
||||
// useMemo - memoizar valores computados
|
||||
const expensiveValue = useMemo(() => computeExpensive(data), [data])
|
||||
|
||||
// useRef - referencias mutaveis
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
```
|
||||
|
||||
## Component Patterns
|
||||
|
||||
```jsx
|
||||
// SIM: Functional components
|
||||
function UserCard({ user, onSelect }: Props) {
|
||||
return <div onClick={() => onSelect(user.id)}>{user.name}</div>
|
||||
}
|
||||
|
||||
// SIM: Composition over props drilling
|
||||
function Layout({ children }) {
|
||||
return <main className="container">{children}</main>
|
||||
}
|
||||
|
||||
// NAO: Class components (legacy)
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
```jsx
|
||||
// Local state: useState (estado de um componente)
|
||||
|
||||
// Shared state: Context + useReducer
|
||||
const AppContext = createContext<AppState | null>(null)
|
||||
function AppProvider({ children }) {
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
return (
|
||||
<AppContext.Provider value={{ state, dispatch }}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Server state: React Query / SWR
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['users'],
|
||||
queryFn: fetchUsers,
|
||||
})
|
||||
```
|
||||
|
||||
**NAO:** Redux para tudo. Estado global para estado local.
|
||||
|
||||
## Server Components (Next.js 13+)
|
||||
|
||||
```jsx
|
||||
// Server Component (default) - async, data fetching
|
||||
async function UserList() {
|
||||
const users = await db.users.findMany()
|
||||
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
|
||||
}
|
||||
|
||||
// Client Component - interactividade
|
||||
'use client'
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
|
||||
}
|
||||
|
||||
// Padrao: Server parent + Client children
|
||||
// page.tsx (Server) -> InteractiveSection.tsx ('use client')
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
```jsx
|
||||
// Lazy loading componentes
|
||||
const HeavyComponent = lazy(() => import('./HeavyComponent'))
|
||||
<Suspense fallback={<Loading />}>
|
||||
<HeavyComponent />
|
||||
</Suspense>
|
||||
|
||||
// React.memo para componentes puros
|
||||
const MemoizedCard = memo(function Card({ data }) {
|
||||
return <div>{data.title}</div>
|
||||
})
|
||||
|
||||
// Virtualizacao para listas grandes
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
```
|
||||
|
||||
## Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padroes de composicao para componentes React escalaveis. Ver `references/composition-patterns.md` para codigo detalhado.
|
||||
|
||||
**Regras principais:**
|
||||
|
||||
| Regra | Prioridade | Resumo |
|
||||
|-------|-----------|--------|
|
||||
| Evitar Boolean Props | CRITICAL | Variantes explicitas em vez de booleans |
|
||||
| Compound Components | HIGH | Context partilhado, composicao explicita |
|
||||
| State em Providers | HIGH | Boundary do Provider e o que importa |
|
||||
| Interface Generica | HIGH | state/actions/meta para dependency injection |
|
||||
| Children > Render Props | MEDIUM | Composicao natural com children |
|
||||
| React 19 APIs | MEDIUM | `use()` e ref como prop |
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-Pattern | Problema | Solucao |
|
||||
|--------------|----------|---------|
|
||||
| useEffect para tudo | Complexidade | Server Components / React Query |
|
||||
| Props drilling | Manutencao | Context ou Composition |
|
||||
| Inline functions em JSX | Re-renders | useCallback |
|
||||
| State em URL | Perda estado | URL state com hooks |
|
||||
| Class components | Legacy | Functional + Hooks |
|
||||
| Index como key | Bugs listas | ID unico |
|
||||
|
||||
## Checklist Componente
|
||||
|
||||
- [ ] Functional component (nao class)
|
||||
- [ ] TypeScript types/interfaces
|
||||
- [ ] Props destructuring com defaults
|
||||
- [ ] Hooks no topo (nao condicionais)
|
||||
- [ ] Keys unicos em listas
|
||||
- [ ] Error boundaries para erros
|
||||
- [ ] Loading states considerados
|
||||
|
||||
## Estrutura Ficheiros
|
||||
|
||||
```
|
||||
components/
|
||||
ui/ # Componentes base (Button, Input, Card)
|
||||
features/ # Componentes feature-specific
|
||||
layouts/ # Layout components
|
||||
providers/ # Context
|
||||
@@ -0,0 +1,183 @@
|
||||
# React - Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padroes de composicao para componentes React escalaveis. Fonte: Vercel composition-patterns.
|
||||
|
||||
## Regra #1: Evitar Boolean Props (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// NAO: Booleans duplicam estados possiveis
|
||||
<Composer isThread isEditing={false} showAttachments showFormatting={false} />
|
||||
|
||||
// SIM: Variantes explicitas
|
||||
<ThreadComposer channelId="abc" />
|
||||
<EditMessageComposer messageId="xyz" />
|
||||
<ForwardMessageComposer messageId="123" />
|
||||
```
|
||||
|
||||
Cada boolean duplica os estados possiveis. 5 booleans = 32 combinacoes, maioria impossivel.
|
||||
|
||||
---
|
||||
|
||||
## Regra #2: Compound Components com Context (HIGH)
|
||||
|
||||
```tsx
|
||||
// Compound Components: context partilhado, composicao explicita
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
}
|
||||
|
||||
function ComposerInput() {
|
||||
const { state, actions: { update }, meta } = use(ComposerContext)
|
||||
return (
|
||||
<TextInput
|
||||
ref={meta.inputRef}
|
||||
value={state.input}
|
||||
onChangeText={(text) => update(s => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Exportar como compound
|
||||
const Composer = {
|
||||
Frame: ComposerFrame,
|
||||
Input: ComposerInput,
|
||||
Submit: ComposerSubmit,
|
||||
Footer: ComposerFooter,
|
||||
}
|
||||
|
||||
// Uso: composicao clara
|
||||
<Composer.Frame>
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Regra #3: State em Providers (HIGH)
|
||||
|
||||
```tsx
|
||||
// NAO: Estado preso dentro do componente
|
||||
function ForwardComposer() {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Frame>...</Composer.Frame>
|
||||
}
|
||||
|
||||
// SIM: State lifted para Provider
|
||||
function ForwardProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const submit = useForwardMessage()
|
||||
const inputRef = useRef(null)
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update: setState, submit }}
|
||||
meta={{ inputRef }}
|
||||
>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Qualquer componente dentro do Provider acede ao state
|
||||
function ForwardDialog() {
|
||||
return (
|
||||
<ForwardProvider>
|
||||
<Dialog>
|
||||
<ForwardComposer />
|
||||
<MessagePreview />
|
||||
<ForwardButton />
|
||||
</Dialog>
|
||||
</ForwardProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function ForwardButton() {
|
||||
const { actions } = use(ComposerContext)
|
||||
return <Button onPress={actions.submit}>Forward</Button>
|
||||
}
|
||||
```
|
||||
|
||||
**Principio chave:** O boundary do Provider e o que importa, nao o nesting visual.
|
||||
|
||||
---
|
||||
|
||||
## Regra #4: Interface Generica para Dependency Injection (HIGH)
|
||||
|
||||
```tsx
|
||||
// Definir interface generica: state + actions + meta
|
||||
interface ComposerContextValue {
|
||||
state: { input: string; attachments: Attachment[]; isSubmitting: boolean }
|
||||
actions: { update: (fn: (s: State) => State) => void; submit: () => void }
|
||||
meta: { inputRef: React.RefObject<TextInput> }
|
||||
}
|
||||
|
||||
// Multiplos providers implementam a MESMA interface
|
||||
function ChannelProvider({ channelId, children }) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
return <Composer.Provider state={state} actions={{ update, submit }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
function ForwardProvider({ children }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Provider state={state} actions={{ update: setState, submit: forward }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
// O mesmo Composer.Input funciona com AMBOS os providers
|
||||
```
|
||||
|
||||
**Principio:** UI = pecas reutilizaveis. State = injectado pelo provider. Trocar provider, manter UI.
|
||||
|
||||
---
|
||||
|
||||
## Regra #5: Children > Render Props (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// NAO: Render props (inflexivel)
|
||||
<Composer
|
||||
renderHeader={() => <Header />}
|
||||
renderFooter={() => <><Formatting /><Emojis /></>}
|
||||
/>
|
||||
|
||||
// SIM: Children (composicao natural)
|
||||
<Composer.Frame>
|
||||
<Header />
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Formatting />
|
||||
<Emojis />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
|
||||
// Excepcao: render props quando parent passa dados
|
||||
<List data={items} renderItem={({ item }) => <Item item={item} />} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Regra #6: React 19 APIs (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// React 19: ref como prop normal (sem forwardRef)
|
||||
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
|
||||
return <input ref={ref} {...props} />
|
||||
}
|
||||
|
||||
// React 19: use() em vez de useContext()
|
||||
const value = use(MyContext) // pode ser chamado condicionalmente
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist Composicao
|
||||
|
||||
- [ ] Zero boolean props para variantes (usar componentes explicitos)
|
||||
- [ ] Compound components com context partilhado
|
||||
- [ ] State em Provider (nao dentro do componente UI)
|
||||
- [ ] Interface generica (state/actions/meta)
|
||||
- [ ] Children para composicao, render props so para data passthrough
|
||||
- [ ] React 19: `use()` e ref como prop
|
||||
@@ -0,0 +1,66 @@
|
||||
# React - Custom Hooks Uteis
|
||||
|
||||
## useDebounce
|
||||
|
||||
```tsx
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// Uso
|
||||
const searchTerm = useDebounce(input, 500);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## useLocalStorage
|
||||
|
||||
```tsx
|
||||
function useLocalStorage<T>(key: string, initialValue: T) {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
};
|
||||
|
||||
return [storedValue, setValue] as const;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## useFetch
|
||||
|
||||
```tsx
|
||||
function useFetch<T>(url: string) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [url]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user