Add use_callback hook (#2566)

* Add use_callback hook

* Simply use_callback
This commit is contained in:
Jet Li 2022-04-01 18:10:27 +08:00 committed by GitHub
parent ea8a530454
commit 421b4e1f60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 2 deletions

View File

@ -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::*;

View 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()
}

View File

@ -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

View 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!");
}

View File

@ -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)

View File

@ -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>
}
}
```

View File

@ -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 |

View File

@ -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",
],