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