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

투두리스트에서 완료한 업무를 제거해보자. # Delete[2/2]

by 일코 2023. 10. 12.

지난 포스팅에서는
특정 할일의 정확한 삭제(pop)를 위해
State에서 enumerate를 통해 enum_list: list[tuple[int, str]] 라는 변수를 추가로 만들었다.
(이 안에 index번호가 있는 덕분에, 정확하게 순서(인덱스)를 명시하여 특정 요소를 삭제할 수 있게 됐다.)

그래서 이번 포스팅에서는
페이지함수인 index를 완성해보기로 한다.

기존에 만든, "할 일 추가"만 가능한 페이지 함수 index는 아래와 같다.

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)
        )
    )

① 우선 4번 라인의 State.todo_list를 State.enum_list로 변경해야 한다. (삭제를 위한 인덱스 정보도 필요하니까!)

② 4번 라인 rx.foreach 컴포넌트의 렌더 함수(두 번째 파라미터)를 단순히 rx.text로 때웠는데,
    이젠 튜플을 받아서 첫 번째 원소인 인덱스는 삭제시 활용하고,
    두 번째 원소인 값은 rx.text로 감싸주는 별도의 렌더함수를 만들어야 한다.

이 두가지를 반영해서 코드를 수정하면 아래와 같다.

def render_fn(item: tuple[int, str]) -> rx.Component:
    return rx.hstack(
        rx.text(item[1]),  # (idx, val) 중 val
        rx.button("x", on_click=lambda: ListTodoState.delete_item(item[0]))  # (idx, val) 중 idx
    )


def index() -> rx.Component:
    return rx.container(
        rx.foreach(
            State.enum_list, render_fn
        ),
        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)
        )
    )
참고로, render_fn의 이벤트핸들러인 on_click에 지정된
람다함수에 대해 설명해보겠다.

상식적으로, on_click 이벤트에게는 아무 파라미터도 주어지지 않는다.
(반대의 예로 rx.form의 on_submit 이벤트에는 "사용자입력값"이라는 dict 인자가 들어간다.)
그런데 우리의 의도는? State에게 item[0]을 전달해 주어야 하기 때문에,
lambda: ListTodoState.delete_item(item[0]) 이라는 익명함수를 굳이 만든 것이다.
이 람다함수를 def문으로 다시 풀어쓰면 아래와 같을 것이다.

def 익명():  # 사실은 이름이 없고, 아무 파라미터도 받지 않음
    ListTodoState.delete_item(item[0])  # 근데 구현부에서는 특정 로컬변수(item[0])를 사용함.

이런 방식으로, 함수 정의에는 아무 파라미터도 받지 않지만
함수 내부에서 로컬정보인 item[0]을 활용하기 위해 lambda 함수를 활용했다.
이는 GUI 개발에서도 흔하게 쓰는 방법이다.

그럼 State와 index를 포함한 전체 함수는 이제 아래와 같다.

import reflex as rx


class State(rx.State):
    todo_list = ["밥먹기", "잠자기", "공부하기"]
    new_item: str
	
    @rx.var
    def enum_list(self) -> list[tuple[int, str]]:
        return [(i, j) for i, j in enumerate(self.todo_list)]
    
    def add_new_item(self):
        self.todo_list.append(self.new_item)
        self.new_item = ""
        
    def delete_item(self, idx):
        self.todo_list.pop(idx)
        
        
def render_fn(item: tuple[int, str]) -> rx.Component:
    return rx.hstack(
        rx.text(item[1]),  # (idx, val) 중 val
        rx.button("x", on_click=lambda: State.delete_item(item[0]))  # (idx, val) 중 idx
    )


def index() -> rx.Component:
    return rx.container(
        rx.foreach(
            State.enum_list, render_fn
        ),
        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()

이렇게 작성을 완료한 후
터미널에서 reflex run을 입력하고
실행해보자.

실행화면은 아래와 같다.

중복되는 "aa" 중 [x]를 클릭한 아이템만 삭제가 된다.

이 정도로도 충분히 쓸모가 있는 투두리스트처럼 보이지만,
아직 CRUD 중 Update가 남았다.
다음 포스팅에서 이를 구현할 예정인데
개인적인 고민의 결과로는
해당 텍스트를 직접 클릭하면 수정이 되도록
UI를 짜는 것이 최선이겠다는 생각이 들었다.
특히 리플렉스에서는 이와 같은 수정 기능을 위한
rx.editable이라는 컴포넌트를 제공하고 있기 때문에 구현은 수월하다.

참고삼아 먼저 보여드리면 CRUD를 완성한 웹앱은 아래와 같이 작동한다.

댓글