mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Replace mounted with rendered lifecycle method (#1072)
* Replace mounted with rendered lifecycle method * Cleanup
This commit is contained in:
parent
d799368894
commit
a91e7f6512
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user