44 个版本

3.0.0-alpha.15 2024 年 8 月 21 日
3.0.0-alpha.132024 年 7 月 29 日
3.0.0-alpha.102024 年 6 月 19 日
3.0.0-alpha.32024 年 2 月 20 日
1.0.0-alpha.182022 年 11 月 23 日

#173 in Rust 模式

Download history 8/week @ 2024-05-03 3/week @ 2024-05-17 1/week @ 2024-05-24 99/week @ 2024-05-31 162/week @ 2024-06-07 623/week @ 2024-06-14 77/week @ 2024-06-21 107/week @ 2024-06-28 41/week @ 2024-07-05 128/week @ 2024-07-19 222/week @ 2024-07-26 25/week @ 2024-08-02 178/week @ 2024-08-16

435 每月下载量
用于 hooks-yew

MIT 许可证

245KB
6K SLoC

::hooks

Crates.io docs.rs GitHub license GitHub stars

在安全 Rust 中的编译时、异步钩子。

快速入门

运行 cargo add hookshooks 添加到您的项目中。

注意,该项目仍在 alpha 阶段,可能会有重大更改。

在升级之前,请参阅 变更日志

您可以使用 hook_fn!(...)#[hook] 在钩子 fn 中组合钩子。

使用 hook_fn!(...)

use hooks::prelude::*;

hook_fn!(
    fn use_demo() {
        let (state, updater) = h![hooks::use_shared_set(0)];

        let updater = updater.clone();

        h![hooks::use_effect(move |v: &i32| {
            println!("state = {}", *v);

            if *v < 2 {
              updater.set(*v + 1);
            }
        }, *state)];
    }
);

futures_lite::future::block_on(async {
    let mut hook = use_demo().into_hook();
    while let Some(()) = hook.next_value().await {}
});

使用 #[hook]

此属性宏仅在 proc-macro 功能下可用。使用 cargo add -p hook --features proc-macro 启用它。 #[hook] 允许使用钩子而不需要 h!()。任何以 use_ 开头的函数调用或方法调用都会自动检测为钩子。

use hooks::prelude::*;

#[hook]
fn use_demo() {
    let (state, updater) = hooks::use_shared_set(0);

    let updater = updater.clone();

    hooks::use_effect(move |v: &i32| {
        println!("state = {}", *v);

        if *v < 2 {
          updater.set(*v + 1);
        }
    }, *state);
}

fn main() {
    futures_lite::future::block_on(async {
        let mut hook = use_demo().into_hook();
        while let Some(()) = hook.next_value().await {}
    });
}

您将看到以下日志。然后程序优雅地退出,因为它知道不会有新的值。

state = 0
state = 1
state = 2

什么是编译时的 Hook

为了理解这个crate背后的概念,您可以展开此部分并阅读详细内容。

React 16.8中引入的Hooks,是一种将状态引入函数组件的方法。Hooks可以使无状态的函数组件有状态和响应式。

传统的Hooks实现使用全局状态来记录hook调用及其状态。这样,无状态的函数可以通过运行时上下文来保持其状态。因此,hook调用的顺序不能改变;也禁止条件hook调用。开发者必须遵循Hooks规则来编写有效的自定义hook。yew.rs还向使用的hook传递hook上下文。我们可以看到上述实现依赖于hook fn的运行时行为。hook运行器必须运行该函数一次以知道初始化了什么。我们称这种hook为运行时Hooks

Rust语言拥有强大的静态类型系统。实际上,hook函数的状态是静态类型的。难题是如何使无状态的函数有状态,这意味着其状态也应由执行器知道。我们称这种hook实现为编译时Hooks

此crate为您定义和实现了编译时Hooks

当类型实现Hook时,它定义了三种行为

  1. 使用此hook时,它会输出什么?

    Hook::use_hook返回HookValue::Value

    此crate使用GAT (泛型关联类型)允许输出类型从hook本身借用。由于一些真实GAT的限制,此crate使用由Sabrina Jewson引入的更好的GAT模式。感谢她!

  2. 我们应该何时重用此hook?

    Hooks有状态。当状态不改变时,我们不需要重新调用use_hook来获取新的输出。我们可以等待hook的状态改变,使用HookPollNextUpdate::poll_next_update,或者简单地通过hook.next_update().await

    要等待状态改变后的下一个值,可以使用hook.next_value().await方法。

如何编写自定义hook?

请参阅Hook特质。

如何在hook函数中使用hooks?

使用宏 hook_fn!,你可以在顶级标记树(不是像()[]{}那样的标记树中)中使用h![use_another_hook(arg0, arg1)]。该宏将转换调用。

使用宏 #[hook],你可以在顶级表达式(不在像{}这样的内部块中)中调用use_another_hook(arg0, arg1)。该宏将转换调用。你可以查看此宏输出的快照

如何有条件地使用钩子?

请参阅 use_lazy_pinned_hookuse_uninitialized_hook

如何在不是钩子函数的情况下使用钩子

钩子函数实际上返回 impl UpdateHookUninitialized。要使用它,你可以运行 use_my_hook().into_hook() 将它转换为 Hook,或者运行 use_my_hook().into_hook_values()(它运行 use_my_hook().into_hook().into_values())来获取异步迭代值。

要消费 Hook,你可以使用其下一个值与 hook.next_value().await。你可以使用 hook.values()hook.into_values()(如果钩子是 NonLendingHook,则它是一个 Stream)来获取异步迭代值。

# use hooks::prelude::*;
hook_fn!(
    fn use_demo() -> i32 {
        let (state, updater) = h![use_shared_call(
            0,
            |v| {
                if *v < 2 {
                    *v += 1;
                    true // indicating state is updated
                } else {
                    false // indicating state is not updated
                }
            },
        )];

        let updater = updater.clone();
        h![hooks::use_effect(move |_: &i32| {
            updater.call();
        }, *state)];

        *state
    }
);

// with hook.next_value().await
futures_lite::future::block_on(async {
    let mut hook = use_demo().into_hook();
    assert_eq!(hook.next_value().await, Some(0));
    assert_eq!(hook.next_value().await, Some(1));
    assert_eq!(hook.next_value().await, Some(2));
    assert_eq!(hook.next_value().await, None);
});

// with hook.into_hook_values() and stream.next().await
futures_lite::future::block_on(async {
    use futures_lite::StreamExt;

    let mut values = use_demo().into_hook_values();
    assert_eq!(values.next().await, Some(0));
    assert_eq!(values.next().await, Some(1));
    assert_eq!(values.next().await, Some(2));
    assert_eq!(values.next().await, None);
});

// with hook.into_hook_values() and stream.collect().await
futures_lite::future::block_on(async {
    use futures_lite::StreamExt;

    let values = use_demo().into_hook_values();
    let values = values.collect::<Vec<_>>().await;
    assert_eq!(values, [0, 1, 2]);
});

依赖项

~115–315KB