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

Download history 13/week @ 2024-06-30 138/week @ 2024-07-28

每月 150 次下载
用于 yew-input

MIT 许可证

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 />
    }
}

用法

共享状态通过状态句柄(SharedHandleStorageHandle)访问,这些句柄由组件包装器 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_oncereduce_callback_once_with 也提供了 Callback::Once 变体。

关于 StateView 的更多信息

StateView 除了 view 以外还支持其他几个钩子,允许对组件行为进行更多控制:renderedchange

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。这要求您的状态还必须实现 SerializeDeserializeStorable

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