Don't notify consumers when context hasn't changed

This commit is contained in:
Justin Starry 2020-07-01 20:42:48 +08:00 committed by Justin Starry
parent 018f04898f
commit bb6ca01e96
2 changed files with 25 additions and 16 deletions

View File

@ -11,18 +11,18 @@ use yew::{Children, Component, ComponentLink, Html, Properties};
type ConsumerCallback<T> = Box<dyn Fn(Rc<T>)>;
#[derive(Clone, PartialEq, Properties)]
pub struct ContextProviderProps<T: Clone> {
pub struct ContextProviderProps<T: Clone + PartialEq> {
pub context: T,
pub children: Children,
}
pub struct ContextProvider<T: Clone + 'static> {
pub struct ContextProvider<T: Clone + PartialEq + 'static> {
context: Rc<T>,
children: Children,
consumers: RefCell<Vec<Weak<ConsumerCallback<T>>>>,
}
impl<T: Clone> ContextProvider<T> {
impl<T: Clone + PartialEq> ContextProvider<T> {
/// Add the callback to the subscriber list to be called whenever the context changes.
/// The consumer is unsubscribed as soon as the callback is dropped.
fn subscribe_consumer(&self, mut callback: Weak<ConsumerCallback<T>>) {
@ -53,7 +53,7 @@ impl<T: Clone> ContextProvider<T> {
}
}
impl<T: Clone + 'static> Component for ContextProvider<T> {
impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
type Message = ();
type Properties = ContextProviderProps<T>;
@ -77,8 +77,11 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
false
};
self.context = Rc::new(props.context);
self.notify_consumers();
let new_context = Rc::new(props.context);
if self.context != new_context {
self.context = new_context;
self.notify_consumers();
}
should_render
}
@ -88,7 +91,7 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
}
}
fn find_context_provider_scope<T: 'static + Clone>(
fn find_context_provider_scope<T: Clone + PartialEq + 'static>(
scope: &AnyScope,
) -> Option<Scope<ContextProvider<T>>> {
let expected_type_id = TypeId::of::<ContextProvider<T>>();
@ -104,7 +107,7 @@ fn with_provider_component<T, F, R>(
f: F,
) -> Option<R>
where
T: Clone,
T: Clone + PartialEq,
F: FnOnce(&ContextProvider<T>) -> R,
{
provider_scope
@ -112,16 +115,16 @@ where
.and_then(|scope| scope.get_component().map(|comp| f(&*comp)))
}
pub fn use_context<T: 'static + Clone>() -> Option<Rc<T>> {
pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<Rc<T>> {
let scope = get_current_scope()
.expect("No current Scope. `use_context` can only be called inside functional components");
struct UseContextState<T2: 'static + Clone> {
struct UseContextState<T2: Clone + PartialEq + 'static> {
provider_scope: Option<Scope<ContextProvider<T2>>>,
current_context: Option<Rc<T2>>,
callback: Option<Rc<ConsumerCallback<T2>>>,
}
impl<T: 'static + Clone> Hook for UseContextState<T> {
impl<T: Clone + PartialEq + 'static> Hook for UseContextState<T> {
fn tear_down(&mut self) {
if let Some(cb) = self.callback.take() {
drop(cb);

View File

@ -21,7 +21,7 @@ fn obtain_result(id: &str) -> String {
#[wasm_bindgen_test]
fn use_context_scoping_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct ExampleContext(String);
struct UseContextFunctionOuter {}
struct UseContextFunctionInner {}
@ -178,7 +178,7 @@ fn use_context_works_with_multiple_types() {
#[wasm_bindgen_test]
fn use_context_update_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct MyContext(String);
#[derive(Clone, Debug, PartialEq, Properties)]
@ -186,6 +186,7 @@ fn use_context_update_works() {
id: String,
children: Children,
}
struct RenderCounterFunction;
impl FunctionProvider for RenderCounterFunction {
type TProps = RenderCounterProps;
@ -283,7 +284,12 @@ fn use_context_update_works() {
let app: App<TestComponent> = yew::App::new();
app.mount(yew::utils::document().get_element_by_id("output").unwrap());
assert_eq!(obtain_result("test-0"), "total: 1");
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 1");
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 1");
// 1 initial render + 3 update steps
assert_eq!(obtain_result("test-0"), "total: 4");
// 1 initial + 2 context update
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 3");
// 1 initial + 1 context update + 1 magic update + 1 context update
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 4");
}