11 个版本 (4 个破坏性更新)
0.5.2 | 2021 年 4 月 11 日 |
---|---|
0.5.1 | 2021 年 4 月 11 日 |
0.5.0 | 2021 年 2 月 8 日 |
0.4.0 | 2020 年 10 月 18 日 |
0.1.0 | 2020 年 7 月 13 日 |
#7 in #shared-state
每月 150 次下载
用于 yew-input
49KB
1K SLoC
注意:项目已重命名为 Yewdux
yew-state 将不再接收更新。请使用 Yewdux 代替。
在 Yew 中进行状态管理可能会变得复杂,尤其是在许多组件需要可变访问共享状态时。这个 crate 简化了这种访问(与 React 中的上下文钩子类似),这样你可以花更少的时间编写样板代码!
安装
从终端安装此包
$ cargo install cargo-edit
$ cargo add yew-state
或将它添加到你的项目的 Cargo.toml
[dependencies]
yew-state = "^0.4"
快速入门
SharedStateComponent
通过添加 SharedHandle
属性并将它们包裹在 SharedStateComponent
中来为你的组件提供共享状态。
use yew::prelude::*;
use yew_state::{SharedHandle, SharedStateComponent};
use yewtil::NeqAssign;
type Handle = SharedHandle<u64>;
enum Msg {
Reset,
}
struct Model {
handle: Handle,
link: ComponentLink<Self>,
}
impl Component for Model {
type Properties = Handle;
type Message = Msg;
fn create(handle: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { handle, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Reset => {
// Reset count to 0.
self.handle.reduce(|count| *count = 0);
// Don't render yet, receive changes in `change`.
false
}
}
}
fn change(&mut self, handle: Self::Properties) -> ShouldRender {
// Receive new state here.
self.handle.neq_assign(handle)
}
fn view(&self) -> Html {
// Increment count by 1.
let incr = self.handle.reduce_callback(|count| *count += 1);
// Emit message to reset count.
let reset = self.link.callback(|_| Msg::Reset);
html! {
<>
<button onclick=incr>{ self.handle.state() }</button>
<button onclick=reset>{"Reset"}</button>
</>
}
}
}
type App = SharedStateComponent<Model>;
StateView
StateView
组件是快速编写对共享状态简单访问的便捷方式。以牺牲一点控制为代价,它们几乎不需要样板代码
use yew::prelude::*;
use yew_state::{component, SharedHandle, StateView};
fn view_counter() -> Html {
type Handle = SharedHandle<u64>;
let view = component::view(|handle: &Handle| {
// Increment count by 1.
let incr = handle.reduce_callback(|count| *count += 1);
// Reset count to 0.
let reset = handle.reduce_callback(|count| *count = 0);
html! {
<>
<button onclick=incr>{ handle.state() }</button>
<button onclick=reset>{"Reset"}</button>
</>
}
});
html! {
<StateView<Handle> view=view />
}
}
用法
共享状态通过状态句柄(SharedHandle
或 StorageHandle
)访问,这些句柄由组件包装器 SharedStateComponent
管理。这个包装器负责所有无聊的消息传递,用于发送和接收状态更新。然后,更新后的状态像任何其他属性一样传递给您的组件,并在 Component::change
中处理。
将以下内容添加到您的组件中,以提供共享状态(其他细节省略)
use yew::prelude::*;
use yew_state::{SharedHandle, SharedStateComponent};
#[derive(Default, Clone)]
struct MyState {
// ..
}
struct Model {
handle: SharedHandle<MyState>,
}
impl Component for Model {
type Properties = SharedHandle<MyState>;
// ..
}
type MyComponent = SharedStateComponent<Model>;
状态句柄
state
提供对当前状态的引用
let state: &MyState = self.handle.state();
reduce
允许您直接修改共享状态
// SharedHandle<UserState>
self.handle.reduce(move |user| *user = new_user);
reduce_callback
允许从回调中修改共享状态
// SharedHandle<usize>
let onclick = self.handle.reduce_callback(|state| *state += 1);
html! {
<button onclick=onclick>{"+1"}</button>
}
reduce_callback_with
还提供了触发的事件
// SharedHandle<UserState>
let oninput = self
.handle
.reduce_callback_with(|user, i: InputData| user.name = i.value);
html! {
<input type="text" placeholder="Enter your name" oninput=oninput />
}
reduce_callback_once
和 reduce_callback_once_with
也提供了 Callback::Once
变体。
关于 StateView 的更多信息
StateView
除了 view
以外还支持其他几个钩子,允许对组件行为进行更多控制:rendered
和 change
。
use yew::prelude::*;
use yew_state::{component, SharedHandle, StateView};
fn view_counter() -> Html {
type Handle = SharedHandle<usize>;
// Display counter button.
let view = component::view(|handle: &Handle| {
let onclick = handle.reduce_callback(|count| *count += 1);
html! {
<button onclick=onclick>{ handle.state() }</button>
}
});
// Magically set count to 1 for example.
let rendered = component::rendered(|handle: &Handle, first_render| {
if first_render {
handle.reduce(|count| *count = 1);
}
});
// Reset count to 0 if greater than 10.
let change = component::change(|old: &Handle, new: &Handle| -> ShouldRender {
if *new.state() > 10 {
new.reduce(|count| *count = 0);
}
old != new
});
html! {
<StateView<Handle, SCOPE> view=view rendered=rendered change=change />
}
}
共享状态属性
状态句柄提供了方便的 Properties
,但它们也可以用于您自己的属性。只需实现 SharedState
#[derive(Clone, Properties)]
pub struct Props {
#[prop_or_default]
handle: SharedHandle<AppState>,
}
impl SharedState for Props {
type Handle = SharedHandle<AppState>;
fn handle(&mut self) -> &mut Self::Handle {
&mut self.handle
}
}
待办:为 SharedState
添加 derive 宏
持久化
要使状态持久化,请使用 StorageHandle
。这要求您的状态还必须实现 Serialize
、Deserialize
和 Storable
。
use serde::{Serialize, Deserialize};
use yew_state::{Storable, Area};
#[derive(Clone, Default, Serialize, Deserialize)]
struct T;
impl Storable for T {
fn area() -> Area {
Area::Session // Default is Area::Local
}
}
现在,当刷新或用户离开时,您的状态将不会丢失。
待办:为 Storable
添加 derive 宏
作用域
默认情况下,所有组件使用相同的范围。组件只与其他具有相同作用域的组件共享状态;一个作用域中共享状态的更改不会影响不同作用域的组件。
要更改组件的作用域,只需给它一个不同的作用域类型
struct MyScope;
type MyComponent = SharedStateComponent<MyModel, MyScope>;
自定义状态处理器
为了自定义应用的状态管理,您可以定义自己的 StateHandler
。这类似于代理,并允许您的组件创建自己的消息传递桥梁(无需组件包装器和处理程序)。
有两个示例演示了如何使用状态处理器。在 examples/handler_bridge
中,显示了一个简单的桥梁,该桥梁直接与您的状态处理器通信。在 examples/service_bridge
中,它也与您的状态处理器以及其父状态服务通信。当您想要接收状态更新时,这很有用(基本上是组件包装器为您做的事情)。
示例
此示例演示了如何独立地递增具有不同作用域的两个计数器。
use yew::prelude::*;
use yew_state::{component, SharedHandle, StateView};
struct FooScope;
struct BarScope;
fn view_counter<SCOPE: 'static>() -> Html {
type Handle = SharedHandle<usize>;
let view = component::view(|handle: &Handle| {
let onclick = handle.reduce_callback(|count| *count += 1);
html! {
<button onclick=onclick>{ handle.state() }</button>
}
});
html! {
<StateView<Handle, SCOPE> view=view />
}
}
struct App;
impl Component for App {
type Message = ();
type Properties = ();
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<>
<h1>{"FooScope"}</h1>
{ view_counter::<FooScope>() }
<h1>{"BarScope"}</h1>
{ view_counter::<BarScope>() }
</>
}
}
}
技巧和窍门
性能
CoW 说 moo
我们使用写时复制模式来更改共享状态。这使得组件可以决定何时接收更改 Component::change
。如果您需要共享昂贵的克隆状态,请务必将其包装在 Rc
中!
拆分
将应用程序状态拆分有助于组件仅共享它们需要的部分。这样,组件就不会通知它们不关心的更改。例如,布局组件可能共享一个 LayoutState
,该状态可以在布局更改时更新,而不会影响您的其他组件。
请不要弄乱意大利面
为了保持理智,请尽量只从少数组件中修改共享状态。随着应用程序复杂性的增加,跟踪哪些组件正在更改状态变得越来越困难。
警惕无限渲染循环
考虑我们对快速入门示例的轻微修改
fn view_counter() -> Html {
type Handle = SharedHandle<u64>;
let view = component::view(|handle: &Handle| {
// Increment count by 1 right away.
// THIS WILL NEVER STOP COUNTING!
handle.reduce(|count| *count += 1);
// Increment count by 1.
let incr = handle.reduce_callback(|count| *count += 1);
// Reset count to 0.
let reset = handle.reduce_callback(|count| *count = 0);
html! {
<>
<button onclick=incr>{ handle.state() }</button>
<button onclick=reset>{"Reset"}</button>
</>
}
});
html! {
<StateView<Handle> view=view />
}
}
此代码可以编译,但只要 view_counter
被渲染,您的应用程序就会冻结,因为计数器无限递增自己。
上面的示例可以这样修复
if *handle.state() == 0 {
handle.reduce(|count| *count += 1);
}
这是一个简单的示例,但它可以以多种不同的方式发生。如果您的应用程序冻结,那么很可能是某个组件陷入渲染循环。
依赖关系
~3–4.5MB
~82K SLoC