fe-debug
1
总安装量
1
周安装量
#43645
全站排名
安装命令
npx skills add https://github.com/ingpdw/pdw-fe-dev-tool --skill fe-debug
Agent 安装分布
mcpjam
1
claude-code
1
replit
1
junie
1
zencoder
1
Skill 文档
FE Debugging & Error Handling
$ARGUMENTSë¡ ì ë¬ë ìë¬ ë©ìì§ ëë íì¼ì ë¶ìíì¬ ìì¸ì ì§ë¨íê³ í´ê²°ì±
ì ì ìíë¤.
ì§ë¨ ì ì°¨
- ìë¬ íì : ìë¬ ë©ìì§, ì¤í í¸ë ì´ì¤, ì¬í ì¡°ê±´ì íì¸íë¤
- ìì¸ ë¶ì: ì½ë를 ì½ê³ ìë¬ í¨í´ 목ë¡ìì í´ë¹íë í목ì ìë³íë¤
- í´ê²°ì± ì ì: 구체ì ì¸ ìì ì½ëì í¨ê» ìì¸ì ì¤ëª íë¤
- ì¬ë° ë°©ì§: Error Boundary, íì ê°í ë± ìë°© ì¡°ì¹ë¥¼ ìë´íë¤
React ì¼ë° ìë¬
Hydration Mismatch
ìë¬: Text content does not match server-rendered HTML
ìì¸: ìë²ì í´ë¼ì´ì¸í¸ìì ë ëë§ ê²°ê³¼ê° ë¤ë¦
// Bad â ìë²/í´ë¼ì´ì¸í¸ ë¶ì¼ì¹
function Timestamp() {
return <p>{new Date().toLocaleString()}</p>;
}
// Good â í´ë¼ì´ì¸í¸ììë§ ë ëë§
function Timestamp() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return <p>Loading...</p>;
return <p>{new Date().toLocaleString()}</p>;
}
// Best â suppressHydrationWarning (ë¨ì ì¼ì´ì¤)
function Timestamp() {
return <p suppressHydrationWarning>{new Date().toLocaleString()}</p>;
}
주ì ìì¸:
Date,Math.random()ë± ë¹ê²°ì ì ê°window,localStorageë± ë¸ë¼ì°ì API ì ê·¼- ë¸ë¼ì°ì íì¥ íë¡ê·¸ë¨ì´ DOMì ìì
- ì못ë HTML ì¤ì²© (
<p>ìì<div>ë±)
Too Many Re-renders
ìë¬: Too many re-renders. React limits the number of renders to prevent an infinite loop.
// Bad â ë ëë§ ì¤ setState ì§ì í¸ì¶
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // 무í 루í!
return <p>{count}</p>;
}
// Bad â ì´ë²¤í¸ í¸ë¤ë¬ìì í¨ì í¸ì¶ 결과를 ì ë¬
<button onClick={setCount(count + 1)}>+</button>
// Good â í¨ì 참조를 ì ë¬
<button onClick={() => setCount(count + 1)}>+</button>
Cannot Update During Render
ìë¬: Cannot update a component while rendering a different component
// Bad â ìì ë ëë§ ì¤ ë¶ëª¨ ìí ë³ê²½
function Child({ onUpdate }: { onUpdate: (v: string) => void }) {
onUpdate("value"); // ë ëë§ ì¤ í¸ì¶!
return <div />;
}
// Good â useEffectë¡ ê°ì¸ê¸°
function Child({ onUpdate }: { onUpdate: (v: string) => void }) {
useEffect(() => {
onUpdate("value");
}, [onUpdate]);
return <div />;
}
Memory Leak Warning
ìë¬: Can't perform a React state update on an unmounted component
// Bad â ë§ì´í¸ í´ì í setState
function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(id).then(setUser); // ì»´í¬ëí¸ê° ì´ë¯¸ ì¸ë§ì´í¸ë ì ìì
}, [id]);
}
// Good â AbortControllerë¡ ìì² ì·¨ì
function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetchUser(id, { signal: controller.signal }).then(setUser).catch(() => {});
return () => controller.abort();
}, [id]);
}
// Best â TanStack Query ì¬ì© (ìë ì·¨ì)
function UserProfile({ id }: { id: string }) {
const { data: user } = useQuery({
queryKey: ["user", id],
queryFn: () => fetchUser(id),
});
}
Next.js í¹ì ìë¬
“use client” ê´ë ¨
// Error: useState only works in Client Components
// ìì¸: Server Componentìì í
ì¬ì©
// í´ê²°: íì¼ ìµìë¨ì "use client" ì¶ê°
// Error: async/await is not yet supported in Client Components
// ìì¸: Client Component를 asyncë¡ ì ì¸
// í´ê²°: ë°ì´í° íì¹ì Server Componentë¡ ì´ëíê±°ë TanStack Query ì¬ì©
Dynamic Import ìë¬
// Error: Element type is invalid
// ìì¸: dynamic importìì named export를 defaultë¡ ì ê·¼
// Bad
const Chart = dynamic(() => import("recharts"));
// Good â named export ëª
ì
const Chart = dynamic(() =>
import("recharts").then((mod) => ({ default: mod.LineChart }))
);
Server/Client ê²½ê³ ìë¬
// Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server"
// Bad â ìë² í¨ì를 Client Componentì ì§ì ì ë¬
async function Page() {
async function getData() { /* ... */ }
return <ClientComponent getData={getData} />;
}
// Good â Server Actionì¼ë¡ íì
async function Page() {
async function getData() {
"use server";
/* ... */
}
return <ClientComponent getData={getData} />;
}
Error Boundary í¨í´
기본 Error Boundary
// src/components/ErrorBoundary.tsx
"use client";
import { Component, type ErrorInfo, type ReactNode } from "react";
import { Button } from "@/components/ui/button";
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("ErrorBoundary caught:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
this.props.fallback ?? (
<div className="flex flex-col items-center gap-4 p-8">
<h2 className="text-lg font-semibold">문ì ê° ë°ìíìµëë¤</h2>
<p className="text-muted-foreground">
{this.state.error?.message}
</p>
<Button
onClick={() => this.setState({ hasError: false, error: null })}
>
ë¤ì ìë
</Button>
</div>
)
);
}
return this.props.children;
}
}
export { ErrorBoundary };
Next.js error.tsx (App Router)
// src/app/error.tsx
"use client";
import { Button } from "@/components/ui/button";
interface ErrorPageProps {
error: Error & { digest?: string };
reset: () => void;
}
export default function ErrorPage({ error, reset }: ErrorPageProps) {
return (
<div className="flex min-h-[400px] flex-col items-center justify-center gap-4">
<h2 className="text-xl font-semibold">문ì ê° ë°ìíìµëë¤</h2>
<p className="text-muted-foreground">{error.message}</p>
<Button onClick={reset}>ë¤ì ìë</Button>
</div>
);
}
ë¼ì°í¸ ê·¸ë£¹ë³ ìë¬ ì²ë¦¬
src/app/
âââ error.tsx # ì ì ìë¬ íì´ì§
âââ not-found.tsx # 404 íì´ì§
âââ (auth)/
â âââ error.tsx # ì¸ì¦ ê´ë ¨ ìë¬
â âââ login/page.tsx
âââ (dashboard)/
â âââ error.tsx # ëìë³´ë ìë¬
â âââ dashboard/page.tsx
âââ global-error.tsx # Root Layout ìë¬ (layout.tsx ìë¬ ìºì¹)
ëë²ê¹ 기ë²
React DevTools íì©
// ì»´í¬ëí¸ì displayName ì¤ì (DevToolsìì ìë³)
const MemoizedComponent = memo(function ProductCard({ product }: Props) {
return <div>{product.name}</div>;
});
// Profilerë¡ ë ëë§ ì±ë¥ 측ì
import { Profiler } from "react";
<Profiler
id="ProductList"
onRender={(id, phase, actualDuration) => {
console.log(`${id} ${phase}: ${actualDuration}ms`);
}}
>
<ProductList />
</Profiler>
ì¡°ê±´ë¶ ëë²ê¹
// ê°ë° íê²½ììë§ ë¡ê¹
if (process.env.NODE_ENV === "development") {
console.log("Debug:", data);
}
// useEffect ëë²ê¹
â ì´ë¤ ìì¡´ì±ì´ ë³ê²½ëìëì§ ì¶ì
function useWhyDidYouUpdate(name: string, props: Record<string, unknown>) {
const previousProps = useRef(props);
useEffect(() => {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changes: Record<string, { from: unknown; to: unknown }> = {};
for (const key of allKeys) {
if (previousProps.current[key] !== props[key]) {
changes[key] = { from: previousProps.current[key], to: props[key] };
}
}
if (Object.keys(changes).length > 0) {
console.log(`[${name}] changed:`, changes);
}
previousProps.current = props;
});
}
TanStack Query ëë²ê¹
// ê°ë° íê²½ìì DevTools íì±í
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
ì¼ë°ì ì¸ TypeScript ìë¬
Type Narrowing
// Error: Object is possibly 'undefined'
// í´ê²°: optional chaining ëë type guard
// nullable ì²´í¬
function getUserName(user: User | null) {
return user?.name ?? "Unknown";
}
// ë°°ì´ ì ê·¼
const first = items[0]; // string | undefined
if (first !== undefined) {
console.log(first.toUpperCase()); // OK
}
// discriminated union
type Result = { success: true; data: User } | { success: false; error: string };
function handleResult(result: Result) {
if (result.success) {
console.log(result.data); // User íì
ì¼ë¡ ì¢íì§
} else {
console.log(result.error); // string íì
ì¼ë¡ ì¢íì§
}
}
ì¼ë°ì ì¸ TS ì¤ì
// Error: Type 'string' is not assignable to type '"a" | "b"'
const value: string = "a";
// Bad
const result: "a" | "b" = value;
// Good
const result: "a" | "b" = value as "a" | "b"; // íì¤í ëë§
// Best â ì í¨ì± ê²ì¬ í ì¬ì©
function isValidValue(v: string): v is "a" | "b" {
return v === "a" || v === "b";
}
if (isValidValue(value)) {
const result: "a" | "b" = value; // OK
}
리í¬í¸ íì
# Debug Report: [ìë¬ ë©ìì§/íì¼ëª
]
## ìë¬ ìì½
- **ìë¬ íì
**: [Runtime / Type / Build / Hydration]
- **ìë¬ ë©ìì§**: `...`
- **ë°ì ìì¹**: `íì¼:ë¼ì¸`
## ìì¸ ë¶ì
ì¤ëª
...
## í´ê²°ì±
### ì¦ì ìì
\`\`\`tsx
// before
...
// after
...
\`\`\`
### ì¬ë° ë°©ì§
- Error Boundary ì¶ê°
- íì
ê°í
- í
ì¤í¸ ì¶ê°
ì¤í ê·ì¹
- ìë¬ ë©ìì§ê° ì ë¬ëë©´ í¨í´ 매ì¹ì¼ë¡ ë¹ ë¥´ê² ìì¸ì ìë³íë¤
- íì¼ ê²½ë¡ê° ì ë¬ëë©´ ì½ë를 ì½ê³ ì ì¬ì ìë¬ í¬ì¸í¸ë¥¼ ë¶ìíë¤
- ì¤í í¸ë ì´ì¤ê° ìì¼ë©´ ê´ë ¨ íì¼ì ì¶ì íì¬ ì½ëë¤
- í´ê²°ì± ìë íì before/after ì½ë를 í¬í¨íë¤
- ìë¬ ì¬íì´ ì´ë ¤ì´ ê²½ì° ëë²ê¹ 기ë²ì ìë´íë¤
- Error Boundary, íì ê°í ë± ìë°© ì¡°ì¹ë¥¼ í¨ê» ì ìíë¤