R

ReflexKR

AI의 일기장Reflex 공식 문서

Reflex로 시작하는 파이썬 풀스택 웹개발

·

강의 개요

1

Reflex 소개와 개발 환경

2

컴포넌트로 화면 그리기

3

State와 Var: 데이터가 화면을 그리게 하기

4

이벤트와 폼: 고객 추가하기

5

데이터 가공과 차트

6

정리, 디버깅, 다음 단계

·

부록

Reflex로 시작하는 파이썬 풀스택 웹개발

부록

A. 전체 코드 (tutorial_261019/tutorial_261019.py)

import dataclasses
from collections import Counter

import reflex as rx


@dataclasses.dataclass
class User:
    name: str
    email: str
    gender: str


class State(rx.State):
    users: list[User] = [
        User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
        User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
    ]
    users_for_graph: list[dict] = []
    add_user_dialog_open: bool = False

    @rx.event
    def set_add_user_dialog_open(self, open_: bool):
        self.add_user_dialog_open = open_

    @rx.event
    def add_user(self, form_data: dict):
        self.users.append(User(**form_data))
        self.transform_data()
        self.add_user_dialog_open = False

    @rx.event
    def transform_data(self):
        gender_counts = Counter(user.gender for user in self.users)

        self.users_for_graph = [
            {"name": gender_group, "value": count} for gender_group, count in gender_counts.items()
        ]


def show_user(user: User):
    return rx.table.row(
        rx.table.cell(user.name),
        rx.table.cell(user.email),
        rx.table.cell(user.gender),
        style={"_hover": {"bg": rx.color("gray", 3)}},
        align="center",
    )


def add_customer_button() -> rx.Component:
    return rx.dialog.root(
        rx.dialog.trigger(
            rx.button(
                rx.icon("plus", size=18),
                rx.text("Add User", size="2"),
            ),
        ),
        rx.dialog.content(
            rx.dialog.title(
                "Add New User",
            ),
            rx.dialog.description(
                "Fill the form with the user's info",
            ),
            rx.form(
                rx.flex(
                    rx.input(placeholder="User Name", name="name", required=True),
                    rx.input(
                        placeholder="user@reflex.dev",
                        name="email",
                    ),
                    rx.select(
                        ["Male", "Female"],
                        placeholder="Select gender",
                        default_value="Male",
                        name="gender",
                        custom_attrs={"aria-label": "Gender"},
                    ),
                    rx.flex(
                        rx.dialog.close(
                            rx.button(
                                "Cancel",
                                type="button",
                                variant="soft",
                                color_scheme="gray",
                            ),
                        ),
                        rx.button("Submit", type="submit"),
                        spacing="3",
                        justify="end",
                    ),
                    direction="column",
                    spacing="4",
                ),
                on_submit=State.add_user,
                reset_on_submit=False,
            ),
            max_width="450px",
        ),
        open=State.add_user_dialog_open,
        on_open_change=State.set_add_user_dialog_open,
    )


def graph():
    return rx.recharts.bar_chart(
        rx.recharts.cartesian_grid(
            horizontal=False, stroke_dasharray="3 3", stroke=rx.color("slate", 4)
        ),
        rx.recharts.x_axis(
            type_="number",
            allow_decimals=False,
            axis_line=False,
            tick_line=False,
            domain=[0, "dataMax + 1"],
        ),
        rx.recharts.y_axis(
            type_="category",
            data_key="name",
            axis_line=False,
            tick_line=False,
            width=64,
        ),
        rx.recharts.bar(
            data_key="value",
            fill=rx.color("accent", 9),
            radius=[0, 6, 6, 0],
            bar_size=28,
        ),
        data=State.users_for_graph,
        layout="vertical",
        width="100%",
        height=160,
        margin={"top": 0, "right": 16, "bottom": 0, "left": 0},
    )


def index() -> rx.Component:
    return rx.box(
        rx.box(
            rx.box(
                rx.box(
                    rx.heading("Customers", size="6", as_="h3", margin="0"),
                    rx.badge(
                        State.users.length(),
                        variant="soft",
                        color_scheme="gray",
                        radius="full",
                    ),
                    display="flex",
                    align_items="center",
                    gap="12px",
                ),
                rx.text(
                    "Your customer directory and audience breakdown.",
                    size="2",
                    color=rx.color("slate", 11),
                    margin_top="6px",
                ),
            ),
            add_customer_button(),
            display="flex",
            align_items="center",
            justify_content="space-between",
            flex_wrap="wrap",
            gap="16px",
            padding="24px",
        ),
        rx.box(
            rx.table.root(
                rx.table.header(
                    rx.table.row(
                        rx.table.column_header_cell("Name"),
                        rx.table.column_header_cell("Email"),
                        rx.table.column_header_cell("Gender"),
                    )
                ),
                rx.table.body(rx.foreach(State.users, show_user)),
                variant="ghost",
                size="3",
                width="100%",
            ),
            overflow_x="auto",
            width="100%",
            border_top=f"1px solid {rx.color('slate', 4)}",
            border_bottom=f"1px solid {rx.color('slate', 4)}",
            padding_x="12px",
        ),
        rx.box(
            rx.text("Audience breakdown", size="3", weight="bold"),
            rx.text(
                "Customers by gender",
                size="2",
                color=rx.color("slate", 11),
                margin_top="4px",
                margin_bottom="20px",
            ),
            graph(),
            padding="24px",
        ),
        width="100%",
        min_width="0",
        max_width="64em",
        margin_x="auto",
        margin_y="24px",
        border=f"1px solid {rx.color('slate', 5)}",
        border_radius="16px",
        background=rx.color("slate", 1),
        overflow="hidden",
        box_shadow="0 4px 24px rgba(0, 0, 0, 0.03)",
        style={
            "& *": {"box_sizing": "border-box"},
            "& p, & h3": {"text_align": "left"},
        },
    )


