跳到主要内容

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 类方法。这些参数可用于自定义组件,无论是通过应用默认值还是将属性传递给某些子组件。

在以下示例中,我们实现了一个可编辑的文本组件,允许用户点击文本将其转换为输入字段。如果用户未提供自己的 valueon_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 组件被设计为可重用,所以它可以处理 valueon_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
),
),
),
)