reflex 是一个使用纯Python构建全栈web应用的库,但是需要使用node,所以你懂的。
reflex安装:实践reflex:一个使用纯Python构建全栈web应用的库-CSDN博客
创建hello项目
(base) sky@ub:~$ mkdir hello
(base) sky@ub:~$ cd hello/
(base) sky@ub:~/hello$ reflex init
─────────────────────── Initializing hello ────────────────────────
[06:42:32] Initializing the web directory. console.py:104
Warning: Failed to fetch templates. Falling back to default
template.
[06:42:50] Initializing the app directory. console.py:104
Success: Initialized hello
项目路径结构:
hello
├── .web
├── assets
├── hello
│ ├── __init__.py
│ └── hello.py
└── rxconfig.py
其中.web是隐藏目录,需要ls -la 才能看到。
.web
这是编译的Javascript文件的存储位置。您永远不需要触摸此目录,但它对调试很有用。
每个Reflex页面都将编译为.web/pages
目录中相应的.js
文件。
assets资产
目录是您可以存储任何您想要公开可用的静态资产的地方。这包括图像、字体和其他文件。
例如,如果您将图像保存到assets/image.png
,您可以从应用程序中显示它,如下所示:
rx.image(src="image.png")
主目录hello
每个项目的主目录,都跟项目名字一样。
初始化您的项目会创建一个与应用程序同名的目录。您将在这里编写应用程序的逻辑。
Reflex在hello/hello.py
文件中生成一个默认应用程序。您可以修改此文件来自定义您的应用程序。
配置
rxconfig.py文件
应用程序模块
Reflex根据config中的app_name
导入主应用程序模块,该模块必须将模块级全局命名app
定义为rx.App
的实例。
主应用程序模块负责导入构成应用程序的所有其他模块,并定义app = rx.App()
包含页面、状态和模型的所有其他模块必须由主应用程序模块或软件包导入,以便Reflex将它们包含在编译的输出中。
更详细的说明
页面包:example_big_app/pages
所有复杂的应用程序都将有多个页面,因此建议创建example_big_app/pages
作为软件包。
- 此软件包应在应用程序中每页包含一个模块。
- 如果特定页面取决于状态,则该子状态应在与页面相同的模块中定义。
- 返回页面的函数应该用
rx.page()
进行装饰,以便将其作为路由添加到应用程序中。
import reflex as rxfrom ..state import AuthStateclass LoginState(AuthState):def handle_submit(self, form_data):self.logged_in = authenticate(form_data["username"], form_data["password"])def login_field(name: str, **input_props):return rx.hstack(rx.text(name.capitalize()),rx.input(name=name, **input_props),width="100%",justify="between",)@rx.page(route="/login")
def login():return rx.card(rx.form(rx.vstack(login_field("username"),login_field("password", type="password"),rx.button("Login"),width="100%",justify="center",),on_submit=LoginState.handle_submit,),)
模板:example_big_app/template.py
大多数应用程序在页面之间保持一致的布局和结构。在单独的模块中定义此通用结构有助于在构建单个页面时轻松共享和重复使用。
最佳实践
- 将常见的前端用户界面元素分解为返回组件的函数。
- 如果一个函数接受返回组件的函数,它可以用作装饰器,如下所示。
from typing import Callableimport reflex as rxfrom .components.menu import menu from .components.navbar import navbardef template(page: Callable[[], rx.Component] ) -> rx.Component:return rx.vstack(navbar(),rx.hstack(menu(),rx.container(page()),),width="100%",)
状态管理
进入其它状态
在以前的版本中,如果应用程序想要将设置与用于修改的页面或组件一起存储在SettingsState
中,则需要访问这些设置的任何其他具有事件处理程序的状态必须从SettingsState
继承,即使其他状态大多是正交的。另一种状态现在也必须始终加载设置,即使对于不需要访问它们的事件处理程序也是如此。
更好的策略是仅从需要访问子状态的事件处理程序中按需加载所需状态。
设置组件
example_big_app/components/settings.py
import reflex as rxclass SettingsState(rx.State):refresh_interval: int = 15auto_update: bool = Trueprefer_plain_text: bool = Trueposts_per_page: int = 20def settings_dialog():return rx.dialog(...)
帖子页面:example_big_app/pages/posts.py
此页面加载SettingsState
,以确定每页显示多少个帖子以及刷新频率。
import reflex as rxfrom ..models import Post
from ..template import template
from ..components.settings import SettingsStateclass PostsState(rx.State):refresh_tick: intpage: intposts: list[Post]async def on_load(self):settings = await self.get_state(SettingsState)if settings.auto_update:self.refresh_tick = (settings.refresh_interval * 1000)else:self.refresh_tick = 0async def tick(self, _):settings = await self.get_state(SettingsState)with rx.session() as session:q = (Post.select().offset(self.page * settings.posts_per_page).limit(settings.posts_per_page))self.posts = q.all()def go_to_previous(self):if self.page > 0:self.page = self.page - 1def go_to_next(self):if self.posts:self.page = self.page + 1@rx.page(route="/posts", on_load=PostsState.on_load)
@template
def posts():return rx.vstack(rx.foreach(PostsState.posts, post_view),rx.hstack(rx.button("< Prev", on_click=PostsState.go_to_previous),rx.button("Next >", on_click=PostsState.go_to_next),justify="between",),rx.moment(interval=PostsState.refresh_tick,on_change=PostsState.tick,display="none",),width="100%",)
共同状态:example_big_app/state.py
由多个页面或组件共享的通用状态和子状态应在单独的模块中实现,以避免循环导入。此模块不应在应用程序中导入其他模块。
组件的可重复使用性
在Reflex中重复使用组件的主要机制是定义一个返回组件的函数,然后在需要该功能的地方简单地调用它。
组件函数通常不应将任何状态类作为参数,而是倾向于导入所需的状态并直接访问类上的变量。
记忆功能以提高性能
在大型应用程序中,如果一个组件有许多子组件或在大量位置使用,它可以提高编译和运行时性能,使用@lru_cache
装饰器对函数进行内啮。
要回忆foo
组件以避免多次重新创建,只需将@lru_cache
添加到函数定义中,该组件将仅每组唯一参数创建一次。
from functools import lru_cacheimport reflex as rxclass State(rx.State):v: str = "foo"@lru_cache
def foo():return rx.text(State.v)def index():return rx.flex(rx.button("Change",on_click=State.set_v(rx.cond(State.v != "bar", "bar", "foo")),),*[foo() for _ in range(100)],direction="row",wrap="wrap",)
示例_大_应用程序/组件
此软件包包含应用程序的可重复使用部分,例如页眉、页脚和菜单。如果特定组件需要状态,则子状态可以在同一区域模块中定义。组件模块中定义的任何子状态应仅包含与该单个组件相关的字段和事件处理程序。
外部组件
Reflex 0.4.3引入了对reflex componentCLI命令的支持,这使得它可以轻松捆绑通用功能,作为独立的Python软件包在PyPI上发布,可以在任何Reflex应用程序中安装和使用。
在包装npm组件或其他自包含的功能片段时,将此复杂性移出应用程序本身可能会有所帮助,以便于在其他应用程序中更易于维护和重复使用。
数据库模型:example_big_app/models.py
建议在单个文件中实现所有数据库模型,以便更容易定义关系和理解整个模式。
然而,如果模式非常大,则有一个models
包,在自己的模块中定义了单个模型,这可能是有意义的。
无论如何,单独定义模型允许任何页面或组件导入和使用它们,而无需循环导入。
顶级套餐:example_big_app/__init__.py
这是一个导入所有状态、模型和页面的好地方,这些状态、模型和页面应该是应用程序的一部分。通常,组件和助手不需要导入,因为它们将由使用它们的页面导入(或者它们将未使用)。
from . import state, models
from .pages import (index,login,post,product,profile,schedule,
)__all__ = ["state","models","index","login","post","product","profile","schedule",
]