mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add use_callback hook (#2566)
* Add use_callback hook * Simply use_callback
This commit is contained in:
parent
ea8a530454
commit
421b4e1f60
@ -1,3 +1,4 @@
|
||||
mod use_callback;
|
||||
mod use_context;
|
||||
mod use_effect;
|
||||
mod use_memo;
|
||||
@ -5,6 +6,7 @@ mod use_reducer;
|
||||
mod use_ref;
|
||||
mod use_state;
|
||||
|
||||
pub use use_callback::*;
|
||||
pub use use_context::*;
|
||||
pub use use_effect::*;
|
||||
pub use use_memo::*;
|
||||
|
||||
75
packages/yew/src/functional/hooks/use_callback.rs
Normal file
75
packages/yew/src/functional/hooks/use_callback.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::callback::Callback;
|
||||
use crate::functional::{hook, use_memo};
|
||||
|
||||
/// Get a immutable reference to a memoized `Callback`.
|
||||
///
|
||||
/// Memoization means it will only get recreated when provided dependencies update/change.
|
||||
/// This is useful when passing callbacks to optimized child components that rely on
|
||||
/// PartialEq to prevent unnecessary renders.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use yew::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Properties, PartialEq)]
|
||||
/// pub struct Props {
|
||||
/// pub callback: Callback<String, String>,
|
||||
/// }
|
||||
///
|
||||
/// #[function_component(MyComponennt)]
|
||||
/// fn my_component(props: &Props) -> Html {
|
||||
/// let greeting = props.callback.emit("Yew".to_string());
|
||||
///
|
||||
/// html! {
|
||||
/// <>{ &greeting }</>
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[function_component(UseCallback)]
|
||||
/// fn callback() -> Html {
|
||||
/// let counter = use_state(|| 0);
|
||||
/// let onclick = {
|
||||
/// let counter = counter.clone();
|
||||
/// Callback::from(move |_| counter.set(*counter + 1))
|
||||
/// };
|
||||
///
|
||||
/// // This callback depends on (), so it's created only once, then MyComponennt
|
||||
/// // will be rendered only once even when you click the button mutiple times.
|
||||
/// let callback = use_callback(
|
||||
/// move |name| format!("Hello, {}!", name),
|
||||
/// ()
|
||||
/// );
|
||||
///
|
||||
/// // It can also be used for events.
|
||||
/// let oncallback = {
|
||||
/// let counter = counter.clone();
|
||||
/// use_callback(
|
||||
/// move |_e| (),
|
||||
/// counter
|
||||
/// )
|
||||
/// };
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <button {onclick}>{ "Increment value" }</button>
|
||||
/// <button onclick={oncallback}>{ "Callback" }</button>
|
||||
/// <p>
|
||||
/// <b>{ "Current value: " }</b>
|
||||
/// { *counter }
|
||||
/// </p>
|
||||
/// <MyComponennt {callback} />
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[hook]
|
||||
pub fn use_callback<IN, OUT, F, D>(f: F, deps: D) -> Callback<IN, OUT>
|
||||
where
|
||||
IN: 'static,
|
||||
OUT: 'static,
|
||||
F: Fn(IN) -> OUT + 'static,
|
||||
D: PartialEq + 'static,
|
||||
{
|
||||
(*use_memo(move |_| Callback::from(f), deps)).clone()
|
||||
}
|
||||
@ -3,9 +3,35 @@ use std::rc::Rc;
|
||||
|
||||
use crate::functional::{hook, use_state};
|
||||
|
||||
/// Get a immutable reference to a memoized value
|
||||
/// Get a immutable reference to a memoized value.
|
||||
///
|
||||
/// Memoization means it will only get recalculated when provided dependencies update/change
|
||||
/// Memoization means it will only get recalculated when provided dependencies update/change.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use yew::prelude::*;
|
||||
/// #
|
||||
/// #[derive(PartialEq, Properties)]
|
||||
/// pub struct Props {
|
||||
/// pub step: usize,
|
||||
/// }
|
||||
///
|
||||
/// #[function_component(UseMemo)]
|
||||
/// fn memo(props: &Props) -> Html {
|
||||
/// // Will only get recalculated if `props.step` value changes
|
||||
/// let message = use_memo(
|
||||
/// |step| format!("{}. Do Some Expensive Calculation", step),
|
||||
/// props.step
|
||||
/// );
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <span>{ (*message).clone() }</span>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[hook]
|
||||
pub fn use_memo<T, F, D>(f: F, deps: D) -> Rc<T>
|
||||
where
|
||||
|
||||
71
packages/yew/tests/use_callback.rs
Normal file
71
packages/yew/tests/use_callback.rs
Normal file
@ -0,0 +1,71 @@
|
||||
#![cfg(feature = "wasm_test")]
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
mod common;
|
||||
|
||||
use common::obtain_result;
|
||||
use gloo::timers::future::sleep;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen_test::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn use_callback_works() {
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct Props {
|
||||
callback: Callback<String, String>,
|
||||
}
|
||||
|
||||
#[function_component(MyComponennt)]
|
||||
fn my_component(props: &Props) -> Html {
|
||||
let greeting = props.callback.emit("Yew".to_string());
|
||||
|
||||
static CTR: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
if CTR.swap(true, Ordering::Relaxed) {
|
||||
panic!("multiple times rendered!");
|
||||
}
|
||||
|
||||
html! {
|
||||
<div>
|
||||
{"The test output is: "}
|
||||
<div id="result">{&greeting}</div>
|
||||
{"\n"}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(UseCallbackComponent)]
|
||||
fn use_callback_comp() -> Html {
|
||||
let state = use_state(|| 0);
|
||||
|
||||
let callback = use_callback(move |name| format!("Hello, {}!", name), ());
|
||||
|
||||
use_effect(move || {
|
||||
if *state < 5 {
|
||||
state.set(*state + 1);
|
||||
}
|
||||
|
||||
|| {}
|
||||
});
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<MyComponennt {callback} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
yew::Renderer::<UseCallbackComponent>::with_root(
|
||||
gloo_utils::document().get_element_by_id("output").unwrap(),
|
||||
)
|
||||
.render();
|
||||
|
||||
sleep(Duration::ZERO).await;
|
||||
|
||||
let result = obtain_result();
|
||||
assert_eq!(result.as_str(), "Hello, Yew!");
|
||||
}
|
||||
@ -28,6 +28,7 @@ Yew comes with the following predefined Hooks:
|
||||
- [`use_state`](./use-state.mdx)
|
||||
- [`use_state_eq`](./use-state.mdx#use_state_eq)
|
||||
- [`use_memo`](./use-memo.mdx)
|
||||
- [`use_callback`](./use-callback.mdx)
|
||||
- [`use_mut_ref`](./use-mut-ref.mdx)
|
||||
- [`use_node_ref`](./use-node-ref.mdx)
|
||||
- [`use_reducer`](./use-reducer.mdx)
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
---
|
||||
title: "use_callback"
|
||||
---
|
||||
|
||||
`use_callback` is used for obtaining an immutable reference to a memoized `Callback`.
|
||||
Its state persists across renders.
|
||||
It will be recreated only if any of the dependencies values change.
|
||||
|
||||
`use_callback` can be useful when passing callbacks to optimized child components that rely on
|
||||
PartialEq to prevent unnecessary renders.
|
||||
|
||||
```rust
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct Props {
|
||||
pub callback: Callback<String, String>,
|
||||
}
|
||||
|
||||
#[function_component(MyComponennt)]
|
||||
fn my_component(props: &Props) -> Html {
|
||||
let greeting = props.callback.emit("Yew".to_string());
|
||||
|
||||
html! {
|
||||
<>{ &greeting }</>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(UseCallback)]
|
||||
fn callback() -> Html {
|
||||
let counter = use_state(|| 0);
|
||||
let onclick = {
|
||||
let counter = counter.clone();
|
||||
Callback::from(move |_| counter.set(*counter + 1))
|
||||
};
|
||||
|
||||
// This callback depends on (), so it's created only once, then MyComponennt
|
||||
// will be rendered only once even when you click the button mutiple times.
|
||||
let callback = use_callback(
|
||||
move |name| format!("Hello, {}!", name),
|
||||
()
|
||||
);
|
||||
|
||||
// It can also be used for events.
|
||||
let oncallback = {
|
||||
let counter = counter.clone();
|
||||
use_callback(
|
||||
move |_e| (),
|
||||
counter
|
||||
)
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<button {onclick}>{ "Increment value" }</button>
|
||||
<button onclick={oncallback}>{ "Callback" }</button>
|
||||
<p>
|
||||
<b>{ "Current value: " }</b>
|
||||
{ *counter }
|
||||
</p>
|
||||
<MyComponennt {callback} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -13,5 +13,6 @@ This table can be used as a guide when deciding what state storing type fits bes
|
||||
| [use_reducer](./hooks/use-reducer) | got reduced | component instance |
|
||||
| [use_reducer_eq](./hooks/use-reducer#use_reducer_eq) | got reduced with diff. value | component instance |
|
||||
| [use_memo](./hooks/use-memo) | dependencies changed | component instance |
|
||||
| [use_callback](./hooks/use-callback) | dependencies changed | component instance |
|
||||
| [use_mut_ref](./hooks/use-mut-ref) | - | component instance |
|
||||
| a static global variable | - | global, used by all |
|
||||
|
||||
@ -62,6 +62,7 @@ module.exports = {
|
||||
"concepts/function-components/hooks/use-node-ref",
|
||||
"concepts/function-components/hooks/use-effect",
|
||||
"concepts/function-components/hooks/use-memo",
|
||||
"concepts/function-components/hooks/use-callback",
|
||||
"concepts/function-components/hooks/use-context",
|
||||
"concepts/function-components/hooks/custom-hooks",
|
||||
],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user