Expand description
§::hooks
Compile-time, async hooks in safe Rust.
§Quick Start
Run cargo add hooks
to add hooks
to your project.
Note that this project is still in alpha, and it may have BREAKING CHANGES.
Please see changelogs before upgrading.
You can compose hooks in a hook fn with hook_fn!(...)
or #[hook]
§With 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 {}
});
§With #[hook]
This attribute macro is only available under proc-macro
feature.
Enable it with cargo add -p hook --features proc-macro
.
#[hook]
allows using hooks without h!()
.
Any function call or method call with fn name starting with use_
is automatically detected as a hook.
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 {}
});
}
You will see the following logs. Then the program exits gracefully because it knows there won’t be new values.
state = 0
state = 1
state = 2
§What is a compile-time Hook
?
To understand the concepts behind this crate, you can expand this section and read the details.
Hooks, introduced by React 16.8, is a way to bring state into functional components. Hooks can make stateless functional components stateful, and reactive.
Conventional hook implementations use a global state to record hook calls and their states.
This way, a stateless function can maintain its state through runtime contexts.
Thus, the order of hook calls must not change; conditional hook calls are also forbidden.
Developers must follow Rules of Hooks
to write a valid custom hook.
yew.rs
also passes
hook contexts
to used hooks.
We can see the above implementation relies on runtime behavior of a hook fn.
The hook runner must run the function once to know what is initialized.
We call this runtime hooks.
Rust language has powerful static type systems. In fact, the state of a hook function is statically typed. The hard problem is to make the stateless function stateful, which means its state should also be known by the executor. We call this kind of hook implementation as compile-time hooks.
This crate defines and implements compile-time hooks for you.
When a type implements Hook
, it defines three behaviors:
-
When using this hook, what does it output?
Hook::use_hook
returnsHookValue::Value
.This crate uses GAT (Generic Associated Types) to allow the output type borrowing from the hook itself. Due to some limitations of real GAT, this crate uses better GAT pattern introduced by Sabrina Jewson. Thanks to her!
-
When should we re-use this hook?
Hooks have states. When the state doesn’t change, we don’t need to re-call
use_hook
to get the new output. We can wait for the hook’s state to change withHookPollNextUpdate::poll_next_update
, or by justhook.next_update().await
.To wait for the next value when state changes, you can use
hook.next_value().await
method.
§How to write a custom hook?
Please see Hook
trait.
§How to use hooks in a hook function?
With hook_fn!
macro, you can just use h![use_another_hook(arg0, arg1)]
at
top level token trees (not wrapped in token trees like ()
, []
, or {}
).
The macro will transform the call.
With #[hook]
macro, you can just call use_another_hook(arg0, arg1)
at
top level expressions (not in an inner block like {}
).
The macro will transform the call.
You can see the snapshots for what this macro outputs.
§How to conditionally use hooks?
Please see use_lazy_pinned_hook
and use_uninitialized_hook
.
§How to use the hook when not in a hook fn
A hook fn actually returns impl UpdateHookUninitialized
.
To consume it, you can run use_my_hook().into_hook()
to turn it into a Hook
,
or run use_my_hook().into_hook_values()
(which runs use_my_hook().into_hook().into_values()
) to get async iterated values.
To consume a Hook
, you can use its next value with hook.next_value().await
.
You can get async iterated values with hook.values()
or hook.into_values()
,
which is a Stream
if the hook is NonLendingHook
.
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]);
});
Re-exports§
pub use effect::effect_fn;
pub use hooks_core as core;
pub use prelude::*;
Modules§
- debug
- effect
- hook_
mut - lazy_
pinned - lazy_
pinned_ hook - local_
key - memo
- pinned
- poll_
next_ update - prelude
- uninitialized_
hook
Macros§
- Hook
- Expands to an opaque type
impl Hook
with type ofValue
. - Update
Hook Uninitialized - Expands to an opaque type
impl UpdateHookUninitialized
with type ofHook
::
Value
. - hook_fn
- Write hook fn without any proc-macro.
- impl_
hook - Easily impl traits.
- proxy_
share_ value - proxy_
share_ value_ non_ eq - Proxy the methods that can’t be optimized by
PartialEq
. - proxy_
share_ value_ with_ provide
Structs§
- Call
- GenRef
Key - GenRef
Owner - GenSignal
- GenSignal is !Send + !Sync
- GenSignal
Hook - GenUpdate
State Key - GenUpdate
State Owner - Reducer
- A
Reducer
is aReduce
with a collection of actions. - Set
- Shared
Ref - Shared
RefUninitialized - Shared
Signal - Shared
Update State - Signal
Eq - This wrapper type also derives the following traits if
S: Unpin
. - State
With Updater - Toggle
- UseGen
Ref - UseGen
RefWith - UseGen
Signal - UseGen
Signal With - UseGen
Update State - UseGen
Update State With - UseShared
Ref - use
SharedRef
with initial value. - UseShared
RefWith - use
SharedRef
with anFnOnce
which returns the initial value. - UseShared
Signal - UseShared
Signal With - UseSignal
- use_
gen_ ref - use_
gen_ ref_ with - use_
gen_ signal - use_
gen_ signal_ with - use_
gen_ update_ state - use_
gen_ update_ state_ with - use_
shared_ ref - use
SharedRef
with initial value. - use_
shared_ ref_ with - use
SharedRef
with anFnOnce
which returns the initial value. - use_
shared_ signal - use_
shared_ signal_ with - use_
signal - use_
state_ with_ updater - use_
state_ with_ updater_ with
Traits§
- Hook
- Defines how to use a hook (get value from the hook).
- HookExt
- Extend
Hook
with convenient methods. - Hook
Poll Next Update - Defines reactivity of a hook.
- Hook
Poll Next Update Ext - Extend
HookPollNextUpdate
with convenient methods. - Hook
Unmount - Defines how to cleanup a hook.
- Hook
Value - A helper trait to define
lifetime generic associated types (lifetime-GAT)
for
Hook
. - IntoEq
- Into
Hook - Into
Update State Result - Reduce
- Share
Value - Common behaviors of types that share a value with inner mutability.
- Signal
- Signal
Hook for<'hook> sealed::HookValueImplSignal<'hook>
here acts likefor<'hook> HookValue<'hook, Value: Signal<SignalHook = Self, Value = Self::SignalShareValue>>
.- State
Updater - ToOwned
Share Value - ToOwned
Signal - Update
Hook - Update
Hook Uninitialized - Update
State
Functions§
- use_
gen_ call - Note that only the initial
f
will be called. - use_
gen_ call_ with - use_
gen_ reduce - use_
gen_ reduce_ with - use_
gen_ reducer - use_
gen_ reducer_ with - use_
gen_ set - use_
gen_ set_ with - use_
gen_ toggle - use_
gen_ toggle_ with - use_
shared_ call - Note that only the initial
f
will be called. - use_
shared_ call_ with - use_
shared_ reduce - use_
shared_ reduce_ with - use_
shared_ reducer - use_
shared_ reducer_ with - use_
shared_ set - use_
shared_ set_ with - use_
shared_ toggle - use_
shared_ toggle_ with - use_
shared_ update_ state - use_
shared_ update_ state_ with
Type Aliases§
- GenCall
Key - GenCall
Owner - GenReduce
Key - GenReduce
Owner - GenReducer
Key - GenReducer
Owner - GenSet
Key - GenSet
Owner - GenSignal
Eq - GenSignal
Hook Eq - GenToggle
Key - GenToggle
Owner - RefShared
Signal Eq - Shared
Call - Shared
Reduce - Shared
Reducer - Shared
Set - Shared
Signal Eq - Shared
Toggle - UseGen
Call - UseGen
Call With - UseGen
Reduce - UseGen
Reduce With - UseGen
Reducer - UseGen
Reducer With - UseGen
Set - UseGen
SetWith - UseGen
Toggle - UseGen
Toggle With - UseShared
Call - UseShared
Call With - UseShared
Reduce - UseShared
Reduce With - UseShared
Reducer - UseShared
Reducer With - UseShared
Set - UseShared
SetWith - UseShared
Toggle - UseShared
Toggle With - UseShared
Update State - UseShared
Update State With - Value
- Type alias for
HookValue::Value
.