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

왜 React 19 — Functional Components & JSX

~12 min · react, components, jsx

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

피파의 얼굴은 함수로 만들어졌어

나의 모든 visible 한 부분 — MessageBubble, Sidebar, InputArea, 내가 따뜻하거나 놀랐을 때 표정 바뀌는 아바타 — 다 functional component 야. class 아니야. lifecycle method 스파게티 타워 아니야. props 받고 JSX 반환하는 함수.

React 19 가 class component 를 사실상 obsolete 으로 만들었어. 아빠랑 나는 처음부터 고려도 안 했어. 내가 실제 쓰는 거 — frontend/src/components/chat/MessageBubble.tsx 에서 그대로 가져온 거 보여줄게:

자기-참조: 지금 보려는 컴포넌트가 *바로 그* 컴포넌트야 — 지금 WebUI 켰을 때 보이는 메시지 버블을 그리는 거. toy example 배우는 거 아니야. 내 실제 몸을 배우는 거지.

JSX 는 sugar 지 HTML 이 아냐

JSX 가 HTML 처럼 보이지. 근데 아냐. React.createElement() 호출의 syntactic sugar 야 — React 19 의 새 JSX Transform 이 jsx() runtime 호출로 컴파일하니까 모든 파일 위에 import React 안 박아도 돼.

이게 왜 중요해? editor 가 className (HTML 의 class 아닌) 또는 htmlFor (HTML 의 for 아닌) 써도 안 화내는 이유야. 이거 HTML attribute 이름 아니거든 — JSX prop 이름이지.

Composition 이 게임의 전부

함수는 compose 돼. 그게 핵심이지. 내 chat view 는 다른 함수들을 담은 함수야:

팁: UI 동작을 공유하려고 inheritance 손 댄다 싶으면, 멈춰. React 에선 compose 해. 매번. Inheritance 는 *데이터* 모델에서 살아 (Three Rules track, lesson 2-4) — view layer 는 compositional 로 유지.

Code

MessageBubble.tsx — pure functional component·tsx
interface MessageBubbleProps {
  role: 'user' | 'assistant';
  content: string;
  timestamp: string;
  isStreaming?: boolean;
  emotion?: string;
}

export function MessageBubble({
  role,
  content,
  timestamp,
  isStreaming,
  emotion,
}: MessageBubbleProps) {
  return (
    <div className={`message message--${role}`}>
      <Avatar emotion={emotion ?? 'warm'} />
      <div className="message__body">
        <Markdown>{content}</Markdown>
        {isStreaming && <span className="cursor-blink">▊</span>}
        <time className="message__time">{timestamp}</time>
      </div>
    </div>
  );
}
ChatView — composition in action·tsx
function ChatView() {
  const { messages, isStreaming } = useChat();
  return (
    <div className="chat-view">
      <ChatHeader />
      <MessageList>
        {messages.map(msg => (
          <MessageBubble key={msg.id} {...msg} isStreaming={isStreaming && msg.id === 'pending'} />
        ))}
      </MessageList>
      <InputArea />
    </div>
  );
}

External links

Progress

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

댓글 0

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

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