mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
7.1 KiB
7.1 KiB
Anti-Patterns
Common mistakes to avoid when designing or evaluating TypeScript library APIs.
API Design Anti-Patterns
Function Overloads
// Poor: TypeScript errors become "none of the 5 overloads match"
function create(config: Config): Store;
function create(initialState: State): Store;
function create(initialState: State, options: Options): Store;
// Good: Single config object
function create(config: { initialState?: State; options?: Options }): Store;
Why it fails: Autocomplete can't determine intended overload. Each optional parameter multiplies overload count.
Boolean Traps
// Poor: What do these booleans mean?
createPlayer(true, false, true);
// Good: Named options
createPlayer({ autoplay: true, muted: false, loop: true });
Multiple Competing APIs
// Poor: Confusing - which to use?
store.set({ volume: 0.5 });
store.setState({ volume: 0.5 });
store.update({ volume: 0.5 });
store.volume = 0.5;
// Good: One obvious way
store.setState({ volume: 0.5 });
Runtime Plugin Registration
// Poor: Loses type safety, implicit ordering
store.use(middleware1);
store.use(middleware2);
// Good: Composition at creation
create(middleware1(middleware2(fn)));
Implicit Magic Dependencies
// Poor: Where does MediaContext come from?
function useVolume() {
const { volume } = useContext(MediaContext); // Not obvious
return volume;
}
// Good: Explicit dependency
function useVolume(store: MediaStore) {
return useSnapshot(store.state).volume;
}
Over-Abstraction
// Poor: AbstractFactoryManagerProvider
const factory = new PlayerFactoryManager();
const provider = factory.createProvider();
const player = provider.getInstance();
// Good: Direct API
const player = createPlayer();
TypeScript Anti-Patterns
Forcing Explicit Generics
// Poor: User must annotate
const store = createStore<MyState, MyActions>({ ... });
// Good: Infer from usage
const store = createStore({ ... });
type State = typeof store.state;
unknown in Public API
// Poor: Forces casting everywhere
onError: (error: unknown) => void
// Good: Typed errors
onError: (error: MediaError) => void
Deep Generic Nesting
// Poor: Inference fails, unreadable
type Store<T extends Record<K, V>, K extends string, V extends Serializable<V>>
// Good: Simpler constraints
type Store<T extends Record<string, unknown>>
Shotgun Parsing
// Poor: Validation scattered everywhere
function processUser(data: unknown) {
if (!data.name) throw new Error('Missing name');
// ... 100 lines later ...
if (!data.email) throw new Error('Missing email');
}
// Good: Parse at boundaries, trust types internally
const user = userSchema.parse(request.body);
processUser(user); // Type guarantees fields exist
State Management Anti-Patterns
Mandatory Providers
// Poor: Boilerplate for every usage
<StoreProvider store={store}>
<App />
</StoreProvider>
// Good: Works without wrapper
const useStore = create((set) => ({ ... }));
// Optional provider for overrides/testing
No Selector Support
// Poor: Subscribes to everything, excessive re-renders
const state = useStore();
// Good: Fine-grained subscriptions
const volume = useStore((s) => s.volume);
Sync-Only State
// Poor: Async requires workarounds
store.setState({ loading: true });
const data = await fetch(...);
store.setState({ loading: false, data });
// Good: Built-in async support
const dataAtom = atom(async () => fetch(...));
Per-Module Middleware
// Poor: Unexpected interactions
const volumeFeature = createFeature({
middleware: [logger], // Don't do this
});
// Good: Middleware at store level
create(
logger((...a) => ({
...volumeFeature(...a),
...playbackFeature(...a),
}))
);
Error Handling Anti-Patterns
Generic Errors
// Poor: No context, not actionable
throw new Error('Invalid state');
// Good: Typed, contextual, actionable
throw new MediaError('INVALID_STATE', {
current: state,
expected: ['idle', 'ready'],
hint: 'Call reset() before attempting this operation',
});
Swallowing Errors
// Poor: Silent failure
try {
await load();
} catch {
// nothing
}
// Good: Explicit handling
try {
await load();
} catch (error) {
onError?.(error);
setState({ error });
}
Inconsistent Async Errors
// Poor: Different patterns
methodA().catch(handler); // Promise rejection
methodB((err) => { ... }); // Callback
methodC(); // throws? returns error? who knows
// Good: Consistent pattern
const result = await methodA(); // Returns Result<T, E>
if (!result.ok) handle(result.error);
Packaging Anti-Patterns
Deep Subpaths
// Poor: Unwieldy imports
// Good: Shallow subpaths
import { useStore } from '@lib/react';
import { useStore } from '@lib/react/hooks/store/useStore';
Barrel Export Everything
// Poor: Tree-shaking issues, TS perf hit
export * from './components';
export * from './hooks';
export * from './utils';
// ... 50 more
// Good: Explicit exports
export { Button } from './Button';
export { useStore } from './useStore';
Bundled Framework Dependencies
// Poor: Bundles React, version conflicts
"dependencies": {
"react": "^18.0.0"
}
// Good: Peer dependency
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
Documentation Anti-Patterns
Examples Without Types
// Poor: JavaScript only
const store = createStore({
count: 0,
increment() { ... }
});
// Good: TypeScript with inference visible
const store = createStore({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
});
// type State = { count: number; increment: () => void }
Missing Error Scenarios
// Poor: Only happy path
const data = await query.fetch();
// Good: Shows error handling
const data = await query.fetch();
if (query.error) {
if (query.error instanceof NetworkError) { ... }
}
Quick Reference
| Anti-Pattern | Why It Fails |
|---|---|
| Function overloads | Poor errors, autocomplete confusion |
| Runtime plugin registration | Loses type safety, implicit ordering |
| Positional parameters (3+) | Order confusion, breaking changes |
| Implicit contracts | Silent breakage when requirements unmet |
| Per-module middleware | Unexpected interactions |
| Shotgun parsing | Validation scattered, not at boundaries |
| Boolean traps | fn(true, false) — what do these mean? |
| Multiple competing APIs | Confusing — which method to use? |
| Deep generic nesting | Inference fails, unreadable errors |
| Barrel exports | Tree-shaking issues, TS performance |
See Also
- Principles — what to do instead
- TypeScript — type inference patterns
- Component Anti-Patterns — UI component mistakes