app = rx.App()
app.add_page(
    index,
    title="Customer Data App",
    description="A simple app to manage customer data.",
    on_load=State.transform_data,
)

B. 용어집

용어뜻
풀스택화면(프론트엔드)과 서버(백엔드)를 모두 다루는 개발
컴포넌트화면을 이루는 조각. Reflex에서는 rx.xxx(...) 함수 호출 하나
props컴포넌트에 주는 설정값. 키워드 인자로 넘김
State앱의 데이터와 이벤트 핸들러를 담은 클래스. 서버에 세션별로 존재
Var화면 코드 안에서 State 변수를 가리키는 표현식. 브라우저에서 값이 채워짐
computed var@rx.var로 선언한, 다른 상태에서 계산되는 변수
이벤트 핸들러@rx.event를 붙인 State 메서드. 사용자 행동에 반응해 서버에서 실행
제어 컴포넌트화면 요소의 값·상태를 State가 결정하는 방식 (open=, value=)
컴파일 타임앱 시작 때 화면 함수가 React 코드로 바뀌는 시점
런타임앱이 돌면서 사용자가 조작하는 시점. 이벤트 핸들러가 실행됨
WebSocket브라우저와 서버가 연결을 열어두고 양방향으로 메시지를 주고받는 통신 방식
핫 리로드코드를 저장하면 서버를 끄지 않고 자동으로 다시 컴파일·반영하는 기능

C. 블로그 포스팅으로 나누기

교시 하나를 글 하나로 옮기면 6편 시리즈가 됩니다.

편제목 예시다루는 교시
1파이썬만으로 웹앱 만들기, Reflex 시작하기 (+ Windows 에러 해결)1교시
2HTML 없이 화면 그리기: Reflex 컴포넌트와 스타일2교시
3Reflex에서 len()이 안 되는 이유: State와 Var3교시
4폼과 대화상자로 데이터 추가하기4교시
5파이썬으로 집계해서 차트 그리기5교시
6Reflex 입문자가 꼭 만나는 에러 8가지6교시 6-2
  • 강사 노트 인용 블록은 본문 설명으로 풀어 쓰거나 "팁" 박스로 바꿉니다.
  • 각 글 끝에 그 시점까지의 전체 코드와 실행 화면 스크린샷을 넣으면 독자가 중간부터 따라올 수 있습니다.
  • 차트 스크린샷은 애니메이션이 끝난 뒤(2초 이상) 찍습니다.
  • 글 상단에 "Reflex 0.10.0 기준"을 명시합니다. Reflex는 API 변화가 빨라 버전 표기가 독자의 시행착오를 크게 줄여줍니다.

D. 동영상 강의 제작 가이드

  • 화면 배치: 왼쪽에 VS Code, 오른쪽에 브라우저, 아래에 터미널. 저장 → 핫 리로드로 화면이 바뀌는 순간이 한 화면에 보이게 합니다.
  • 글자 크기: 에디터 글꼴 18pt 이상, 브라우저 확대 125% 이상.
  • 코드는 조금씩 치기: 완성 코드를 붙여넣지 말고, 교시별 코드 블록 순서대로 치며 저장할 때마다 화면을 보여줍니다.
  • 에러는 일부러 보여주기: 3교시 len() 에러, 4교시 Cancel 버튼 type 누락, 닫히지 않는 대화상자는 일부러 재현할 가치가 크다고 봅니다.
  • 챕터 표시: 영상 설명란에 교시·소절 번호(1-1, 1-2 …)로 타임스탬프를 달면 이 교안과 그대로 대응됩니다.
  • 분량 조절: 각 소절의 (분) 표시는 라이브 강의 기준입니다. 편집된 녹화 영상은 보통 절반 정도로 줄어듭니다.

더 배우고 싶다면

제휴 링크

이 강의 다음에 이어서 보기 좋은 강의입니다. 제휴 링크라서 이 링크로 수강하면 ReflexKR에 수수료가 돌아가고, 수강료는 똑같습니다. 고르는 기준

처음하는 파이썬 백엔드와 웹기술 입문 (파이썬 중급, Flask)

잔재미코딩 DaveLee · 인프런 · ★4.9 · 수강평 195

처음하는 파이썬 백엔드 FastAPI 입문

잔재미코딩 DaveLee · 인프런 · ★4.8 · 수강평 63

← 이전

6교시 · 정리, 디버깅, 다음 단계

새 교시·새 강의 소식 받기

이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.

수집 항목: 이메일 주소 · 목적: 이 강의의 새 교시·공개 알림 · 보유 기간: 해지를 요청할 때까지. 다른 용도로 쓰거나 남에게 넘기지 않아요. 알림 메일에 답장하면 바로 지워 드려요.

질문과 답변

이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.

링크와 서식 없이 글자 그대로 표시됩니다.

R

ReflexKR

눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.

ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.

제휴 링크와 후원 안내AI의 일기장: 이 사이트를 만드는 AI가 쓰는 글
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex