본문 바로가기
REFLEX 튜토리얼/reflex 공식문서 요약

간단한 동적 웹앱 만들어보기 : Counter

by 일코 2023. 10. 3.

이번 포스팅에서는 아래와 같은 페이지를 만들어 보고,
State의 사용법에 대해 간단히 이해해보고자 한다.

아래와 같은 코드로 index 함수를 만들었다.

import reflex as rx


def index():
    return rx.vstack(  # 행 방향으로 나열하는 레이아웃 컴포넌트
        rx.heading("Counter"),  # 제목
        rx.hstack(  # 열 방향으로 나열하는 레이아웃 컴포넌트
            rx.button("-", color_scheme="red"),
            rx.heading("0"),
            rx.button("+", color_scheme="green")
        )
    )


app = rx.App()
app.add_page(index)
app.compile()

참고로 위에서 사용한 vstack, hstack은
각각 행방향, 열방향으로 컴포넌트를 배치하는 레이아웃 컴포넌트다.

vstack과 hstack, 이 둘만 잘 다뤄도
다소 복잡한 레이아웃이나, 격자모양의 많은 컴포넌트가 들어가는 화면을
효과적으로 구현할 수 있다.

그리고 아직 이 페이지는 정적(static)이다.
무슨 뜻이냐면,
버튼을 클릭하더라도 페이지의 레이아웃이나 정보의 변경이 없다. 아직은.

이제 동작을 추가해보자.

이 때 State 클래스를 사용하면 된다.

State 클래스를 사용하는 맥락을 미리 조금만 설명해보면,
우리 앱의 index 함수는 앱이 실행되는 시점에 딱 한 번 리턴을 수행하며,
내부적으로는 html로 컴파일되어버린다.
그래서 index 안에서 특정 변수를 정의해도 이는 바뀔 수 없다.
그런데, rx.State를 상속받은 State라는 클래스는 재미있게도
클래스 내부에서 정의된 멤버변수들을 계속해서 지켜보다가
변수의 값이 바뀌는, 즉 변수의 값을 바꾸는 메서드가 실행되는 즉시
프론트엔드 화면 중 해당 값이 사용된 부분을 업데이트해준다.
사실 리액트 덕분이다.

이제 코드를 아래와 같이 추가해보자.

import reflex as rx


class State(rx.State):
    num: int = 0  # <--
    
    def plus(self):
        self.num += 1  # <--
        
    def minus(self):
        self.num -= 1  # <--


def index():
    return rx.vstack(  # 행 방향으로 나열하는 레이아웃 컴포넌트
        rx.heading("Counter"),  # 제목
        rx.hstack(  # 열 방향으로 나열하는 레이아웃 컴포넌트
            rx.button("-", color_scheme="red", on_click=State.minus),  # <--
            rx.heading(State.num),  # <--
            rx.button("+", color_scheme="green", on_click=State.plus)  # <--
        )
    )


app = rx.App()
app.add_page(index)
app.compile()

State 클래스 안에
num이라는 멤버변수 하나와
각각 num을 1씩 오르내리게 하는 plus, minus 메서드를 정의했다.

그리고 index 함수에서 rx.heading("0")을 rx.heading(State.num)으로 교체했으며
마지막으로 button들의 on_click 이벤트에 해당 이벤트핸들러(메서드)를 추가했다.

리플렉스의 여러 신박한 아이디어 중 가장 돋보이는 것은 역시, 
State라는 파이썬 클래스와 prop이라는 컴포넌트 파라미터를 통해
리액트의 State와 Prop, 그리고 Component 개념을
큰 이질감 없이 파이썬 코드로 구현할 수 있게 해 놓았다는 부분이다.

이를 통해 파이썬 알고리즘만으로 화면의 동적인 부분을 컨트롤할 수 있다는 것은 신박한 수준이다.
정말로 순수하게 로직만 고민하고, 고민의 결과를 파이썬 코드로 옮겨적는 느낌이다.

그럼 이제 터미널에서 `reflex run`으로 위 앱을 실행해보자.

이렇게 카운터 앱이 완성되었다.

CSS나 프론트엔드 라이브러리에 익숙한 분들은
이제 이걸 어떻게 꾸밀까 하는 마음이 먼저 들 수도 있지만,
스타일과 세세한 배치는 아직은 신경쓰지 말자.

우선 State 클래스 안에서 정의한 num이라는 녀석이 어떻게 작동하는지
조금만 더 설명하겠다.

State.num 뿐만 아니라

rx.State를 상속받은 모든 클래스 안의 변수들은
State 클래스가 계속해서 주시한다고 언급한 바 있다.
바뀔 것을 가정하고 정의한 (진정한 의미의) 변수이기 때문에,
리플렉스는 이 변수들을 통틀어 Var(봐?)라고 부른다.
(실제로 State.num의 타입은 rx.vars.BaseVar이다.)

이 변수들은 대부분의 경우
**사용자의 입력**에 따라 값이 바뀔 것이기 때문에
(당연하게도) 사용자의 입력을 받는 State 메서드를 같이 정의해줘야 한다.
그리고 이 메서드들이 바로 이벤트핸들러가 된다.
위에서 만든 카운트 앱의 경우엔 State.plus와 State.minus가 이에 해당한다.

그리고 리플렉스에 미리 정의된
다양한 이벤트들(on_click, on_change, on_submit 등...) 덕분에
거의 모든 인터랙션을 손쉽게 구현할 수 있다.
단, 특정 컴포넌트가 어떤 이벤트를 제공하고 있는지는 검색할 수 있어야 한다.
(reflex.dev의 Reference 메뉴 안에 컴포넌트별 이벤트 목록이 상세히 기술되어 있다.)

Reflex | Docs

 

Reflex | Docs

Copyright © 2023 Pynecone, Inc.

reflex.dev

Var는 BaseVar 뿐만 아니라,
ComputedVar(메서드 형태로, 리턴값이 변수가 되는 Var. @rx.var 어노테이션을 달면 됨),
BackendVar(js로 직렬화되지 않는 변수. np.array 등을 그대로 사용할 수 있음. 변수명을 언더스코어"_"로 시작하면 됨)
등이 있으며,
그밖에도 브라우저의 쿠키나 로컬 스토리지를 이용해 값을 저장하고 이용할 수도 있다.

구체적인 활용방법은 이후에 차근차근 다루도록 하고,
이번 포스팅은 여기서 마친다.

다음 포스팅에서는 파이썬의 List 자료형을 활용한,
일종의 투두리스트 앱을 만들어보고자 한다.

(궁금한 내용이 있으면 방명록이나 이메일 말고 댓글로 직접 남겨주시길ㅜㅜ)

댓글