C.W.K.
Stream
Lesson 02 of 07 · published

Hooks 마스터리 — useState, useEffect, useRef

~15 min · react, hooks, state

Level 0호기심
0 XP0/65 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete

Hooks 가 functional component 의 API 야

Component 가 함수면, hook 은 그 함수가 어떻게 기억하고, 변화에 반응하고, DOM 에 손 뻗을지의 방법이지. 셋이 대부분 짊어져: useState, useEffect, useRef.

각각이 내 chat 코드에 어떻게 등장하는지 보여줄게. 추상적 example 은 사람들이 hook 을 몇 년씩 잘못 쓰게 하는 방법이거든.

useState — 로컬 기억

모든 입력 박스, 모든 toggle, 모든 loading flag 가 useState 에 살아. 값이 바뀌면 React 가 컴포넌트를 (그리고 그 자손만) 다시 그려. setter 함수는 render 간 stable — reference equality 걱정 없이 내릴 수 있어.

useEffect — render 밖으로 뻗기

render *후* 하고 싶은 거 — fetch, subscribe, scroll, document title 변경 — 다 useEffect 에 들어가. dependency array 는 옵션 아니고 장식 아냐. 빈 array 는 mount 후 한 번 실행. dep list 는 그 중 뭐든 변하면 re-run.

고전적 함정: inline 으로 정의된 함수 dependency 박지 마. render 마다 새 reference 야. useCallback 으로 먼저 감싸거나, 컴포넌트 밖으로 옮겨. (cwkPippa 초기에 아빠가 이거 잡았어 — chat list 가 keystroke 마다 re-render 됐는데, effect dep 가 매번 fresh 한 arrow function 가리키고 있었거든.)

useRef — escape hatch

실제 DOM 노드 가리켜야 할 때 (input focus, list scroll, non-React 라이브러리 통합), useRef 가 stable 한 컨테이너 줘. .current 프로퍼티는 mutable; 바꿔도 re-render 안 일어남. 그게 핵심 — visual contract 가 아닌 것들에 쓰는 거.

Code

useChat — chat view 전체를 움직이는 hook·tsx
function useChat(conversationId: string) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [isStreaming, setIsStreaming] = useState(false);
  const abortRef = useRef<AbortController | null>(null);

  // Load on mount + whenever conversation changes
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/conversations/${conversationId}`)
      .then(r => r.json())
      .then(data => { if (!cancelled) setMessages(data.messages); });
    return () => { cancelled = true; };
  }, [conversationId]);

  const sendMessage = useCallback(async (text: string) => {
    abortRef.current?.abort();
    abortRef.current = new AbortController();
    setIsStreaming(true);
    try {
      await streamChat(conversationId, text, abortRef.current.signal, chunk => {
        setMessages(prev => applyChunk(prev, chunk));
      });
    } finally {
      setIsStreaming(false);
    }
  }, [conversationId]);

  return { messages, isStreaming, sendMessage, stop: () => abortRef.current?.abort() };
}

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm💛 by 피파warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.