diff --git a/yew/src/html/scope.rs b/yew/src/html/scope.rs index 12296c0195f..db7964ebed1 100644 --- a/yew/src/html/scope.rs +++ b/yew/src/html/scope.rs @@ -120,13 +120,17 @@ impl Scope { node_ref: NodeRef, props: COMP::Properties, ) -> Scope { - *self.state.borrow_mut() = Some(ComponentState::new( - element, - ancestor, - node_ref, - self.clone(), - props, - )); + scheduler().push_comp( + ComponentRunnableType::Create, + Box::new(CreateComponent { + state: self.state.clone(), + element, + ancestor, + node_ref, + scope: self.clone(), + props, + }), + ); self.update(ComponentUpdate::Force, true); self } @@ -219,13 +223,16 @@ impl Scope { } } +type Dirty = bool; +const DIRTY: Dirty = true; + struct ComponentState { element: Element, node_ref: NodeRef, scope: Scope, component: Box, last_root: Option, - rendered: bool, + render_status: Option, } impl ComponentState { @@ -243,7 +250,37 @@ impl ComponentState { scope, component, last_root: ancestor, - rendered: false, + render_status: None, + } + } +} + +struct CreateComponent +where + COMP: Component, +{ + state: Shared>>, + element: Element, + ancestor: Option, + node_ref: NodeRef, + scope: Scope, + props: COMP::Properties, +} + +impl Runnable for CreateComponent +where + COMP: Component, +{ + fn run(self: Box) { + let mut current_state = self.state.borrow_mut(); + if current_state.is_none() { + *current_state = Some(ComponentState::new( + self.element, + self.ancestor, + self.node_ref, + self.scope, + self.props, + )); } } } @@ -277,7 +314,7 @@ where }; if should_update { - state.rendered = false; + state.render_status = state.render_status.map(|_| DIRTY); let mut root = state.component.render(); let last_root = state.last_root.take(); if let Some(node) = @@ -311,10 +348,16 @@ where { fn run(self: Box) { if let Some(mut state) = self.state.borrow_mut().as_mut() { - if !state.rendered { - state.rendered = true; - state.component.rendered(self.first_render); + if self.first_render && state.render_status.is_some() { + return; } + + if !self.first_render && state.render_status != Some(DIRTY) { + return; + } + + state.render_status = Some(!DIRTY); + state.component.rendered(self.first_render); } } } @@ -354,17 +397,22 @@ mod tests { #[derive(Clone, Properties)] struct Props { lifecycle: Rc>>, + create_message: Option, } + struct Comp { props: Props, } impl Component for Comp { - type Message = (); + type Message = bool; type Properties = Props; - fn create(props: Self::Properties, _: ComponentLink) -> Self { + fn create(props: Self::Properties, link: ComponentLink) -> Self { props.lifecycle.borrow_mut().push("create".into()); + if let Some(msg) = props.create_message { + link.send_message(msg); + } Comp { props } } @@ -375,9 +423,12 @@ mod tests { .push(format!("rendered({})", first_render)); } - fn update(&mut self, _: Self::Message) -> ShouldRender { - self.props.lifecycle.borrow_mut().push("update".into()); - false + fn update(&mut self, msg: Self::Message) -> ShouldRender { + self.props + .lifecycle + .borrow_mut() + .push(format!("update({})", msg)); + msg } fn change(&mut self, _: Self::Properties) -> ShouldRender { @@ -398,11 +449,12 @@ mod tests { } #[test] - fn text_mount_in_place() { + fn mount() { let document = crate::utils::document(); let lifecycle: Rc>> = Rc::default(); let props = Props { lifecycle: lifecycle.clone(), + create_message: None, }; let scope = Scope::::new(None); @@ -418,4 +470,53 @@ mod tests { ] ); } + + #[test] + fn mount_with_create_message() { + let document = crate::utils::document(); + let lifecycle: Rc>> = Rc::default(); + let props = Props { + lifecycle: lifecycle.clone(), + create_message: Some(false), + }; + + let scope = Scope::::new(None); + let el = document.create_element("div").unwrap(); + scope.mount_in_place(el, None, NodeRef::default(), props); + + assert_eq!( + lifecycle.borrow_mut().deref(), + &vec![ + "create".to_string(), + "update(false)".to_string(), + "view".to_string(), + "rendered(true)".to_string() + ] + ); + } + + #[test] + fn mount_with_create_render_message() { + let document = crate::utils::document(); + let lifecycle: Rc>> = Rc::default(); + let props = Props { + lifecycle: lifecycle.clone(), + create_message: Some(true), + }; + + let scope = Scope::::new(None); + let el = document.create_element("div").unwrap(); + scope.mount_in_place(el, None, NodeRef::default(), props); + + assert_eq!( + lifecycle.borrow_mut().deref(), + &vec![ + "create".to_string(), + "update(true)".to_string(), + "view".to_string(), + "view".to_string(), + "rendered(true)".to_string() + ] + ); + } } diff --git a/yew/src/scheduler.rs b/yew/src/scheduler.rs index d735f31cfaf..5e25310f5fb 100644 --- a/yew/src/scheduler.rs +++ b/yew/src/scheduler.rs @@ -31,6 +31,7 @@ pub(crate) struct Scheduler { pub(crate) enum ComponentRunnableType { Destroy, + Create, Update, Rendered, } @@ -39,6 +40,7 @@ pub(crate) enum ComponentRunnableType { struct ComponentScheduler { // Queues destroy: Shared>>, + create: Shared>>, update: Shared>>, // Stack @@ -49,6 +51,7 @@ impl ComponentScheduler { fn new() -> Self { ComponentScheduler { destroy: Rc::new(RefCell::new(VecDeque::new())), + create: Rc::new(RefCell::new(VecDeque::new())), update: Rc::new(RefCell::new(VecDeque::new())), rendered: Rc::new(RefCell::new(Vec::new())), } @@ -56,6 +59,7 @@ impl ComponentScheduler { fn next_runnable(&self) -> Option> { None.or_else(|| self.destroy.borrow_mut().pop_front()) + .or_else(|| self.create.borrow_mut().pop_front()) .or_else(|| self.update.borrow_mut().pop_front()) .or_else(|| self.rendered.borrow_mut().pop()) } @@ -75,6 +79,7 @@ impl Scheduler { ComponentRunnableType::Destroy => { self.component.destroy.borrow_mut().push_back(runnable) } + ComponentRunnableType::Create => self.component.create.borrow_mut().push_back(runnable), ComponentRunnableType::Update => self.component.update.borrow_mut().push_back(runnable), ComponentRunnableType::Rendered => self.component.rendered.borrow_mut().push(runnable), };