Substates
子状态允许你将状态分解为多个类,使其更易于管理。随着应用程序的增长,这非常有用,因为它允许你将每个页面视为一个独立的实体。子状态还允许你共享常见的状态资源,如变量或事件处理程序。
当某个 state 类变得过于庞大时,将其拆分为若干子状态可以带来性能上的优势,因为只需加载用于处理特定事件的部分状态。
Multiple States
一种常见的模式是为应用中的每个页面创建一个子状态。这使您能够将每个页面视为一个独立的实体,并随着应用的扩展更轻松地管理代码。
要创建一个子状态,只需多次继承自 rx.State 即可:
# index.py
import reflex as rx
class IndexState(rx.State):
    """Define your main state here."""
    data: str = "Hello World"
@rx.page()
def index():
    return rx.box(rx.text(IndexState.data)
# signup.py
import reflex as rx
class SignupState(rx.State):
    """Define your signup state here."""
    username: str = ""
    password: str = ""
    def signup(self):
        ...
@rx.page()
def signup_page():
    return rx.box(
        rx.input(value=SignupState.username),
        rx.input(value=SignupState.password),
    )
# login.py
import reflex as rx
class LoginState(rx.State):
    """Define your login state here."""
    username: str = ""
    password: str = ""
    def login(self):
        ...
@rx.page()
def login_page():
    return rx.box(
        rx.input(value=LoginState.username),
        rx.input(value=LoginState.password),
    )
Separating the states is purely a matter of organization. You can still access the state from other pages by importing the state class.
# index.py
import reflex as rx
from signup import SignupState
...
def index():
    return rx.box(
        rx.text(IndexState.data),
        rx.input(value=SignupState.username),
        rx.input(value=SignupState.password),
    )
State Inheritance
A substate can also inherit from another substate other than rx.State, allowing you to create a hierarchy of states.
例如,您可以创建一个基础状态,定义应用程序中所有页面共有的变量和事件处理程序,比如当前登录的用户。
class BaseState(rx.State):
    """Define your base state here."""
    current_user: str = ""
    def logout(self):
        self.current_user = ""
class LoginState(BaseState):
    """Define your login state here."""
    username: str = ""
    password: str = ""
    def login(self, username, password):
        # authenticate
        authenticate(...)
        # Set the var on the parent state.
        self.current_user = username
您可以从子状态自动访问父状态的属性。
Accessing Arbitrary States
通过调用 .get_state 这个异步方法,State A 也能访问到 State B 的成员变量;如果请求的状态尚未加载,将按需加载并反序列化。
在以下示例中, GreeterState 访问 SettingsState 以获取 salutation ,并使用它来更新 message 变量。
class SettingsState(rx.State):
    salutation: str = "Hello"
def set_salutation_popover():
    return rx.popover.root(
        rx.popover.trigger(
            rx.icon_button(rx.icon("settings")),
        ),
        rx.popover.content(
            rx.input(
                value=SettingsState.salutation,
                on_change=SettingsState.set_salutation,
            ),
        ),
    )
class GreeterState(rx.State):
    message: str = ""
    @rx.event
    async def handle_submit(
        self, form_data: dict[str, Any]
    ):
        settings = await self.get_state(SettingsState)
        self.message = (
            f"{settings.salutation} {form_data['name']}"
        )
def index():
    return rx.vstack(
        rx.form(
            rx.vstack(
                rx.hstack(
                    rx.input(placeholder="Name", id="name"),
                    set_salutation_popover(),
                ),
                rx.button("Submit"),
            ),
            reset_on_submit=True,
            on_submit=GreeterState.handle_submit,
        ),
        rx.text(GreeterState.message),
    )
Performance Implications 性能影响
当 event handler 被调用时,Reflex 不仅会加载包含该 event handler 的子状态的数据,还会加载其所有子状态和父状态的数据。如果一个状态拥有大量子状态或包含大量数据,可能会减慢与该状态相关的事件处理速度。
为了获得最佳性能,保持一个扁平结构,使大多数子状态类直接从 rx.State 继承。仅当父状态包含子状态常用的数据时,才从另一个状态继承。Implementing different parts of the app with separate, unconnected states ensures that only the necessary data is loaded for processing events for a particular page or component.
避免在包含大量数据的状态中定义计算变量,因为带有计算变量的状态总是会被加载以确保值被重新计算。使用计算变量时,最好将其定义在直接继承自 rx.State 且没有其他状态继承自它的状态中,以避免加载不必要的数据。
Component State
定义 rx.ComponentState 的子类会创建一种特殊类型的状态,这种状态与组件实例绑定,而非全局存在于应用中。组件状态将 UI 代码与状态变量和事件处理器结合,有助于创建彼此独立运行的可复用组件。
class ReusableCounter(rx.ComponentState):
    count: int = 0
    @rx.event
    def increment(self):
        self.count += 1
    @rx.event
    def decrement(self):
        self.count -= 1
    @classmethod
    def get_component(cls, **props):
        return rx.hstack(
            rx.button("Decrement", on_click=cls.decrement),
            rx.text(cls.count),
            rx.button("Increment", on_click=cls.increment),
            **props,
        )
reusable_counter = ReusableCounter.create
def multiple_counters():
    return rx.vstack(
        reusable_counter(),
        reusable_counter(),
        reusable_counter(),
    )
在 ReusableCounter 类上定义的变量和事件处理程序与普通 State 类类似处理,但将限定在组件实例范围内。每次创建 reusable_counter 时,也会为该组件实例创建一个新的 state class。
get_component 类方法用于定义组件的用户界面并将其与状态关联,状态通过 cls 参数访问。返回的组件可能还会引用其他状态,但 cls 始终是 ComponentState 的实例,且该实例对于返回的组件是唯一的。
Passing Props
类似于普通组件, ComponentState.create 类方法接受任  意的 *children 和 **props 参数,并默认将它们传递给您的 get_component 类方法。这些参数可用于自定义组件,无论是通过应用默认值还是将属性传递给某些子组件。
在以下示例中,我们实现了一个可编辑的文本组件,允许用户点击文本将其转换为输入字段。如果用户未提供自己的 value 或 on_change 属性,则将使用 EditableText 类中定义的默认值。
class EditableText(rx.ComponentState):
    text: str = "Click to edit"
    original_text: str
    editing: bool = False
    @rx.event
    def start_editing(self, original_text: str):
        self.original_text = original_text
        self.editing = True
    @rx.event
    def stop_editing(self):
        self.editing = False
        self.original_text = ""
    @classmethod
    def get_component(cls, **props):
        # Pop component-specific props with defaults before passing **props
        value = props.pop("value", cls.text)
        on_change = props.pop("on_change", cls.set_text)
        cursor = props.pop("cursor", "pointer")
        # Set the initial value of the State var.
        initial_value = props.pop("initial_value", None)
        if initial_value is not None:
            # Update the pydantic model to use the initial value as default.
            cls.__fields__["text"].default = initial_value
        # Form elements for editing, saving and reverting the text.
        edit_controls = rx.hstack(
            rx.input(
                value=value,
                on_change=on_change,
                **props,
            ),
            rx.icon_button(
                rx.icon("x"),
                on_click=[
                    on_change(cls.original_text),
                    cls.stop_editing,
                ],
                type="button",
                color_scheme="red",
            ),
            rx.icon_button(rx.icon("check")),
            align="center",
            width="100%",
        )
        # Return the text or the form based on the editing Var.
        return rx.cond(
            cls.editing,
            rx.form(
                edit_controls,
                on_submit=lambda _: cls.stop_editing(),
            ),
            rx.text(
                value,
                on_click=cls.start_editing(value),
                cursor=cursor,
                **props,
            ),
        )
editable_text = EditableText.create
def editable_text_example():
    return rx.vstack(
        editable_text(),
        editable_text(
            initial_value="Edit me!", color="blue"
        ),
        editable_text(
            initial_value="Reflex is fun",
            font_family="monospace",
            width="100%",
        ),
    )
因为这个 EditableText 组件被设计为可重用,所以它可以处理 value 和 on_change 链接到普通全局状态的情况。
class EditableTextDemoState(rx.State):
    value: str = "Global state text"
def editable_text_with_global_state():
    return rx.vstack(
        editable_text(
            value=EditableTextDemoState.value,
            on_change=EditableTextDemoState.set_value,
        ),
        rx.text(EditableTextDemoState.value.upper()),
    )
Accessing the State
ComponentState 的底层状态类可通过 .State 属性访问。要使用它,请将组件实例分配给局部变量,然后将该实例包含在页面中。
def counter_sum():
    counter1 = reusable_counter()
    counter2 = reusable_counter()
    return rx.vstack(
        rx.text(
            f"Total: {counter1.State.count + counter2.State.count}"
        ),
        counter1,
        counter2,
    )
其他组件也可以通过 .State 属性引用其事件处理程序或变量来影响 ComponentState 。
def extended_counter():
    counter1 = reusable_counter()
    return rx.vstack(
        counter1,
        rx.hstack(
            rx.icon_button(
                rx.icon("step_back"),
                on_click=counter1.State.set_count(0),
            ),
            rx.icon_button(
                rx.icon("plus"),
                on_click=counter1.State.increment,
            ),
            rx.button(
                "Double",
                on_click=counter1.State.set_count(
                    counter1.State.count * 2
                ),
            ),
            rx.button(
                "Triple",
                on_click=counter1.State.set_count(
                    counter1.State.count * 3
                ),
            ),
        ),
    )