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

투두리스트에 할 일을 추가해보자. #Create

by 일코 2023. 10. 3.
공식문서의 투두리스트 앱은 로직의 간단함에 비해 다소 구현이 복잡하게 되어 있다.
이 포스팅 시리즈에서는 최소의 코드로 투두리스트를 만들어보면서
리플렉스에서 로직을 구현하는 방법에 익숙해지도록 하는 것을 1차목표로 한다.
(이 포스팅을 읽고 나면 공식문서의 todo 앱도 한 번 연습해 볼 것을 추천한다.)

 

지난 포스팅에서는 리스트를 표현하는 방법으로 시작해서
우리가 구현할 투두리스트 앱의 CRUD 중 R을 완성했다.
이번에는 Create를 구현할 차례인데,
이 또한 너무너무 간단해서 헛웃음이 나올 정도다.

한 번 시작해보자.

지난 포스팅에서 완성한 코드는 아래와 같다.

import reflex as rx


class State(rx.State):
    todo_list = ["밥먹기", "잠자기", "공부하기"]


def index():
    return rx.foreach(
            State.todo_list, rx.text
        )


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

새로운 아이템의 입력은 rx.input과 rx.button을 이용해서
State.todo_list에 아이템을 append하는 방식으로 구현할 것이다.

우선 화면을 먼저 구현해본다. (로직은 직후에)

index 함수를 아래와 같이 수정해보았다.

def index():
    return rx.container(
        rx.foreach(
            State.todo_list, rx.text
        ),
        rx.hstack(
            rx.input(), rx.button("추가")
        )
    )

화면이 아래와 같이 변경되었다.

아직은 인풋과 버튼이 작동하지 않는다.
이제 State에 추가로 필요한 변수인 new_item을 추가하고
관련 이벤트핸들러(클래스메서드)인 add_new_item을 정의하자.

State 클래스를 아래와 같이 업데이트하자.

class State(rx.State):
    todo_list = ["밥먹기", "잠자기", "공부하기"]
    new_item: str
    
    def add_new_item(self):
        self.todo_list.append(self.new_item)
참고로 `new_item: str`이라는 코드는 값을 미리 정하지 않고 자료형만 정의하겠다는 뜻이며
`new_item = ""` 이나 `new_item: str=""` 방식으로 정의해도 무관하다.
(이후에 설명하겠지만, State와 페이지 함수간의 인터랙션을 위해 자료형을 꼭 명시해야 하는 경우가 있다.
 파이썬 코드 안에서 자료형을 정의하는 것이 지금은 다소 귀찮더라도 조금씩 익숙해지자.)

이제 클래스변수와 메서드를 index 안에도 적용해보자.

이벤트핸들러를 적용하는 방법은 대체로 비슷하며,
on_change, on_submit, on_click 등 on_~~ 형태의 미리 정의된 파라미터를 사용한다.
IDE의 자동완성 기능을 적극 지원하므로 Ctrl-Space 등의 단축키로 조회해보자.

index 함수를 아래와 같이 업데이트하였다.

def index():
    return rx.container(
        rx.foreach(
            State.todo_list, rx.text
        ),
        rx.hstack(
            rx.input(value=State.new_item, on_change=State.set_new_item),  # <--
            rx.button("추가", on_click=State.add_new_item)  # <--
        )
    )
<State.set_new_item의 정체>

코드를 꼼꼼히 읽어보면서 작성했다면
State.set_new_item 메서드를 정의한 적이 없다는 사실을 알아차렸을 것이다.
이는 rx.State에서 지원하는 편의기능으로,
리플렉스 앱이 실행되는 시점에
모든 State 변수마다 `set_변수명`이라는 setter 메서드가 자동생성된다.

rx.input에서 value 파라미터에 변수명을 입력하면
입력한 값으로 해당 변수의 값이 변경된다.
단 입력을 완료하고 버튼을 클릭하는 시점이 아니라,
매번 값이 수정되는(타이핑하고 삭제하는) 모든 시점에 값이 변경되도록 하기 위해
on_change 이벤트를 이용했다.
만약 작성중에는 변수값이 바뀌지 않고,
인풋컴포넌트에서 포커스가 떠나는 시점(버튼을 누르는 시점)에 
변수의 값이 바뀌게 하고 싶다면?
그 때는 on_blur 이벤트를 사용하면 된다.

하여튼 실행해보자. (다소 어색한 부분을 찾아볼 것)

이상한 점이 보이는가? 추가 후에도 인풋창 값이 그대로다!

State.add_new_item 메서드 안에
State.new_item의 값을 초기화하는 로직이 빠져 있어서
입력 후에도 인풋칸의 value가 유지되기 때문이다.
`self.new_item = ""`이라는 단 한 줄의 코드만 추가하면 된다.

State 클래스의 add_new_item 메서드만 아래와 같이 수정하자.

def add_new_item(self):
    self.todo_list.append(self.new_item)
    self.new_item = ""

다시 실행해보면!?

인풋 컴포넌트가 정상적으로 비워지는 것까지 확인했다.
여기까지 투두리스트의 CRUD 중 R과 C를 구현해보았다.

이제 다음 포스팅에서 차례대로 Delete와 Update를 구현해보자.

참고로 인풋창에서 할일 입력 후 엔터키만 쳐도 목록에 추가되게 하는 방법은
아래의 접은글을 펼쳐 확인할 수 있다.

더보기

굳이 버튼을 클릭하지 않고,
인풋창에서 입력 후 엔터만 쳐도 할일이 추가되게 하려면
on_submit이라는 이벤트를 사용해야 하는데,
이는 rx.input이나 rx.button에서 지원하는 기능이 아니다.
rx.form이라는 컴포넌트 안에 input과 button을 넣고,
rx.form에서 직접 on_submit 이벤트를 사용해야 한다.
그리 복잡하지 않으므로 아래 코드의 화살표 주석 부분만 읽어보자.

 

import reflex as rx


class State(rx.State):
    todo_list = ["밥먹기", "잠자기", "공부하기"]
    new_item: str

    def add_new_item(self):
        self.todo_list.append(self.new_item)
        self.new_item = ""


def index():
    return rx.container(
        rx.foreach(
            State.todo_list, rx.text
        ),
        rx.form(  # <--
            rx.hstack(
                rx.input(value=State.new_item, on_change=State.set_new_item,
                         placeholder="추가할 일 입력 후 엔터"),
                rx.button("추가", on_click=State.add_new_item)
            ),
            on_submit=lambda x: State.add_new_item(x)  # <--
        )
    )


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

실행화면은 아래와 같다.
(엔터키를 눌러 할일 추가를 할 수 있게 되었다.)

우선은 Delete부터!

댓글