Replace mounted with rendered lifecycle method (#1072)

* Replace mounted with rendered lifecycle method

* Cleanup
This commit is contained in:
Justin Starry 2020-04-25 17:00:20 +08:00 committed by GitHub
parent d799368894
commit a91e7f6512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 116 deletions

View File

@ -28,11 +28,12 @@ impl Component for Model {
}
}
fn mounted(&mut self) -> ShouldRender {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus().unwrap();
fn rendered(&mut self, first_render: bool) {
if first_render {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus().unwrap();
}
}
false
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {

View File

@ -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<HtmlCanvasElement>,
gl: Option<GL>,
@ -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 {

View File

@ -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! {
<UseEffectComponent destroy_called=props.destroy_called.clone() />
};
@ -196,11 +203,11 @@ fn use_effect_destroys_on_component_drop() {
}
let app: App<UseEffectWrapperComponent> = 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());

View File

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

View File

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

View File

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

View File

@ -29,11 +29,12 @@ impl Component for Model {
}
}
fn mounted(&mut self) -> ShouldRender {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus();
fn rendered(&mut self, first_render: bool) {
if first_render {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus();
}
}
false
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {

View File

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

View File

@ -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>) -> 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::<InputElement>() {
/// input.focus();
/// fn rendered(&mut self, first_render: bool) {
/// if first_render {
/// if let Some(input) = self.node_ref.cast::<InputElement>() {
/// input.focus();
/// }
/// }
/// false
/// }
///
/// fn change(&mut self, _: Self::Properties) -> ShouldRender {

View File

@ -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<COMP: Component> Scope<COMP> {
};
*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<COMP: Component> Scope<COMP> {
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<COMP: Component> Scope<COMP> {
T: Into<COMP::Message>,
{
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<COMP::Message>) {
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<COMP: Component> {
impl<COMP: Component> ReadyState<COMP> {
fn create(self) -> CreatedState<COMP> {
CreatedState {
rendered: false,
component: COMP::create(self.props, self.scope),
element: self.element,
last_frame: self.ancestor,
@ -205,6 +213,7 @@ impl<COMP: Component> ReadyState<COMP> {
}
struct CreatedState<COMP: Component> {
rendered: bool,
element: Element,
component: COMP,
last_frame: Option<VNode>,
@ -212,13 +221,11 @@ struct CreatedState<COMP: Component> {
}
impl<COMP: Component> CreatedState<COMP> {
/// 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<COMP: Component> CreatedState<COMP> {
}
}
struct MountedComponent<COMP>
struct RenderedComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
first_render: bool,
}
impl<COMP> Runnable for MountedComponent<COMP>
impl<COMP> Runnable for RenderedComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
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<Self>) {
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,

View File

@ -26,8 +26,43 @@ pub(crate) trait Runnable {
pub(crate) struct Scheduler {
lock: Rc<RefCell<()>>,
main: Shared<VecDeque<Box<dyn Runnable>>>,
create_component: Shared<VecDeque<Box<dyn Runnable>>>,
mount_component: Shared<Vec<Box<dyn Runnable>>>,
component: ComponentScheduler,
}
pub(crate) enum ComponentRunnableType {
Destroy,
Create,
Update,
Rendered,
}
#[derive(Clone)]
struct ComponentScheduler {
// Queues
destroy: Shared<VecDeque<Box<dyn Runnable>>>,
create: Shared<VecDeque<Box<dyn Runnable>>>,
update: Shared<VecDeque<Box<dyn Runnable>>>,
// Stack
rendered: Shared<Vec<Box<dyn Runnable>>>,
}
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<Box<dyn Runnable>> {
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_comp(&self, run_type: ComponentRunnableType, runnable: Box<dyn Runnable>) {
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(&self, runnable: Box<dyn Runnable>) {
self.main.borrow_mut().push_back(runnable);
self.start();
}
pub(crate) fn push_create(&self, runnable: Box<dyn Runnable>) {
self.create_component.borrow_mut().push_back(runnable);
self.start();
}
pub(crate) fn push_mount(&self, runnable: Box<dyn Runnable>) {
self.mount_component.borrow_mut().push(runnable);
self.start();
fn next_runnable(&self) -> Option<Box<dyn Runnable>> {
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;
}
}
}