From a91e7f651253382ac9abafb5ca2e0e0df669cd2c Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 25 Apr 2020 17:00:20 +0800 Subject: [PATCH] Replace mounted with rendered lifecycle method (#1072) * Replace mounted with rendered lifecycle method * Cleanup --- examples/node_refs/src/lib.rs | 9 ++- examples/webgl/src/lib.rs | 27 +++---- yew-functional/tests/lib.rs | 17 ++-- yew-router/examples/guide/src/guide.rs | 11 ++- .../examples/guide/src/markdown_window.rs | 4 - yew-router/src/router.rs | 3 +- yew-stdweb/examples/node_refs/src/lib.rs | 9 ++- yew-stdweb/examples/webgl/src/lib.rs | 24 +++--- yew/src/html/mod.rs | 27 ++++--- yew/src/html/scope.rs | 65 +++++++++------ yew/src/scheduler.rs | 80 +++++++++++++------ 11 files changed, 161 insertions(+), 115 deletions(-) diff --git a/examples/node_refs/src/lib.rs b/examples/node_refs/src/lib.rs index f39c27419d9..af8ea219954 100644 --- a/examples/node_refs/src/lib.rs +++ b/examples/node_refs/src/lib.rs @@ -28,11 +28,12 @@ impl Component for Model { } } - fn mounted(&mut self) -> ShouldRender { - if let Some(input) = self.refs[self.focus_index].cast::() { - input.focus().unwrap(); + fn rendered(&mut self, first_render: bool) { + if first_render { + if let Some(input) = self.refs[self.focus_index].cast::() { + input.focus().unwrap(); + } } - false } fn update(&mut self, msg: Self::Message) -> ShouldRender { diff --git a/examples/webgl/src/lib.rs b/examples/webgl/src/lib.rs index cf55ca5b338..4577ffe6bdc 100644 --- a/examples/webgl/src/lib.rs +++ b/examples/webgl/src/lib.rs @@ -1,10 +1,9 @@ +use wasm_bindgen::JsCast; use web_sys::HtmlCanvasElement; use web_sys::WebGlRenderingContext as GL; use yew::services::{RenderService, Task}; use yew::{html, Component, ComponentLink, Html, NodeRef, ShouldRender}; -use wasm_bindgen::JsCast; - pub struct Model { canvas: Option, gl: Option, @@ -31,8 +30,8 @@ impl Component for Model { } } - fn mounted(&mut self) -> ShouldRender { - // Once mounted, store references for the canvas and GL context. These can be used for + fn rendered(&mut self, first_render: bool) { + // Once rendered, store references for the canvas and GL context. These can be used for // resizing the rendering area when the window or canvas element are resized, as well as // for making GL calls. @@ -52,18 +51,16 @@ impl Component for Model { // done here, such as enabling or disabling depth testing, depth functions, face // culling etc. - // The callback to request animation frame is passed a time value which can be used for - // rendering motion independent of the framerate which may vary. - let render_frame = self.link.callback(Msg::Render); - let handle = RenderService::new().request_animation_frame(render_frame); + if first_render { + // The callback to request animation frame is passed a time value which can be used for + // rendering motion independent of the framerate which may vary. + let render_frame = self.link.callback(Msg::Render); + let handle = RenderService::new().request_animation_frame(render_frame); - // A reference to the handle must be stored, otherwise it is dropped and the render won't - // occur. - self.render_loop = Some(Box::new(handle)); - - // Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to - // render the DOM element(s) again. - false + // A reference to the handle must be stored, otherwise it is dropped and the render won't + // occur. + self.render_loop = Some(Box::new(handle)); + } } fn update(&mut self, msg: Self::Message) -> ShouldRender { diff --git a/yew-functional/tests/lib.rs b/yew-functional/tests/lib.rs index d8153e20122..a9e62e8d482 100644 --- a/yew-functional/tests/lib.rs +++ b/yew-functional/tests/lib.rs @@ -181,9 +181,16 @@ fn use_effect_destroys_on_component_drop() { type TProps = DestroyCalledProps; fn run(props: &Self::TProps) -> Html { - let (should_rerender, set_rerender) = use_state(|| true); - if *should_rerender { - set_rerender(false); + let (show, set_show) = use_state(|| true); + use_effect_with_deps( + move |_| { + set_show(false); + || {} + }, + (), + ); + + if *show { return html! { }; @@ -196,11 +203,11 @@ fn use_effect_destroys_on_component_drop() { } let app: App = yew::App::new(); let destroy_counter = Rc::new(std::cell::RefCell::new(0)); - let destroy_country_c = destroy_counter.clone(); + let destroy_counter_c = destroy_counter.clone(); app.mount_with_props( yew::utils::document().get_element_by_id("output").unwrap(), DestroyCalledProps { - destroy_called: Rc::new(move || *destroy_country_c.borrow_mut().deref_mut() += 1), + destroy_called: Rc::new(move || *destroy_counter_c.borrow_mut().deref_mut() += 1), }, ); assert_eq!(1, *destroy_counter.borrow().deref()); diff --git a/yew-router/examples/guide/src/guide.rs b/yew-router/examples/guide/src/guide.rs index 193176d73a9..9545790fb77 100644 --- a/yew-router/examples/guide/src/guide.rs +++ b/yew-router/examples/guide/src/guide.rs @@ -34,18 +34,21 @@ impl Component for Guide { } } - fn mounted(&mut self) -> ShouldRender { + fn rendered(&mut self, _first_render: bool) { self.router_agent.send(GetCurrentRoute); - false } fn update(&mut self, msg: Self::Message) -> bool { match msg { Msg::UpdateRoute(route) => { - self.route = Some(route); + let new_route = Some(route); + if self.route != new_route { + self.route = new_route; + return true; + } } } - true + false } fn change(&mut self, _: Self::Properties) -> bool { diff --git a/yew-router/examples/guide/src/markdown_window.rs b/yew-router/examples/guide/src/markdown_window.rs index 16132bf0c37..337c76fcda4 100644 --- a/yew-router/examples/guide/src/markdown_window.rs +++ b/yew-router/examples/guide/src/markdown_window.rs @@ -41,10 +41,6 @@ impl Component for MarkdownWindow { } } - fn mounted(&mut self) -> ShouldRender { - false - } - fn update(&mut self, msg: Self::Message) -> bool { match msg { Msg::MarkdownArrived(md) => { diff --git a/yew-router/src/router.rs b/yew-router/src/router.rs index 4e0a349986e..169d9069ae7 100644 --- a/yew-router/src/router.rs +++ b/yew-router/src/router.rs @@ -185,9 +185,8 @@ where } } - fn mounted(&mut self) -> ShouldRender { + fn rendered(&mut self, _first_render: bool) { self.router_agent.send(RouteRequest::GetCurrentRoute); - false } fn update(&mut self, msg: Self::Message) -> ShouldRender { diff --git a/yew-stdweb/examples/node_refs/src/lib.rs b/yew-stdweb/examples/node_refs/src/lib.rs index 06a3f656f70..d944b5c9f8d 100644 --- a/yew-stdweb/examples/node_refs/src/lib.rs +++ b/yew-stdweb/examples/node_refs/src/lib.rs @@ -29,11 +29,12 @@ impl Component for Model { } } - fn mounted(&mut self) -> ShouldRender { - if let Some(input) = self.refs[self.focus_index].cast::() { - input.focus(); + fn rendered(&mut self, first_render: bool) { + if first_render { + if let Some(input) = self.refs[self.focus_index].cast::() { + input.focus(); + } } - false } fn update(&mut self, msg: Self::Message) -> ShouldRender { diff --git a/yew-stdweb/examples/webgl/src/lib.rs b/yew-stdweb/examples/webgl/src/lib.rs index 3bda1091276..59bce916004 100644 --- a/yew-stdweb/examples/webgl/src/lib.rs +++ b/yew-stdweb/examples/webgl/src/lib.rs @@ -33,8 +33,8 @@ impl Component for Model { } } - fn mounted(&mut self) -> ShouldRender { - // Once mounted, store references for the canvas and GL context. These can be used for + fn rendered(&mut self, first_render: bool) { + // Once rendered, store references for the canvas and GL context. These can be used for // resizing the rendering area when the window or canvas element are resized, as well as // for making GL calls. let c: CanvasElement = self.node_ref.cast().unwrap(); @@ -47,18 +47,16 @@ impl Component for Model { // done here, such as enabling or disabling depth testing, depth functions, face // culling etc. - // The callback to request animation frame is passed a time value which can be used for - // rendering motion independent of the framerate which may vary. - let render_frame = self.link.callback(Msg::Render); - let handle = RenderService::new().request_animation_frame(render_frame); - - // A reference to the handle must be stored, otherwise it is dropped and the render won't - // occur. - self.render_loop = Some(Box::new(handle)); + if first_render { + // The callback to request animation frame is passed a time value which can be used for + // rendering motion independent of the framerate which may vary. + let render_frame = self.link.callback(Msg::Render); + let handle = RenderService::new().request_animation_frame(render_frame); - // Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to - // render the DOM element(s) again. - false + // A reference to the handle must be stored, otherwise it is dropped and the render won't + // occur. + self.render_loop = Some(Box::new(handle)); + } } fn update(&mut self, msg: Self::Message) -> ShouldRender { diff --git a/yew/src/html/mod.rs b/yew/src/html/mod.rs index 9252a77574d..8eed9030484 100644 --- a/yew/src/html/mod.rs +++ b/yew/src/html/mod.rs @@ -67,14 +67,9 @@ pub trait Component: Sized + 'static { /// Components are created with their properties as well as a `ComponentLink` which /// can be used to send messages and create callbacks for triggering updates. fn create(props: Self::Properties, link: ComponentLink) -> Self; - /// Called after the component has been attached to the VDOM and it is safe to receive messages - /// from agents but before the browser updates the screen. If true is returned, the view will - /// be re-rendered and the user will not see the initial render. - fn mounted(&mut self) -> ShouldRender { - false - } - /// Called everytime when a messages of `Msg` type received. It also takes a - /// reference to a context. + + /// Components handle messages in their `update` method and commonly use this method + /// to update their state and (optionally) re-render themselves. fn update(&mut self, msg: Self::Message) -> ShouldRender; /// When the parent of a Component is re-rendered, it will either be re-created or @@ -107,7 +102,12 @@ pub trait Component: Sized + 'static { /// `html!` procedural macro. The full guide to using the macro can be found in [Yew's /// documentation](https://yew.rs/docs/concepts/html). fn view(&self) -> Html; - /// Called for finalization on the final point of the component's lifetime. + + /// The `rendered` method is called after each time a Component is rendered but + /// before the browser updates the page. + fn rendered(&mut self, _first_render: bool) {} + + /// The `destroy` method is called right before a Component is unmounted. fn destroy(&mut self) {} // TODO(#941): Replace with `Drop` } @@ -359,11 +359,12 @@ where /// } /// } /// -/// fn mounted(&mut self) -> ShouldRender { -/// if let Some(input) = self.node_ref.cast::() { -/// input.focus(); +/// fn rendered(&mut self, first_render: bool) { +/// if first_render { +/// if let Some(input) = self.node_ref.cast::() { +/// input.focus(); +/// } /// } -/// false /// } /// /// fn change(&mut self, _: Self::Properties) -> ShouldRender { diff --git a/yew/src/html/scope.rs b/yew/src/html/scope.rs index 3b57d1c33b7..f286249d268 100644 --- a/yew/src/html/scope.rs +++ b/yew/src/html/scope.rs @@ -1,5 +1,5 @@ use super::*; -use crate::scheduler::{scheduler, Runnable, Shared}; +use crate::scheduler::{scheduler, ComponentRunnableType, Runnable, Shared}; use crate::virtual_dom::{VDiff, VNode}; use cfg_if::cfg_if; use std::cell::RefCell; @@ -73,22 +73,15 @@ impl Scope { }; *scope.shared_state.borrow_mut() = ComponentState::Ready(ready_state); scope.create(); - scope.mounted(); scope } - /// Schedules a task to call the mounted method on a component and optionally re-render - pub(crate) fn mounted(&mut self) { - let shared_state = self.shared_state.clone(); - let mounted = MountedComponent { shared_state }; - scheduler().push_mount(Box::new(mounted)); - } - /// Schedules a task to create and render a component and then mount it to the DOM pub(crate) fn create(&mut self) { let shared_state = self.shared_state.clone(); let create = CreateComponent { shared_state }; - scheduler().push_create(Box::new(create)); + scheduler().push_comp(ComponentRunnableType::Create, Box::new(create)); + self.rendered(true); } /// Schedules a task to send a message or new props to a component @@ -97,14 +90,25 @@ impl Scope { shared_state: self.shared_state.clone(), update, }; - scheduler().push(Box::new(update)); + scheduler().push_comp(ComponentRunnableType::Update, Box::new(update)); + self.rendered(false); + } + + /// Schedules a task to call the rendered method on a component + pub(crate) fn rendered(&self, first_render: bool) { + let shared_state = self.shared_state.clone(); + let rendered = RenderedComponent { + shared_state, + first_render, + }; + scheduler().push_comp(ComponentRunnableType::Rendered, Box::new(rendered)); } /// Schedules a task to destroy a component pub(crate) fn destroy(&mut self) { let shared_state = self.shared_state.clone(); let destroy = DestroyComponent { shared_state }; - scheduler().push(Box::new(destroy)); + scheduler().push_comp(ComponentRunnableType::Destroy, Box::new(destroy)); } /// Send a message to the component @@ -113,10 +117,13 @@ impl Scope { T: Into, { self.update(ComponentUpdate::Message(msg.into())); + self.rendered(false); } + /// Send a batch of messages to the component pub fn send_message_batch(&self, messages: Vec) { self.update(ComponentUpdate::MessageBatch(messages)); + self.rendered(false); } /// Creates a `Callback` which will send a message to the linked component's @@ -196,6 +203,7 @@ struct ReadyState { impl ReadyState { fn create(self) -> CreatedState { CreatedState { + rendered: false, component: COMP::create(self.props, self.scope), element: self.element, last_frame: self.ancestor, @@ -205,6 +213,7 @@ impl ReadyState { } struct CreatedState { + rendered: bool, element: Element, component: COMP, last_frame: Option, @@ -212,13 +221,11 @@ struct CreatedState { } impl CreatedState { - /// Called once immediately after the component is created. - fn mounted(mut self) -> Self { - if self.component.mounted() { - self.update() - } else { - self - } + /// Called after a component and all of its children have been rendered. + fn rendered(mut self, first_render: bool) -> Self { + self.rendered = true; + self.component.rendered(first_render); + self } fn update(mut self) -> Self { @@ -237,22 +244,25 @@ impl CreatedState { } } -struct MountedComponent +struct RenderedComponent where COMP: Component, { shared_state: Shared>, + first_render: bool, } -impl Runnable for MountedComponent +impl Runnable for RenderedComponent where COMP: Component, { fn run(self: Box) { let current_state = self.shared_state.replace(ComponentState::Processing); self.shared_state.replace(match current_state { - ComponentState::Created(state) => ComponentState::Created(state.mounted()), - ComponentState::Destroyed => current_state, + ComponentState::Created(s) if !s.rendered => { + ComponentState::Created(s.rendered(self.first_render)) + } + ComponentState::Destroyed | ComponentState::Created(_) => current_state, ComponentState::Empty | ComponentState::Processing | ComponentState::Ready(_) => { panic!("unexpected component state: {}", current_state); } @@ -274,7 +284,7 @@ where fn run(self: Box) { let current_state = self.shared_state.replace(ComponentState::Processing); self.shared_state.replace(match current_state { - ComponentState::Ready(state) => ComponentState::Created(state.create().update()), + ComponentState::Ready(s) => ComponentState::Created(s.create().update()), ComponentState::Created(_) | ComponentState::Destroyed => current_state, ComponentState::Empty | ComponentState::Processing => { panic!("unexpected component state: {}", current_state); @@ -341,7 +351,12 @@ where this.component.change(props) } }; - let next_state = if should_update { this.update() } else { this }; + let next_state = if should_update { + this.rendered = false; + this.update() + } else { + this + }; ComponentState::Created(next_state) } ComponentState::Destroyed => current_state, diff --git a/yew/src/scheduler.rs b/yew/src/scheduler.rs index 779ddd65b45..5e25310f5fb 100644 --- a/yew/src/scheduler.rs +++ b/yew/src/scheduler.rs @@ -26,8 +26,43 @@ pub(crate) trait Runnable { pub(crate) struct Scheduler { lock: Rc>, main: Shared>>, - create_component: Shared>>, - mount_component: Shared>>, + component: ComponentScheduler, +} + +pub(crate) enum ComponentRunnableType { + Destroy, + Create, + Update, + Rendered, +} + +#[derive(Clone)] +struct ComponentScheduler { + // Queues + destroy: Shared>>, + create: Shared>>, + update: Shared>>, + + // Stack + rendered: Shared>>, +} + +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())), + } + } + + 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()) + } } impl Scheduler { @@ -35,43 +70,36 @@ impl Scheduler { Scheduler { lock: Rc::new(RefCell::new(())), main: Rc::new(RefCell::new(VecDeque::new())), - create_component: Rc::new(RefCell::new(VecDeque::new())), - mount_component: Rc::new(RefCell::new(Vec::new())), + component: ComponentScheduler::new(), } } - pub(crate) fn push(&self, runnable: Box) { - self.main.borrow_mut().push_back(runnable); + pub(crate) fn push_comp(&self, run_type: ComponentRunnableType, runnable: Box) { + match run_type { + 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), + }; self.start(); } - pub(crate) fn push_create(&self, runnable: Box) { - self.create_component.borrow_mut().push_back(runnable); + pub(crate) fn push(&self, runnable: Box) { + self.main.borrow_mut().push_back(runnable); self.start(); } - pub(crate) fn push_mount(&self, runnable: Box) { - self.mount_component.borrow_mut().push(runnable); - self.start(); + fn next_runnable(&self) -> Option> { + None.or_else(|| self.component.next_runnable()) + .or_else(|| self.main.borrow_mut().pop_front()) } pub(crate) fn start(&self) { - let lock = self.lock.try_borrow_mut(); - if lock.is_err() { - return; - } - - loop { - let do_next = self - .create_component - .borrow_mut() - .pop_front() - .or_else(|| self.mount_component.borrow_mut().pop()) - .or_else(|| self.main.borrow_mut().pop_front()); - if let Some(runnable) = do_next { + if let Ok(_lock) = self.lock.try_borrow_mut() { + while let Some(runnable) = self.next_runnable() { runnable.run(); - } else { - break; } } }