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

파이썬의 리스트도 표현할 수 있나? # 투두리스트 앱 만들기

by 일코 2023. 10. 3.

앞선 포스팅에서는 간단히 rx.heading이나 rx.text를 이용해 화면에 글자를 출력해보았으며
State라는 클래스와 클래스 변수인 Var를 이용해 동적인 웹앱, Counter를 구현해보았다.

그럼 이런 생각도 해볼 수 있을텐데?

단순한 텍스트 목록은 rx.text를 나열하면 될텐데,
["a", "b", "c"] 같은 리스트도 표현할 수 있을까?

 

(직접 공식문서를 찾아 읽어보신 분이라면, 어렵지 않게 구현하실 것)

이 때는 우리가 사용할 또 다른 레이아웃 컴포넌트,
바로 rx.foreach가 필요하게 되는 시점이다. (굳이 비유하면 파이썬의 for문과 map 함수를 합친 느낌이다.)

참고로 rx.foreach는 단순한 파이썬의 for문과는 살짝 다르다.
무엇이 다른고 하니,

rx.foreach(리스트, 각각 요소에 적용할 함수) 방식으로 사용하면

`함수(요소1), 함수(요소2), 함수(요소3), ...` 식으로 표현된다.

파이썬의 map이나, numpy의 apply 등을 써보신 분들이라면 이해가 빠를 것.

 

이런 배경 없이 단순히 리스트를 표현하고 싶은 분들은 아마
가장 먼저 아래와 같이 작성해보시지 않을까 싶다.

## 잘못된 코드

import reflex as rx


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


def index():
    return rx.vstack(
        State.todo_list
    )


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

하지만 아쉽게도 위 코드는 작동하지 않는다.

텅 빈 브라우저...ㅜ

왜 이렇게 작동하지 않는 걸까?

이 이유를 정확하게 이해하는 것이 꼭 필요한 것은 아니지만,
이를 어느 정도 숙지하고 나면 분명 버그를 현저히 줄일 수 있게 될 것이다.

결론부터 이야기하자면,

데이터가 State 클래스에서 index로 넘어오는 시점에서,
그 변수는 이미 파이썬 타입이 아니기 때문이다. 자바스크립트의 변수로 직렬화되어 있다.
그래서 index 함수 안에서 파이썬의 문법을 쓰는 것은 대부분의 경우 오류가 난다.

그럼 어떻게 해야 할까?
① State클래스 안에서 모든 필요한 연산을 마쳐야 하며, index에서는 고스란히 받아온 값대로만 사용해야 한다.
② 페이지함수(index) 안에서는 for문 대신 레이아웃 컴포넌트를 활용해야 한다. rx.foreach도 그 경우다.

(참고로 아래 접은글에는 BackendVar를 사용해서 해결하는 예시를 적어두었다. 추천하지는 않는다.)

더보기

BackendVar를 대안으로 사용할 수는 있다.

이전 포스팅에서 BackendVar에 대해 스치듯 언급한 바 있다.
State 클래스변수 앞에 언더스코어(_)를 붙이면 BaseVar가 아닌 BackendVar로 정의되며,
이는 내부적으로 직렬화를 거치지 않고 파이썬의 자료형을 그대로 사용한다.

실제로 아래 코드는 잘 작동한다.

## 작동하기는 함

import reflex as rx


class State(rx.State):
    _todo_list = ["밥먹기", "잠자기", "공부하기"]  # 변수명 앞에 _를 붙임


def index():
    return rx.vstack(
        *[rx.text(i) for i in State._todo_list]  # 여기에도.
    )


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

실행화면은 아래와 같다. (rx.vstack은 가운데정렬이 기본값이다.)

잘 구현된다.
하지만 꼭 필요한 경우가 아니라면 사용하지 않는 것이 좋겠다.
굳이 필요한 예를 들어보면, numpy로 배열 연산 관련 인터랙션을 하고 싶은 경우 정도?

하여튼,

이제 rx.foreach를 사용하는 방식으로 코드를 업데이트해보았다.
위의 코드와 차이점을 확인해 보기 바란다.

## 올바른 코드 : rx.foreach 사용

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

위 코드를 실행하면 아래와 같은 화면이 나타난다.

참고로 foreach의 두 번째 인자, render_fn은 굳이 컴포넌트가 아니라(위에서는 rx.text를 사용했음)
임의로 정의한 함수를 적용할 수도 있다.
예를 들어, 각 텍스트를 토마토색 둥그스름 박스 안에 노란색 텍스트로 표시하고 싶다면?

아래와 같이 todo_box라는 임의의 함수를 정의하고
이를 rx.foreach의 render_fn에다 넣어보았다.

import reflex as rx


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


def todo_box(item):
    return rx.box(rx.text(item),
                  bg="tomato", color="yellow", border_radius="1em",
                  text_align="center", margin="0.5em")


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


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

이 코드의 실행결과는 아래와 같다.

 

리스트를 처리하는 방식이 너무 간단해서 별 것 아닌 것처럼 느껴질 수 있지만,
아마 이 포스팅 시리즈의 끝까지 대부분 이런 느낌일지도 모른다.

로직 외의 군더더기를 생각하지 않아도 된다는 건 얼마나 큰 장점인가!

 

이제 이 포스팅을 기점으로,
본격적인 투두리스트 앱을 만들어보자.
우선 스타일이나 레이아웃은 미뤄놓고(솔직히 밝히건대, 전혀 다루지 않을 것)
CRUD 로직을 완성하는 것을 기본목표로 하자.

실은 벌써 투두리스트 앱의 CRUD 네 가지 기능 중 Read를 완성해버렸다.
다음 포스팅에서는 사용자 입력을 통해 할 일을 추가하는 Create 기능을 구현해보자.

댓글