From 8ea2cb6bb9feee26e75c87f5cb9d160a01ebd5db Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 20:23:54 +0900 Subject: [PATCH 01/47] Allow functions to be pushed to scheduler. --- packages/yew/src/html/component/lifecycle.rs | 99 ++++++++-------- packages/yew/src/html/component/scope.rs | 96 +++++++++------- packages/yew/src/scheduler.rs | 112 ++++++++++--------- 3 files changed, 164 insertions(+), 143 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index cd2acc126b5..77453e6cf48 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -17,7 +17,7 @@ use crate::html::NodeRef; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::html::{Html, RenderError}; -use crate::scheduler::{self, Runnable, Shared}; +use crate::scheduler::{self, Shared}; use crate::suspense::{BaseSuspense, Suspension}; use crate::{Callback, Context, HtmlResult}; @@ -316,8 +316,8 @@ pub(crate) struct CreateRunner { pub prepared_state: Option, } -impl Runnable for CreateRunner { - fn run(self: Box) { +impl CreateRunner { + pub fn run(self) { let mut current_state = self.scope.state.borrow_mut(); if current_state.is_none() { *current_state = Some(ComponentState::new( @@ -348,18 +348,17 @@ impl ComponentState { } } -impl Runnable for UpdateRunner { - fn run(self: Box) { +impl UpdateRunner { + pub fn run(self) { if let Some(state) = self.state.borrow_mut().as_mut() { let schedule_render = state.update(); if schedule_render { - scheduler::push_component_render( - state.comp_id, - Box::new(RenderRunner { - state: self.state.clone(), - }), - ); + let render = RenderRunner { + state: self.state.clone(), + }; + + scheduler::push_component_render(state.comp_id, move || render.run()); // Only run from the scheduler, so no need to call `scheduler::start()` } } @@ -416,8 +415,8 @@ impl ComponentState { } } -impl Runnable for DestroyRunner { - fn run(self: Box) { +impl DestroyRunner { + pub fn run(self) { if let Some(state) = self.state.borrow_mut().take() { state.destroy(self.parent_to_detach); } @@ -446,13 +445,12 @@ impl ComponentState { // suspension to parent element. if suspension.resumed() { + let runner = RenderRunner { + state: shared_state.clone(), + }; + // schedule a render immediately if suspension is resumed. - scheduler::push_component_render( - self.comp_id, - Box::new(RenderRunner { - state: shared_state.clone(), - }), - ); + scheduler::push_component_render(self.comp_id, move || runner.run()); } else { // We schedule a render after current suspension is resumed. let comp_scope = self.inner.any_scope(); @@ -464,12 +462,11 @@ impl ComponentState { let comp_id = self.comp_id; let shared_state = shared_state.clone(); suspension.listen(Callback::from(move |_| { - scheduler::push_component_render( - comp_id, - Box::new(RenderRunner { - state: shared_state.clone(), - }), - ); + let runner = RenderRunner { + state: shared_state.clone(), + }; + + scheduler::push_component_render(comp_id, move || runner.run()); scheduler::start(); })); @@ -512,12 +509,14 @@ impl ComponentState { let first_render = !self.has_rendered; self.has_rendered = true; + let runner = RenderedRunner { + state: shared_state.clone(), + first_render, + }; + scheduler::push_component_rendered( self.comp_id, - Box::new(RenderedRunner { - state: shared_state.clone(), - first_render, - }), + move || runner.run(), first_render, ); } @@ -530,14 +529,13 @@ impl ComponentState { ref next_sibling, ref root, } => { + let runner = RenderRunner { + state: shared_state.clone(), + }; + // We schedule a "first" render to run immediately after hydration, // to fix NodeRefs (first_node and next_sibling). - scheduler::push_component_priority_render( - self.comp_id, - Box::new(RenderRunner { - state: shared_state.clone(), - }), - ); + scheduler::push_component_priority_render(self.comp_id, move || runner.run()); let scope = self.inner.any_scope(); @@ -573,8 +571,8 @@ impl ComponentState { } } -impl Runnable for RenderRunner { - fn run(self: Box) { +impl RenderRunner { + pub fn run(self) { let mut state = self.state.borrow_mut(); let state = match state.as_mut() { None => return, // skip for components that have already been destroyed @@ -678,24 +676,23 @@ mod feat_csr { } } - impl Runnable for PropsUpdateRunner { - fn run(self: Box) { + impl PropsUpdateRunner { + pub fn run(self) { let Self { next_sibling, props, state: shared_state, - } = *self; + } = self; if let Some(state) = shared_state.borrow_mut().as_mut() { let schedule_render = state.changed(props, next_sibling); + let runner = RenderRunner { + state: shared_state.clone(), + }; + if schedule_render { - scheduler::push_component_render( - state.comp_id, - Box::new(RenderRunner { - state: shared_state.clone(), - }), - ); + scheduler::push_component_render(state.comp_id, move || runner.run()); // Only run from the scheduler, so no need to call `scheduler::start()` } }; @@ -729,17 +726,19 @@ mod feat_csr { } } - impl Runnable for RenderedRunner { - fn run(self: Box) { + impl RenderedRunner { + pub fn run(self) { if let Some(state) = self.state.borrow_mut().as_mut() { let has_pending_props = state.rendered(self.first_render); if has_pending_props { - scheduler::push_component_props_update(Box::new(PropsUpdateRunner { + let runner = PropsUpdateRunner { state: self.state.clone(), props: None, next_sibling: None, - })); + }; + + scheduler::push_component_props_update(move || runner.run()); } } } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index bca6f17e2b8..8d050942d2b 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -316,18 +316,22 @@ mod feat_ssr { let (tx, rx) = oneshot::channel(); let state = ComponentRenderState::Ssr { sender: Some(tx) }; + let create_runner = CreateRunner { + initial_render_state: state, + props, + scope: self.clone(), + #[cfg(feature = "hydration")] + prepared_state: None, + }; + + let first_render_runner = RenderRunner { + state: self.state.clone(), + }; + scheduler::push_component_create( self.id, - Box::new(CreateRunner { - initial_render_state: state, - props, - scope: self.clone(), - #[cfg(feature = "hydration")] - prepared_state: None, - }), - Box::new(RenderRunner { - state: self.state.clone(), - }), + move || create_runner.run(), + move || first_render_runner.run(), ); scheduler::start(); @@ -353,10 +357,12 @@ mod feat_ssr { collectable.write_close_tag(w); } - scheduler::push_component_destroy(Box::new(DestroyRunner { + let runner = DestroyRunner { state: self.state.clone(), parent_to_detach: false, - })); + }; + + scheduler::push_component_destroy(move || runner.run()); scheduler::start(); } } @@ -480,9 +486,11 @@ mod feat_csr_ssr { #[inline] fn schedule_update(&self) { - scheduler::push_component_update(Box::new(UpdateRunner { + let runner = UpdateRunner { state: self.state.clone(), - })); + }; + + scheduler::push_component_update(move || runner.run()); // Not guaranteed to already have the scheduler started scheduler::start(); } @@ -543,11 +551,13 @@ mod feat_csr { props: Rc, next_sibling: NodeRef, ) { - scheduler::push_component_props_update(Box::new(PropsUpdateRunner { + let runner = PropsUpdateRunner { state, - next_sibling: Some(next_sibling), props: Some(props), - })); + next_sibling: Some(next_sibling), + }; + + scheduler::push_component_props_update(move || runner.run()); // Not guaranteed to already have the scheduler started scheduler::start(); } @@ -577,18 +587,22 @@ mod feat_csr { next_sibling: stable_next_sibling, }; + let create_runner = CreateRunner { + initial_render_state: state, + props, + scope: self.clone(), + #[cfg(feature = "hydration")] + prepared_state: None, + }; + + let first_render_runner = RenderRunner { + state: self.state.clone(), + }; + scheduler::push_component_create( self.id, - Box::new(CreateRunner { - initial_render_state: state, - props, - scope: self.clone(), - #[cfg(feature = "hydration")] - prepared_state: None, - }), - Box::new(RenderRunner { - state: self.state.clone(), - }), + move || create_runner.run(), + move || first_render_runner.run(), ); // Not guaranteed to already have the scheduler started scheduler::start(); @@ -628,10 +642,12 @@ mod feat_csr { /// Process an event to destroy a component fn destroy(self, parent_to_detach: bool) { - scheduler::push_component_destroy(Box::new(DestroyRunner { + let runner = DestroyRunner { state: self.state, parent_to_detach, - })); + }; + + scheduler::push_component_destroy(move || runner.run()); // Not guaranteed to already have the scheduler started scheduler::start(); } @@ -725,17 +741,21 @@ mod feat_hydration { fragment, }; + let create_runner = CreateRunner { + initial_render_state: state, + props, + scope: self.clone(), + prepared_state, + }; + + let first_render_runner = RenderRunner { + state: self.state.clone(), + }; + scheduler::push_component_create( self.id, - Box::new(CreateRunner { - initial_render_state: state, - props, - scope: self.clone(), - prepared_state, - }), - Box::new(RenderRunner { - state: self.state.clone(), - }), + move || create_runner.run(), + move || first_render_runner.run(), ); // Not guaranteed to already have the scheduler started diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index c7f37dbca09..5cf177166be 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -7,53 +7,45 @@ use std::rc::Rc; /// Alias for `Rc>` pub type Shared = Rc>; -/// A routine which could be run. -pub trait Runnable { - /// Runs a routine with a context instance. - fn run(self: Box); -} - -struct QueueEntry { - task: Box, -} +type Runnable = Box; #[derive(Default)] struct FifoQueue { - inner: Vec, + inner: Vec, } impl FifoQueue { - fn push(&mut self, task: Box) { - self.inner.push(QueueEntry { task }); + #[inline(always)] + fn push(&mut self, task: Runnable) { + self.inner.push(task); } - fn drain_into(&mut self, queue: &mut Vec) { + fn drain_into(&mut self, queue: &mut Vec) { queue.append(&mut self.inner); } } #[derive(Default)] - struct TopologicalQueue { /// The Binary Tree Map guarantees components with lower id (parent) is rendered first - inner: BTreeMap, + inner: BTreeMap, } impl TopologicalQueue { #[cfg(any(feature = "ssr", feature = "csr"))] - fn push(&mut self, component_id: usize, task: Box) { - self.inner.insert(component_id, QueueEntry { task }); + fn push(&mut self, component_id: usize, task: Runnable) { + self.inner.insert(component_id, task); } /// Take a single entry, preferring parents over children - fn pop_topmost(&mut self) -> Option { + fn pop_topmost(&mut self) -> Option { // To be replaced with BTreeMap::pop_first once it is stable. let key = *self.inner.keys().next()?; self.inner.remove(&key) } /// Drain all entries, such that children are queued before parents - fn drain_post_order_into(&mut self, queue: &mut Vec) { + fn drain_post_order_into(&mut self, queue: &mut Vec) { if self.inner.is_empty() { return; } @@ -100,8 +92,11 @@ fn with(f: impl FnOnce(&mut Scheduler) -> R) -> R { } /// Push a generic [Runnable] to be executed -pub fn push(runnable: Box) { - with(|s| s.main.push(runnable)); +pub fn push(runnable: F) +where + F: FnOnce() + 'static, +{ + with(|s| s.main.push(Box::new(runnable))); // Execute pending immediately. Necessary for runnables added outside the component lifecycle, // which would otherwise be delayed. start(); @@ -111,32 +106,41 @@ pub fn push(runnable: Box) { mod feat_csr_ssr { use super::*; /// Push a component creation, first render and first rendered [Runnable]s to be executed - pub(crate) fn push_component_create( - component_id: usize, - create: Box, - first_render: Box, - ) { + pub(crate) fn push_component_create(component_id: usize, create: F1, first_render: F2) + where + F1: FnOnce() + 'static, + F2: FnOnce() + 'static, + { with(|s| { - s.create.push(create); - s.render_first.push(component_id, first_render); + s.create.push(Box::new(create)); + s.render_first.push(component_id, Box::new(first_render)); }); } /// Push a component destruction [Runnable] to be executed - pub(crate) fn push_component_destroy(runnable: Box) { - with(|s| s.destroy.push(runnable)); + pub(crate) fn push_component_destroy(runnable: F) + where + F: FnOnce() + 'static, + { + with(|s| s.destroy.push(Box::new(runnable))); } /// Push a component render [Runnable]s to be executed - pub(crate) fn push_component_render(component_id: usize, render: Box) { + pub(crate) fn push_component_render(component_id: usize, render: F) + where + F: FnOnce() + 'static, + { with(|s| { - s.render.push(component_id, render); + s.render.push(component_id, Box::new(render)); }); } /// Push a component update [Runnable] to be executed - pub(crate) fn push_component_update(runnable: Box) { - with(|s| s.update.push(runnable)); + pub(crate) fn push_component_update(runnable: F) + where + F: FnOnce() + 'static, + { + with(|s| s.update.push(Box::new(runnable))); } } @@ -147,22 +151,24 @@ pub(crate) use feat_csr_ssr::*; mod feat_csr { use super::*; - pub(crate) fn push_component_rendered( - component_id: usize, - rendered: Box, - first_render: bool, - ) { + pub(crate) fn push_component_rendered(component_id: usize, rendered: F, first_render: bool) + where + F: FnOnce() + 'static, + { with(|s| { if first_render { - s.rendered_first.push(component_id, rendered); + s.rendered_first.push(component_id, Box::new(rendered)); } else { - s.rendered.push(component_id, rendered); + s.rendered.push(component_id, Box::new(rendered)); } }); } - pub(crate) fn push_component_props_update(props_update: Box) { - with(|s| s.props_update.push(props_update)); + pub(crate) fn push_component_props_update(props_update: F) + where + F: FnOnce() + 'static, + { + with(|s| s.props_update.push(Box::new(props_update))); } } @@ -173,9 +179,12 @@ pub(crate) use feat_csr::*; mod feat_hydration { use super::*; - pub(crate) fn push_component_priority_render(component_id: usize, render: Box) { + pub(crate) fn push_component_priority_render(component_id: usize, render: F) + where + F: FnOnce() + 'static, + { with(|s| { - s.render_priority.push(component_id, render); + s.render_priority.push(component_id, Box::new(render)); }); } } @@ -194,7 +203,7 @@ pub(crate) fn start_now() { break; } for r in queue.drain(..) { - r.task.run(); + r(); } } } @@ -245,7 +254,7 @@ impl Scheduler { /// This method is optimized for typical usage, where possible, but does not break on /// non-typical usage (like scheduling renders in [crate::Component::create()] or /// [crate::Component::rendered()] calls). - fn fill_queue(&mut self, to_run: &mut Vec) { + fn fill_queue(&mut self, to_run: &mut Vec) { // Placed first to avoid as much needless work as possible, handling all the other events. // Drained completely, because they are the highest priority events anyway. self.destroy.drain_into(to_run); @@ -324,14 +333,7 @@ mod tests { static FLAG: Cell = Default::default(); } - struct Test; - impl Runnable for Test { - fn run(self: Box) { - FLAG.with(|v| v.set(true)); - } - } - - push(Box::new(Test)); + push(|| FLAG.with(|v| v.set(true))); FLAG.with(|v| assert!(v.get())); } } From 14705f6efd46da09339374ba5636c1a98186f546 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 22:23:40 +0900 Subject: [PATCH 02/47] Remove component type. --- packages/yew-router/src/lib.rs | 2 - packages/yew-router/src/scope_ext.rs | 145 ---------------------- packages/yew/src/context.rs | 30 +++-- packages/yew/src/html/component/mod.rs | 150 +---------------------- packages/yew/src/html/component/scope.rs | 73 ++++------- packages/yew/src/html/conversion.rs | 4 +- packages/yew/src/lib.rs | 4 +- packages/yew/src/scheduler.rs | 48 -------- packages/yew/src/suspense/component.rs | 25 +++- packages/yew/src/tests/layout_tests.rs | 18 ++- 10 files changed, 85 insertions(+), 414 deletions(-) delete mode 100644 packages/yew-router/src/scope_ext.rs diff --git a/packages/yew-router/src/lib.rs b/packages/yew-router/src/lib.rs index 1cf262e21d8..891c3ad6fc7 100644 --- a/packages/yew-router/src/lib.rs +++ b/packages/yew-router/src/lib.rs @@ -73,7 +73,6 @@ pub mod hooks; pub mod navigator; mod routable; pub mod router; -pub mod scope_ext; pub mod switch; pub mod utils; @@ -99,7 +98,6 @@ pub mod prelude { pub use crate::history::Location; pub use crate::hooks::*; pub use crate::navigator::{NavigationError, NavigationResult, Navigator}; - pub use crate::scope_ext::{LocationHandle, NavigatorHandle, RouterScopeExt}; #[doc(no_inline)] pub use crate::Routable; pub use crate::{BrowserRouter, HashRouter, Router, Switch}; diff --git a/packages/yew-router/src/scope_ext.rs b/packages/yew-router/src/scope_ext.rs deleted file mode 100644 index 5f2172be5f0..00000000000 --- a/packages/yew-router/src/scope_ext.rs +++ /dev/null @@ -1,145 +0,0 @@ -use yew::context::ContextHandle; -use yew::prelude::*; - -use crate::history::Location; -use crate::navigator::Navigator; -use crate::routable::Routable; -use crate::router::{LocationContext, NavigatorContext}; - -/// A [`ContextHandle`] for [`add_location_listener`](RouterScopeExt::add_location_listener). -pub struct LocationHandle { - _inner: ContextHandle, -} - -/// A [`ContextHandle`] for [`add_navigator_listener`](RouterScopeExt::add_navigator_listener). -pub struct NavigatorHandle { - _inner: ContextHandle, -} - -/// An extension to [`Scope`](yew::html::Scope) that provides location information and navigator -/// access. -/// -/// You can access them on `ctx.link()` -/// -/// # Example -/// -/// Below is an example of the implementation of the [`Link`](crate::components::Link) component. -/// -/// ``` -/// # use std::marker::PhantomData; -/// # use wasm_bindgen::UnwrapThrowExt; -/// # use yew::prelude::*; -/// # use yew_router::prelude::*; -/// # use yew_router::components::LinkProps; -/// # -/// # pub struct Link { -/// # _data: PhantomData, -/// # } -/// # -/// # pub enum Msg { -/// # OnClick, -/// # } -/// # -/// impl Component for Link { -/// type Message = Msg; -/// type Properties = LinkProps; -/// -/// fn create(_ctx: &Context) -> Self { -/// Self { _data: PhantomData } -/// } -/// -/// fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { -/// match msg { -/// Msg::OnClick => { -/// ctx.link() -/// .navigator() -/// .expect_throw("failed to get navigator.") -/// .push(&ctx.props().to); -/// false -/// } -/// } -/// } -/// -/// fn view(&self, ctx: &Context) -> Html { -/// html! { -/// -/// { ctx.props().children.clone() } -/// -/// } -/// } -/// } -/// ``` -pub trait RouterScopeExt { - /// Returns current [`Navigator`]. - fn navigator(&self) -> Option; - - /// Returns current [`Location`]. - fn location(&self) -> Option; - - /// Returns current route. - fn route(&self) -> Option - where - R: Routable + 'static; - - /// Adds a listener that gets notified when location changes. - /// - /// # Note - /// - /// [`LocationHandle`] works like a normal [`ContextHandle`] and it unregisters the callback - /// when the handle is dropped. You need to keep the handle for as long as you need the - /// callback. - fn add_location_listener(&self, cb: Callback) -> Option; - - /// Adds a listener that gets notified when navigator changes. - /// - /// # Note - /// - /// [`NavigatorHandle`] works like a normal [`ContextHandle`] and it unregisters the callback - /// when the handle is dropped. You need to keep the handle for as long as you need the - /// callback. - fn add_navigator_listener(&self, cb: Callback) -> Option; -} - -impl RouterScopeExt for yew::html::Scope { - fn navigator(&self) -> Option { - self.context::(Callback::from(|_| {})) - .map(|(m, _)| m.navigator()) - } - - fn location(&self) -> Option { - self.context::(Callback::from(|_| {})) - .map(|(m, _)| m.location()) - } - - fn route(&self) -> Option - where - R: Routable + 'static, - { - let navigator = self.navigator()?; - let location = self.location()?; - - let path = navigator.strip_basename(location.path().into()); - - R::recognize(&path) - } - - fn add_location_listener(&self, cb: Callback) -> Option { - self.context::(Callback::from(move |m: LocationContext| { - cb.emit(m.location()) - })) - .map(|(_, m)| LocationHandle { _inner: m }) - } - - fn add_navigator_listener(&self, cb: Callback) -> Option { - self.context::(Callback::from(move |m: NavigatorContext| { - cb.emit(m.navigator()) - })) - .map(|(_, m)| NavigatorHandle { _inner: m }) - } -} diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index 74514002b79..84dcf725b98 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -5,7 +5,7 @@ use std::cell::RefCell; use slab::Slab; use crate::html::Scope; -use crate::{html, Callback, Children, Component, Context, Html, Properties}; +use crate::{html, BaseComponent, Callback, Children, Context, HtmlResult, Properties}; /// Props for [`ContextProvider`] #[derive(Debug, Clone, PartialEq, Properties)] @@ -77,7 +77,7 @@ impl ContextProvider { } } -impl Component for ContextProvider { +impl BaseComponent for ContextProvider { type Message = (); type Properties = ContextProviderProps; @@ -89,20 +89,34 @@ impl Component for ContextProvider { } } - fn changed(&mut self, ctx: &Context, old_props: &Self::Properties) -> bool { - let props = ctx.props(); + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + true + } - let should_render = old_props.children != props.children; + fn destroy(&mut self, _ctx: &Context) {} + + fn rendered(&mut self, ctx: &Context, first_render: bool) { + if first_render { + return; + } + + let props = ctx.props(); if self.context != props.context { self.context = props.context.clone(); self.notify_consumers(); } + } + + fn prepare_state(&self) -> Option { + None + } - should_render + fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { + true } - fn view(&self, ctx: &Context) -> Html { - html! { <>{ ctx.props().children.clone() } } + fn view(&self, ctx: &Context) -> HtmlResult { + Ok(html! { <>{ ctx.props().children.clone() } }) } } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 5640cf6d979..eca86562e3f 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -16,7 +16,7 @@ pub use properties::*; pub(crate) use scope::Scoped; pub use scope::{AnyScope, Scope, SendAsMessage}; -use super::{Html, HtmlResult, IntoHtmlResult}; +use super::HtmlResult; #[cfg(feature = "hydration")] #[derive(Debug, Clone, Copy, PartialEq)] @@ -114,151 +114,3 @@ pub trait BaseComponent: Sized + 'static { /// Prepares the server-side state. fn prepare_state(&self) -> Option; } - -/// Components are the basic building blocks of the UI in a Yew app. Each Component -/// chooses how to display itself using received props and self-managed state. -/// Components can be dynamic and interactive by declaring messages that are -/// triggered and handled asynchronously. This async update mechanism is inspired by -/// Elm and the actor model used in the Actix framework. -pub trait Component: Sized + 'static { - /// Messages are used to make Components dynamic and interactive. Simple - /// Component's can declare their Message type to be `()`. Complex Component's - /// commonly use an enum to declare multiple Message types. - type Message: 'static; - - /// The Component's properties. - /// - /// When the parent of a Component is re-rendered, it will either be re-created or - /// receive new properties in the context passed to the `changed` lifecycle method. - type Properties: Properties; - - /// Called when component is created. - fn create(ctx: &Context) -> Self; - - /// Called when a new message is sent to the component via its scope. - /// - /// Components handle messages in their `update` method and commonly use this method - /// to update their state and (optionally) re-render themselves. - /// - /// Returned bool indicates whether to render this Component after update. - /// - /// By default, this function will return true and thus make the component re-render. - #[allow(unused_variables)] - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - true - } - - /// Called when properties passed to the component change - /// - /// Returned bool indicates whether to render this Component after changed. - /// - /// By default, this function will return true and thus make the component re-render. - #[allow(unused_variables)] - fn changed(&mut self, ctx: &Context, _old_props: &Self::Properties) -> bool { - true - } - - /// Components define their visual layout using a JSX-style syntax through the use of the - /// `html!` procedural macro. The full guide to using the macro can be found in [Yew's - /// documentation](https://yew.rs/concepts/html). - /// - /// Note that `view()` calls do not always follow a render request from `update()` or - /// `changed()`. Yew may optimize some calls out to reduce virtual DOM tree generation overhead. - /// The `create()` call is always followed by a call to `view()`. - fn view(&self, ctx: &Context) -> Html; - - /// The `rendered` method is called after each time a Component is rendered but - /// before the browser updates the page. - /// - /// Note that `rendered()` calls do not always follow a render request from `update()` or - /// `changed()`. Yew may optimize some calls out to reduce virtual DOM tree generation overhead. - /// The `create()` call is always followed by a call to `view()` and later `rendered()`. - #[allow(unused_variables)] - fn rendered(&mut self, ctx: &Context, first_render: bool) {} - - /// Prepares the state during server side rendering. - /// - /// This state will be sent to the client side and is available via `ctx.prepared_state()`. - /// - /// This method is only called during server-side rendering after the component has been - /// rendered. - fn prepare_state(&self) -> Option { - None - } - - /// Called right before a Component is unmounted. - #[allow(unused_variables)] - fn destroy(&mut self, ctx: &Context) {} -} - -impl BaseComponent for T -where - T: Sized + Component + 'static, -{ - type Message = ::Message; - type Properties = ::Properties; - - fn create(ctx: &Context) -> Self { - Component::create(ctx) - } - - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - Component::update(self, ctx, msg) - } - - fn changed(&mut self, ctx: &Context, old_props: &Self::Properties) -> bool { - Component::changed(self, ctx, old_props) - } - - fn view(&self, ctx: &Context) -> HtmlResult { - Component::view(self, ctx).into_html_result() - } - - fn rendered(&mut self, ctx: &Context, first_render: bool) { - Component::rendered(self, ctx, first_render) - } - - fn destroy(&mut self, ctx: &Context) { - Component::destroy(self, ctx) - } - - fn prepare_state(&self) -> Option { - Component::prepare_state(self) - } -} - -#[cfg(test)] -#[cfg(any(feature = "ssr", feature = "csr"))] -mod tests { - use super::*; - - struct MyCustomComponent; - - impl Component for MyCustomComponent { - type Message = (); - type Properties = (); - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, _ctx: &Context) -> Html { - Default::default() - } - } - - #[test] - fn make_sure_component_update_and_changed_rerender() { - let mut comp = MyCustomComponent; - let ctx = Context { - scope: Scope::new(None), - props: Rc::new(()), - #[cfg(feature = "hydration")] - creation_mode: crate::html::RenderMode::Hydration, - #[cfg(feature = "hydration")] - prepared_state: None, - }; - assert!(Component::update(&mut comp, &ctx, ())); - assert!(Component::changed(&mut comp, &ctx, &Rc::new(()))); - } -} diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 8d050942d2b..fde7010b7a3 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -316,24 +316,18 @@ mod feat_ssr { let (tx, rx) = oneshot::channel(); let state = ComponentRenderState::Ssr { sender: Some(tx) }; - let create_runner = CreateRunner { + CreateRunner { initial_render_state: state, props, scope: self.clone(), #[cfg(feature = "hydration")] prepared_state: None, - }; - - let first_render_runner = RenderRunner { + } + .run(); + RenderRunner { state: self.state.clone(), - }; - - scheduler::push_component_create( - self.id, - move || create_runner.run(), - move || first_render_runner.run(), - ); - scheduler::start(); + } + .run(); let collectable = Collectable::for_component::(); @@ -357,12 +351,11 @@ mod feat_ssr { collectable.write_close_tag(w); } - let runner = DestroyRunner { + DestroyRunner { state: self.state.clone(), parent_to_detach: false, - }; - - scheduler::push_component_destroy(move || runner.run()); + } + .run(); scheduler::start(); } } @@ -587,25 +580,19 @@ mod feat_csr { next_sibling: stable_next_sibling, }; - let create_runner = CreateRunner { + CreateRunner { initial_render_state: state, props, scope: self.clone(), #[cfg(feature = "hydration")] prepared_state: None, - }; + } + .run(); - let first_render_runner = RenderRunner { + RenderRunner { state: self.state.clone(), - }; - - scheduler::push_component_create( - self.id, - move || create_runner.run(), - move || first_render_runner.run(), - ); - // Not guaranteed to already have the scheduler started - scheduler::start(); + } + .run(); } pub(crate) fn reuse(&self, props: Rc, next_sibling: NodeRef) { @@ -642,14 +629,11 @@ mod feat_csr { /// Process an event to destroy a component fn destroy(self, parent_to_detach: bool) { - let runner = DestroyRunner { + DestroyRunner { state: self.state, parent_to_detach, - }; - - scheduler::push_component_destroy(move || runner.run()); - // Not guaranteed to already have the scheduler started - scheduler::start(); + } + .run() } fn destroy_boxed(self: Box, parent_to_detach: bool) { @@ -676,7 +660,6 @@ mod feat_hydration { use crate::dom_bundle::{BSubtree, Fragment}; use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner}; use crate::html::NodeRef; - use crate::scheduler; use crate::virtual_dom::Collectable; impl Scope @@ -741,25 +724,17 @@ mod feat_hydration { fragment, }; - let create_runner = CreateRunner { + CreateRunner { initial_render_state: state, props, scope: self.clone(), prepared_state, - }; - - let first_render_runner = RenderRunner { + } + .run(); + RenderRunner { state: self.state.clone(), - }; - - scheduler::push_component_create( - self.id, - move || create_runner.run(), - move || first_render_runner.run(), - ); - - // Not guaranteed to already have the scheduler started - scheduler::start(); + } + .run(); } } } diff --git a/packages/yew/src/html/conversion.rs b/packages/yew/src/html/conversion.rs index 348c3400f53..e7197068d46 100644 --- a/packages/yew/src/html/conversion.rs +++ b/packages/yew/src/html/conversion.rs @@ -4,11 +4,11 @@ use implicit_clone::unsync::{IArray, IMap}; pub use implicit_clone::ImplicitClone; use super::super::callback::Callback; -use super::{BaseComponent, Children, ChildrenRenderer, Component, NodeRef, Scope}; +use super::{BaseComponent, Children, ChildrenRenderer, NodeRef, Scope}; use crate::virtual_dom::{AttrValue, VChild, VNode}; impl ImplicitClone for NodeRef {} -impl ImplicitClone for Scope {} +impl ImplicitClone for Scope {} // TODO there are still a few missing /// A trait similar to `Into` which allows conversion to a value of a `Properties` struct. diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 257d62aa29f..8ce09ca49cd 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -335,8 +335,8 @@ pub mod prelude { pub use crate::events::*; pub use crate::functional::*; pub use crate::html::{ - create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context, - Html, HtmlResult, NodeRef, Properties, + create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Context, Html, + HtmlResult, NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; pub use crate::suspense::Suspense; diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 5cf177166be..5bac881f801 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -62,15 +62,10 @@ struct Scheduler { // Main queue main: FifoQueue, - // Component queues - destroy: FifoQueue, - create: FifoQueue, - props_update: FifoQueue, update: FifoQueue, render: TopologicalQueue, - render_first: TopologicalQueue, render_priority: TopologicalQueue, rendered_first: TopologicalQueue, @@ -105,26 +100,6 @@ where #[cfg(any(feature = "ssr", feature = "csr"))] mod feat_csr_ssr { use super::*; - /// Push a component creation, first render and first rendered [Runnable]s to be executed - pub(crate) fn push_component_create(component_id: usize, create: F1, first_render: F2) - where - F1: FnOnce() + 'static, - F2: FnOnce() + 'static, - { - with(|s| { - s.create.push(Box::new(create)); - s.render_first.push(component_id, Box::new(first_render)); - }); - } - - /// Push a component destruction [Runnable] to be executed - pub(crate) fn push_component_destroy(runnable: F) - where - F: FnOnce() + 'static, - { - with(|s| s.destroy.push(Box::new(runnable))); - } - /// Push a component render [Runnable]s to be executed pub(crate) fn push_component_render(component_id: usize, render: F) where @@ -255,29 +230,6 @@ impl Scheduler { /// non-typical usage (like scheduling renders in [crate::Component::create()] or /// [crate::Component::rendered()] calls). fn fill_queue(&mut self, to_run: &mut Vec) { - // Placed first to avoid as much needless work as possible, handling all the other events. - // Drained completely, because they are the highest priority events anyway. - self.destroy.drain_into(to_run); - - // Create events can be batched, as they are typically just for object creation - self.create.drain_into(to_run); - - // These typically do nothing and don't spawn any other events - can be batched. - // Should be run only after all first renders have finished. - if !to_run.is_empty() { - return; - } - - // First render must never be skipped and takes priority over main, because it may need - // to init `NodeRef`s - // - // Should be processed one at time, because they can spawn more create and rendered events - // for their children. - if let Some(r) = self.render_first.pop_topmost() { - to_run.push(r); - return; - } - self.props_update.drain_into(to_run); // Priority rendering diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 163a6c3d558..527f6334562 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -15,12 +15,12 @@ pub struct SuspenseProps { #[cfg(any(feature = "csr", feature = "ssr"))] mod feat_csr_ssr { use super::*; - use crate::html::{Children, Component, Context, Html, Scope}; + use crate::html::{BaseComponent, Children, Context, Html, Scope}; use crate::suspense::Suspension; #[cfg(feature = "hydration")] use crate::suspense::SuspensionHandle; use crate::virtual_dom::{VNode, VSuspense}; - use crate::{function_component, html}; + use crate::{function_component, html, HtmlResult}; #[derive(Properties, PartialEq, Debug, Clone)] pub(crate) struct BaseSuspenseProps { @@ -41,7 +41,7 @@ mod feat_csr_ssr { hydration_handle: Option, } - impl Component for BaseSuspense { + impl BaseComponent for BaseSuspense { type Message = BaseSuspenseMsg; type Properties = BaseSuspenseProps; @@ -100,11 +100,11 @@ mod feat_csr_ssr { } } - fn view(&self, ctx: &Context) -> Html { + fn view(&self, ctx: &Context) -> HtmlResult { let BaseSuspenseProps { children, fallback } = (*ctx.props()).clone(); let children = html! {<>{children}}; - match fallback { + Ok(match fallback { Some(fallback) => { let vsuspense = VSuspense::new( children, @@ -117,7 +117,7 @@ mod feat_csr_ssr { VNode::from(vsuspense) } None => children, - } + }) } #[cfg(feature = "hydration")] @@ -128,6 +128,19 @@ mod feat_csr_ssr { } } } + + #[cfg(not(feature = "hydration"))] + fn rendered(&mut self, _ctx: &Context, _first_render: bool) {} + + fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { + true + } + + fn destroy(&mut self, _ctx: &Context) {} + + fn prepare_state(&self) -> Option { + None + } } impl BaseSuspense { diff --git a/packages/yew/src/tests/layout_tests.rs b/packages/yew/src/tests/layout_tests.rs index 85a54419a4b..70627cca163 100644 --- a/packages/yew/src/tests/layout_tests.rs +++ b/packages/yew/src/tests/layout_tests.rs @@ -7,10 +7,10 @@ use yew::NodeRef; use crate::dom_bundle::{BSubtree, Bundle}; use crate::html::AnyScope; use crate::virtual_dom::VNode; -use crate::{scheduler, Component, Context, Html}; +use crate::{scheduler, BaseComponent, Context, HtmlResult}; struct Comp; -impl Component for Comp { +impl BaseComponent for Comp { type Message = (); type Properties = (); @@ -26,7 +26,19 @@ impl Component for Comp { unimplemented!() } - fn view(&self, _ctx: &Context) -> Html { + fn view(&self, _ctx: &Context) -> HtmlResult { + unimplemented!() + } + + fn destroy(&mut self, _ctx: &Context) { + unimplemented!() + } + + fn prepare_state(&self) -> Option { + unimplemented!() + } + + fn rendered(&mut self, _ctx: &Context, _first_render: bool) { unimplemented!() } } From 0d42e3acac7d198034223007412241689ae4c08b Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 23:04:40 +0900 Subject: [PATCH 03/47] Remove update lifecycle. --- packages/yew-macro/src/function_component.rs | 5 - packages/yew/src/context.rs | 8 +- packages/yew/src/functional/mod.rs | 2 +- packages/yew/src/html/component/lifecycle.rs | 62 +---- packages/yew/src/html/component/mod.rs | 5 +- packages/yew/src/html/component/scope.rs | 264 +------------------ packages/yew/src/scheduler.rs | 15 -- packages/yew/src/suspense/component.rs | 189 ++++++------- packages/yew/src/suspense/mod.rs | 2 +- packages/yew/src/tests/layout_tests.rs | 4 - 10 files changed, 105 insertions(+), 451 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 9e6a1df495f..6438b4b8a18 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -292,11 +292,6 @@ impl FunctionComponent { } } - #[inline] - fn update(&mut self, _ctx: &::yew::html::Context, _msg: Self::Message) -> ::std::primitive::bool { - true - } - #[inline] fn changed(&mut self, _ctx: &::yew::html::Context, _old_props: &Self::Properties) -> ::std::primitive::bool { true diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index 84dcf725b98..02861d97007 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -75,6 +75,10 @@ impl ContextProvider { consumer.emit(self.context.clone()); } } + + pub(crate) fn get_context_value(&self) -> T { + self.context.clone() + } } impl BaseComponent for ContextProvider { @@ -89,10 +93,6 @@ impl BaseComponent for ContextProvider { } } - fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { - true - } - fn destroy(&mut self, _ctx: &Context) {} fn rendered(&mut self, ctx: &Context, first_render: bool) { diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 1f3ce01317a..46a20f0f27f 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -333,7 +333,7 @@ where let re_render = { let link = ctx.link().clone(); - Rc::new(move || link.send_message(())) + Rc::new(move || link.schedule_render()) }; Self { diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 77453e6cf48..49e7a9eed21 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -18,8 +18,8 @@ use crate::html::NodeRef; use crate::html::RenderMode; use crate::html::{Html, RenderError}; use crate::scheduler::{self, Shared}; -use crate::suspense::{BaseSuspense, Suspension}; -use crate::{Callback, Context, HtmlResult}; +use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; +use crate::{Callback, Context, ContextProvider, HtmlResult}; pub(crate) enum ComponentRenderState { #[cfg(feature = "csr")] @@ -152,7 +152,6 @@ pub(crate) trait Stateful { fn any_scope(&self) -> AnyScope; - fn flush_messages(&mut self) -> bool; fn props_changed(&mut self, props: Rc) -> bool; fn as_any(&self) -> &dyn Any; @@ -187,17 +186,6 @@ where self.context.creation_mode() } - fn flush_messages(&mut self) -> bool { - self.context - .link() - .pending_messages - .drain() - .into_iter() - .fold(false, |acc, msg| { - self.component.update(&self.context, msg) || acc - }) - } - fn props_changed(&mut self, props: Rc) -> bool { let props = match Rc::downcast::(props) { Ok(m) => m, @@ -302,8 +290,10 @@ impl ComponentState { if let Some(m) = self.suspension.take() { let comp_scope = self.inner.any_scope(); - let suspense_scope = comp_scope.find_parent_scope::().unwrap(); - BaseSuspense::resume(&suspense_scope, m); + let suspense_scope = comp_scope + .find_parent_scope::>() + .unwrap(); + resume_suspension(&suspense_scope, m); } } } @@ -331,40 +321,6 @@ impl CreateRunner { } } -pub(crate) struct UpdateRunner { - pub state: Shared>, -} - -impl ComponentState { - #[tracing::instrument( - level = tracing::Level::DEBUG, - skip(self), - fields(component.id = self.comp_id) - )] - fn update(&mut self) -> bool { - let schedule_render = self.inner.flush_messages(); - tracing::trace!(schedule_render); - schedule_render - } -} - -impl UpdateRunner { - pub fn run(self) { - if let Some(state) = self.state.borrow_mut().as_mut() { - let schedule_render = state.update(); - - if schedule_render { - let render = RenderRunner { - state: self.state.clone(), - }; - - scheduler::push_component_render(state.comp_id, move || render.run()); - // Only run from the scheduler, so no need to call `scheduler::start()` - } - } - } -} - pub(crate) struct DestroyRunner { pub state: Shared>, pub parent_to_detach: bool, @@ -456,7 +412,7 @@ impl ComponentState { let comp_scope = self.inner.any_scope(); let suspense_scope = comp_scope - .find_parent_scope::() + .find_parent_scope::>() .expect("To suspend rendering, a component is required."); let comp_id = self.comp_id; @@ -473,12 +429,12 @@ impl ComponentState { if let Some(ref last_suspension) = self.suspension { if &suspension != last_suspension { // We remove previous suspension from the suspense. - BaseSuspense::resume(&suspense_scope, last_suspension.clone()); + resume_suspension(&suspense_scope, last_suspension.clone()) } } self.suspension = Some(suspension.clone()); - BaseSuspense::suspend(&suspense_scope, suspension); + suspend_suspension(&suspense_scope, suspension); } } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index eca86562e3f..6cf93375882 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -14,7 +14,7 @@ pub use marker::*; pub use properties::*; #[cfg(feature = "csr")] pub(crate) use scope::Scoped; -pub use scope::{AnyScope, Scope, SendAsMessage}; +pub use scope::{AnyScope, Scope}; use super::HtmlResult; @@ -96,9 +96,6 @@ pub trait BaseComponent: Sized + 'static { /// Creates a component. fn create(ctx: &Context) -> Self; - /// Updates component's internal state. - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool; - /// React to changes of component properties. fn changed(&mut self, ctx: &Context, _old_props: &Self::Properties) -> bool; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index fde7010b7a3..0f25cd12d1a 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -1,20 +1,18 @@ //! Component scope module use std::any::{Any, TypeId}; -use std::future::Future; use std::marker::PhantomData; use std::ops::Deref; use std::rc::Rc; use std::{fmt, iter}; -use futures::{Stream, StreamExt}; - #[cfg(any(feature = "csr", feature = "ssr"))] use super::lifecycle::ComponentState; +use super::lifecycle::RenderRunner; use super::BaseComponent; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider}; -use crate::platform::spawn_local; +use crate::scheduler; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; @@ -95,9 +93,6 @@ pub struct Scope { _marker: PhantomData, parent: Option>, - #[cfg(any(feature = "csr", feature = "ssr"))] - pub(crate) pending_messages: MsgQueue, - #[cfg(any(feature = "csr", feature = "ssr"))] pub(crate) state: Shared>, @@ -115,8 +110,6 @@ impl Clone for Scope { Scope { _marker: PhantomData, - #[cfg(any(feature = "csr", feature = "ssr"))] - pending_messages: self.pending_messages.clone(), parent: self.parent.clone(), #[cfg(any(feature = "csr", feature = "ssr"))] @@ -133,48 +126,6 @@ impl Scope { self.parent.as_deref() } - /// Creates a `Callback` which will send a message to the linked - /// component's update method when invoked. - /// - /// If your callback function returns a [Future], - /// use [`callback_future`](Scope::callback_future) instead. - pub fn callback(&self, function: F) -> Callback - where - M: Into, - F: Fn(IN) -> M + 'static, - { - let scope = self.clone(); - let closure = move |input| { - let output = function(input); - scope.send_message(output); - }; - Callback::from(closure) - } - - /// Creates a `Callback` which will send a batch of messages back - /// to the linked component's update method when invoked. - /// - /// The callback function's return type is generic to allow for dealing with both - /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that - /// might not need to send an update. - /// - /// ```ignore - /// link.batch_callback(|_| vec![Msg::A, Msg::B]); - /// link.batch_callback(|_| Some(Msg::A)); - /// ``` - pub fn batch_callback(&self, function: F) -> Callback - where - F: Fn(IN) -> OUT + 'static, - OUT: SendAsMessage, - { - let scope = self.clone(); - let closure = move |input| { - let messages = function(input); - messages.send(&scope); - }; - closure.into() - } - /// Accesses a value provided by a parent `ContextProvider` component of the /// same type. pub fn context( @@ -184,108 +135,19 @@ impl Scope { AnyScope::from(self.clone()).context(callback) } - /// This method asynchronously awaits a [Future] that returns a message and sends it - /// to the linked component. - /// - /// # Panics - /// If the future panics, then the promise will not resolve, and will leak. - pub fn send_future(&self, future: Fut) - where - Msg: Into, - Fut: Future + 'static, - { - let link = self.clone(); - spawn_local(async move { - let message: COMP::Message = future.await.into(); - link.send_message(message); - }); - } - - /// This method creates a [`Callback`] which, when emitted, asynchronously awaits the - /// message returned from the passed function before sending it to the linked component. - /// - /// # Panics - /// If the future panics, then the promise will not resolve, and will leak. - pub fn callback_future(&self, function: F) -> Callback - where - Msg: Into, - Fut: Future + 'static, - F: Fn(IN) -> Fut + 'static, - { - let link = self.clone(); - - let closure = move |input: IN| { - link.send_future(function(input)); - }; - - closure.into() - } - - /// Asynchronously send a batch of messages to a component. This asynchronously awaits the - /// passed [Future], before sending the message batch to the linked component. - /// - /// # Panics - /// If the future panics, then the promise will not resolve, and will leak. - pub fn send_future_batch(&self, future: Fut) - where - Fut: Future + 'static, - Fut::Output: SendAsMessage, - { - let link = self.clone(); - let js_future = async move { - future.await.send(&link); - }; - spawn_local(js_future); - } - - /// This method asynchronously awaits a [`Stream`] that returns a series of messages and sends - /// them to the linked component. - /// - /// # Panics - /// If the stream panics, then the promise will not resolve, and will leak. - /// - /// # Note - /// - /// This method will not notify the component when the stream has been fully exhausted. If - /// you want this feature, you can add an EOF message variant for your component and use - /// [`StreamExt::chain`] and [`stream::once`](futures::stream::once) to chain an EOF message to - /// the original stream. If your stream is produced by another crate, you can use - /// [`StreamExt::map`] to transform the stream's item type to the component message type. - pub fn send_stream(&self, stream: S) - where - M: Into, - S: Stream + 'static, - { - let link = self.clone(); - let js_future = async move { - futures::pin_mut!(stream); - while let Some(msg) = stream.next().await { - let message: COMP::Message = msg.into(); - link.send_message(message); - } - }; - spawn_local(js_future); - } - /// Returns the linked component if available pub fn get_component(&self) -> Option + '_> { self.arch_get_component() } - /// Send a message to the component. - pub fn send_message(&self, msg: T) - where - T: Into, - { - self.arch_send_message(msg) - } + /// Schedules a render. + pub(crate) fn schedule_render(&self) { + let runner = RenderRunner { + state: self.state.clone(), + }; - /// Send a batch of messages to the component. - /// - /// This is slightly more efficient than calling [`send_message`](Self::send_message) - /// in a loop. - pub fn send_message_batch(&self, messages: Vec) { - self.arch_send_message_batch(messages) + scheduler::push_component_render(self.id, move || runner.run()); + scheduler::start(); } } @@ -387,46 +249,6 @@ mod feat_csr_ssr { use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; - use crate::html::component::lifecycle::UpdateRunner; - use crate::scheduler::{self, Shared}; - - #[derive(Debug)] - pub(crate) struct MsgQueue(Shared>); - - impl MsgQueue { - pub fn new() -> Self { - MsgQueue(Rc::default()) - } - - pub fn push(&self, msg: Msg) -> usize { - let mut inner = self.0.borrow_mut(); - inner.push(msg); - - inner.len() - } - - pub fn append(&self, other: &mut Vec) -> usize { - let mut inner = self.0.borrow_mut(); - inner.append(other); - - inner.len() - } - - pub fn drain(&self) -> Vec { - let mut other_queue = Vec::new(); - let mut inner = self.0.borrow_mut(); - - std::mem::swap(&mut *inner, &mut other_queue); - - other_queue - } - } - - impl Clone for MsgQueue { - fn clone(&self) -> Self { - MsgQueue(self.0.clone()) - } - } static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -437,13 +259,9 @@ mod feat_csr_ssr { let state = Rc::new(RefCell::new(None)); - let pending_messages = MsgQueue::new(); - Scope { _marker: PhantomData, - pending_messages, - state, parent, @@ -476,44 +294,9 @@ mod feat_csr_ssr { .ok() }) } - - #[inline] - fn schedule_update(&self) { - let runner = UpdateRunner { - state: self.state.clone(), - }; - - scheduler::push_component_update(move || runner.run()); - // Not guaranteed to already have the scheduler started - scheduler::start(); - } - - #[inline] - pub(super) fn arch_send_message(&self, msg: T) - where - T: Into, - { - // We are the first message in queue, so we queue the update. - if self.pending_messages.push(msg.into()) == 1 { - self.schedule_update(); - } - } - - #[inline] - pub(super) fn arch_send_message_batch(&self, mut messages: Vec) { - let msg_len = messages.len(); - - // The queue was empty, so we queue the update - if self.pending_messages.append(&mut messages) == msg_len { - self.schedule_update(); - } - } } } -#[cfg(any(feature = "ssr", feature = "csr"))] -pub(crate) use feat_csr_ssr::*; - #[cfg(feature = "csr")] mod feat_csr { use std::cell::Ref; @@ -738,32 +521,3 @@ mod feat_hydration { } } } - -/// Defines a message type that can be sent to a component. -/// Used for the return value of closure given to -/// [Scope::batch_callback](struct.Scope.html#method.batch_callback). -pub trait SendAsMessage { - /// Sends the message to the given component's scope. - /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback). - fn send(self, scope: &Scope); -} - -impl SendAsMessage for Option -where - COMP: BaseComponent, -{ - fn send(self, scope: &Scope) { - if let Some(msg) = self { - scope.send_message(msg); - } - } -} - -impl SendAsMessage for Vec -where - COMP: BaseComponent, -{ - fn send(self, scope: &Scope) { - scope.send_message_batch(self); - } -} diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 5bac881f801..88bd78a2675 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -63,7 +63,6 @@ struct Scheduler { main: FifoQueue, props_update: FifoQueue, - update: FifoQueue, render: TopologicalQueue, render_priority: TopologicalQueue, @@ -109,14 +108,6 @@ mod feat_csr_ssr { s.render.push(component_id, Box::new(render)); }); } - - /// Push a component update [Runnable] to be executed - pub(crate) fn push_component_update(runnable: F) - where - F: FnOnce() + 'static, - { - with(|s| s.update.push(Box::new(runnable))); - } } #[cfg(any(feature = "ssr", feature = "csr"))] @@ -243,12 +234,6 @@ impl Scheduler { // Children rendered lifecycle happen before parents. self.rendered_first.drain_post_order_into(to_run); - // Updates are after the first render to ensure we always have the entire child tree - // rendered, once an update is processed. - // - // Can be batched, as they can cause only non-first renders. - self.update.drain_into(to_run); - // Likely to cause duplicate renders via component updates, so placed before them self.main.drain_into(to_run); diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 527f6334562..c9788f57ef1 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -14,143 +14,114 @@ pub struct SuspenseProps { #[cfg(any(feature = "csr", feature = "ssr"))] mod feat_csr_ssr { + use std::cell::RefCell; + use super::*; - use crate::html::{BaseComponent, Children, Context, Html, Scope}; + use crate::html::{Children, Html, Scope}; use crate::suspense::Suspension; - #[cfg(feature = "hydration")] - use crate::suspense::SuspensionHandle; use crate::virtual_dom::{VNode, VSuspense}; - use crate::{function_component, html, HtmlResult}; + use crate::{ + function_component, html, use_reducer, ContextProvider, Reducible, UseReducerDispatcher, + }; - #[derive(Properties, PartialEq, Debug, Clone)] - pub(crate) struct BaseSuspenseProps { - pub children: Children, - pub fallback: Option, + #[derive(Default)] + pub(crate) struct Suspensions { + inner: RefCell>, } - #[derive(Debug)] - pub(crate) enum BaseSuspenseMsg { - Suspend(Suspension), - Resume(Suspension), - } + impl Reducible for Suspensions { + type Action = BaseSuspenseMsg; - #[derive(Debug)] - pub(crate) struct BaseSuspense { - suspensions: Vec, - #[cfg(feature = "hydration")] - hydration_handle: Option, - } + fn reduce(self: std::rc::Rc, action: Self::Action) -> std::rc::Rc { + { + let mut inner = self.inner.borrow_mut(); - impl BaseComponent for BaseSuspense { - type Message = BaseSuspenseMsg; - type Properties = BaseSuspenseProps; - - fn create(_ctx: &Context) -> Self { - #[cfg(not(feature = "hydration"))] - let suspensions = Vec::new(); - - // We create a suspension to block suspense until its rendered method is notified. - #[cfg(feature = "hydration")] - let (suspensions, hydration_handle) = { - use crate::callback::Callback; - use crate::html::RenderMode; - - match _ctx.creation_mode() { - RenderMode::Hydration => { - let link = _ctx.link().clone(); - let (s, handle) = Suspension::new(); - s.listen(Callback::from(move |s| { - link.send_message(BaseSuspenseMsg::Resume(s)); - })); - (vec![s], Some(handle)) + match action { + BaseSuspenseMsg::Resume(m) => { + inner.retain(|n| &m != n); + } + BaseSuspenseMsg::Suspend(m) => { + if m.resumed() { + drop(inner); + return self; + } + inner.push(m); } - _ => (Vec::new(), None), } - }; - - Self { - suspensions, - #[cfg(feature = "hydration")] - hydration_handle, } + + self } + } - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - match msg { - Self::Message::Suspend(m) => { - assert!( - ctx.props().fallback.is_some(), - "You cannot suspend from a component rendered as a fallback." - ); + pub(crate) type DispatchSuspension = UseReducerDispatcher; - if m.resumed() { - return false; - } + #[derive(Properties, PartialEq, Debug, Clone)] + pub(crate) struct BaseSuspenseProps { + pub children: Children, + pub fallback: Option, + } - self.suspensions.push(m); + #[function_component] + pub(crate) fn BaseSuspense(props: &BaseSuspenseProps) -> Html { + let suspensions = use_reducer(Suspensions::default); - true - } - Self::Message::Resume(ref m) => { - let suspensions_len = self.suspensions.len(); - self.suspensions.retain(|n| m != n); + let has_suspension = !suspensions.inner.borrow().is_empty(); - suspensions_len != self.suspensions.len() - } - } - } + let BaseSuspenseProps { children, fallback } = props.clone(); + let dispatch_suspension = suspensions.dispatcher(); - fn view(&self, ctx: &Context) -> HtmlResult { - let BaseSuspenseProps { children, fallback } = (*ctx.props()).clone(); - let children = html! {<>{children}}; - - Ok(match fallback { - Some(fallback) => { - let vsuspense = VSuspense::new( - children, - fallback, - !self.suspensions.is_empty(), - // We don't need to key this as the key will be applied to the component. - None, - ); - - VNode::from(vsuspense) - } - None => children, - }) - } + let children = html! { + context={dispatch_suspension}> + {children} + > + }; - #[cfg(feature = "hydration")] - fn rendered(&mut self, _ctx: &Context, first_render: bool) { - if first_render { - if let Some(m) = self.hydration_handle.take() { - m.resume(); - } - } + #[cfg(debug_assertions)] + if has_suspension && fallback.is_none() { + panic!("You cannot suspend from a fallback component."); } - #[cfg(not(feature = "hydration"))] - fn rendered(&mut self, _ctx: &Context, _first_render: bool) {} - - fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { - true + match fallback { + Some(fallback) => { + let vsuspense = VSuspense::new( + children, + fallback, + has_suspension, + // We don't need to key this as the key will be applied to the component. + None, + ); + + VNode::from(vsuspense) + } + None => children, } + } - fn destroy(&mut self, _ctx: &Context) {} - - fn prepare_state(&self) -> Option { - None + pub(crate) fn resume_suspension( + provider: &Scope>, + s: Suspension, + ) { + if let Some(provider) = provider.get_component() { + let context = provider.get_context_value(); + context.dispatch(BaseSuspenseMsg::Resume(s)); } } - impl BaseSuspense { - pub(crate) fn suspend(scope: &Scope, s: Suspension) { - scope.send_message(BaseSuspenseMsg::Suspend(s)); + pub(crate) fn suspend_suspension( + provider: &Scope>, + s: Suspension, + ) { + if let Some(provider) = provider.get_component() { + let context = provider.get_context_value(); + context.dispatch(BaseSuspenseMsg::Suspend(s)); } + } - pub(crate) fn resume(scope: &Scope, s: Suspension) { - scope.send_message(BaseSuspenseMsg::Resume(s)); - } + #[derive(Debug)] + pub(crate) enum BaseSuspenseMsg { + Suspend(Suspension), + Resume(Suspension), } /// Suspend rendering and show a fallback UI until the underlying task completes. diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs index 339e2ffcc1a..6157b74f40b 100644 --- a/packages/yew/src/suspense/mod.rs +++ b/packages/yew/src/suspense/mod.rs @@ -5,7 +5,7 @@ mod hooks; mod suspension; #[cfg(any(feature = "csr", feature = "ssr"))] -pub(crate) use component::BaseSuspense; +pub(crate) use component::{resume_suspension, suspend_suspension, DispatchSuspension}; pub use component::{Suspense, SuspenseProps}; pub use hooks::*; pub use suspension::{Suspension, SuspensionHandle, SuspensionResult}; diff --git a/packages/yew/src/tests/layout_tests.rs b/packages/yew/src/tests/layout_tests.rs index 70627cca163..9ffe22c5c49 100644 --- a/packages/yew/src/tests/layout_tests.rs +++ b/packages/yew/src/tests/layout_tests.rs @@ -18,10 +18,6 @@ impl BaseComponent for Comp { unimplemented!() } - fn update(&mut self, _ctx: &Context, _: Self::Message) -> bool { - unimplemented!(); - } - fn changed(&mut self, _ctx: &Context, _: &Self::Properties) -> bool { unimplemented!() } From b6d8da74f6d500893a298f4177e21b51cf414116 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 23:14:39 +0900 Subject: [PATCH 04/47] Remove changed lifecycle. --- packages/yew-macro/src/function_component.rs | 6 ------ packages/yew/src/context.rs | 5 ----- packages/yew/src/functional/mod.rs | 2 +- packages/yew/src/html/component/lifecycle.rs | 16 ++++++---------- packages/yew/src/html/component/mod.rs | 6 ------ packages/yew/src/tests/layout_tests.rs | 5 ----- 6 files changed, 7 insertions(+), 33 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 6438b4b8a18..02a45f19dd7 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -281,7 +281,6 @@ impl FunctionComponent { quote! { #[automatically_derived] impl #impl_generics ::yew::html::BaseComponent for #component_name #ty_generics #where_clause { - type Message = (); type Properties = #props_type; #[inline] @@ -292,11 +291,6 @@ impl FunctionComponent { } } - #[inline] - fn changed(&mut self, _ctx: &::yew::html::Context, _old_props: &Self::Properties) -> ::std::primitive::bool { - true - } - #[inline] fn view(&self, ctx: &::yew::html::Context) -> ::yew::html::HtmlResult { ::yew::functional::FunctionComponent::::render( diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index 02861d97007..fd948b194a4 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -82,7 +82,6 @@ impl ContextProvider { } impl BaseComponent for ContextProvider { - type Message = (); type Properties = ContextProviderProps; fn create(ctx: &Context) -> Self { @@ -112,10 +111,6 @@ impl BaseComponent for ContextProvider { None } - fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { - true - } - fn view(&self, ctx: &Context) -> HtmlResult { Ok(html! { <>{ ctx.props().children.clone() } }) } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 46a20f0f27f..967753edc39 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -327,7 +327,7 @@ where /// Creates a new function component. pub fn new(ctx: &Context) -> Self where - T: BaseComponent + FunctionProvider + 'static, + T: BaseComponent + FunctionProvider + 'static, { let scope = AnyScope::from(ctx.link().clone()); let re_render = { diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 49e7a9eed21..112e15012c3 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -187,16 +187,12 @@ where } fn props_changed(&mut self, props: Rc) -> bool { - let props = match Rc::downcast::(props) { - Ok(m) => m, - _ => return false, - }; - - if self.context.props != props { - let old_props = std::mem::replace(&mut self.context.props, props); - self.component.changed(&self.context, &old_props) - } else { - false + match Rc::downcast::(props) { + Ok(m) if m != self.context.props => { + self.context.props = m; + true + } + _ => false, } } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 6cf93375882..6d3db47544b 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -87,18 +87,12 @@ impl Context { /// [`#[function_component]`](crate::functional::function_component) macro to define your /// components. pub trait BaseComponent: Sized + 'static { - /// The Component's Message. - type Message: 'static; - /// The Component's Properties. type Properties: Properties; /// Creates a component. fn create(ctx: &Context) -> Self; - /// React to changes of component properties. - fn changed(&mut self, ctx: &Context, _old_props: &Self::Properties) -> bool; - /// Returns a component layout to be rendered. fn view(&self, ctx: &Context) -> HtmlResult; diff --git a/packages/yew/src/tests/layout_tests.rs b/packages/yew/src/tests/layout_tests.rs index 9ffe22c5c49..e83fcb874e0 100644 --- a/packages/yew/src/tests/layout_tests.rs +++ b/packages/yew/src/tests/layout_tests.rs @@ -11,17 +11,12 @@ use crate::{scheduler, BaseComponent, Context, HtmlResult}; struct Comp; impl BaseComponent for Comp { - type Message = (); type Properties = (); fn create(_: &Context) -> Self { unimplemented!() } - fn changed(&mut self, _ctx: &Context, _: &Self::Properties) -> bool { - unimplemented!() - } - fn view(&self, _ctx: &Context) -> HtmlResult { unimplemented!() } From ec7e47d497b66ed21aef1b7cb068a0c8d1ea0f20 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 23:22:07 +0900 Subject: [PATCH 05/47] Remove props update queue. --- packages/yew/src/html/component/lifecycle.rs | 2 +- packages/yew/src/html/component/scope.rs | 10 +++------- packages/yew/src/scheduler.rs | 11 ----------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 112e15012c3..a95a0208ed5 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -690,7 +690,7 @@ mod feat_csr { next_sibling: None, }; - scheduler::push_component_props_update(move || runner.run()); + scheduler::push(move || runner.run()); } } } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 0f25cd12d1a..359abf40681 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -309,7 +309,6 @@ mod feat_csr { ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner, }; use crate::html::NodeRef; - use crate::scheduler; impl AnyScope { #[cfg(test)] @@ -327,15 +326,12 @@ mod feat_csr { props: Rc, next_sibling: NodeRef, ) { - let runner = PropsUpdateRunner { + PropsUpdateRunner { state, props: Some(props), next_sibling: Some(next_sibling), - }; - - scheduler::push_component_props_update(move || runner.run()); - // Not guaranteed to already have the scheduler started - scheduler::start(); + } + .run(); } impl Scope diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 88bd78a2675..47a9b789681 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -62,8 +62,6 @@ struct Scheduler { // Main queue main: FifoQueue, - props_update: FifoQueue, - render: TopologicalQueue, render_priority: TopologicalQueue, @@ -129,13 +127,6 @@ mod feat_csr { } }); } - - pub(crate) fn push_component_props_update(props_update: F) - where - F: FnOnce() + 'static, - { - with(|s| s.props_update.push(Box::new(props_update))); - } } #[cfg(feature = "csr")] @@ -221,8 +212,6 @@ impl Scheduler { /// non-typical usage (like scheduling renders in [crate::Component::create()] or /// [crate::Component::rendered()] calls). fn fill_queue(&mut self, to_run: &mut Vec) { - self.props_update.drain_into(to_run); - // Priority rendering // // This is needed for hydration susequent render to fix node refs. From 889d22c3ed86ca172b4e4000bea25f64a5ea67ef Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 7 Nov 2022 23:26:54 +0900 Subject: [PATCH 06/47] Remove priority rendering. --- packages/yew/src/html/component/lifecycle.rs | 8 ------ packages/yew/src/html/component/scope.rs | 1 - packages/yew/src/scheduler.rs | 26 -------------------- 3 files changed, 35 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index a95a0208ed5..5045e1c2f93 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -481,14 +481,6 @@ impl ComponentState { ref next_sibling, ref root, } => { - let runner = RenderRunner { - state: shared_state.clone(), - }; - - // We schedule a "first" render to run immediately after hydration, - // to fix NodeRefs (first_node and next_sibling). - scheduler::push_component_priority_render(self.comp_id, move || runner.run()); - let scope = self.inner.any_scope(); // This first node is not guaranteed to be correct here. diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 359abf40681..84b281be313 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -367,7 +367,6 @@ mod feat_csr { prepared_state: None, } .run(); - RenderRunner { state: self.state.clone(), } diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 47a9b789681..dc3610d509e 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -63,7 +63,6 @@ struct Scheduler { main: FifoQueue, render: TopologicalQueue, - render_priority: TopologicalQueue, rendered_first: TopologicalQueue, rendered: TopologicalQueue, @@ -132,23 +131,6 @@ mod feat_csr { #[cfg(feature = "csr")] pub(crate) use feat_csr::*; -#[cfg(feature = "hydration")] -mod feat_hydration { - use super::*; - - pub(crate) fn push_component_priority_render(component_id: usize, render: F) - where - F: FnOnce() + 'static, - { - with(|s| { - s.render_priority.push(component_id, Box::new(render)); - }); - } -} - -#[cfg(feature = "hydration")] -pub(crate) use feat_hydration::*; - /// Execute any pending [Runnable]s pub(crate) fn start_now() { #[tracing::instrument(level = tracing::Level::DEBUG)] @@ -212,14 +194,6 @@ impl Scheduler { /// non-typical usage (like scheduling renders in [crate::Component::create()] or /// [crate::Component::rendered()] calls). fn fill_queue(&mut self, to_run: &mut Vec) { - // Priority rendering - // - // This is needed for hydration susequent render to fix node refs. - if let Some(r) = self.render_priority.pop_topmost() { - to_run.push(r); - return; - } - // Children rendered lifecycle happen before parents. self.rendered_first.drain_post_order_into(to_run); From 7abacb14af1f90dd26009cb6e01ff493b3955f82 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 00:29:00 +0900 Subject: [PATCH 07/47] Remove generics on components. --- packages/yew-macro/src/function_component.rs | 10 +-- packages/yew/src/context.rs | 20 +++-- packages/yew/src/functional/mod.rs | 14 +++- packages/yew/src/html/component/lifecycle.rs | 45 +++++------ packages/yew/src/html/component/mod.rs | 23 +++--- packages/yew/src/html/component/scope.rs | 78 +++++++++++--------- packages/yew/src/tests/layout_tests.rs | 8 +- 7 files changed, 109 insertions(+), 89 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 02a45f19dd7..afb8167737e 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -284,7 +284,7 @@ impl FunctionComponent { type Properties = #props_type; #[inline] - fn create(ctx: &::yew::html::Context) -> Self { + fn create(ctx: &::yew::html::Context) -> Self { Self { _marker: ::std::marker::PhantomData, function_component: ::yew::functional::FunctionComponent::::new(ctx), @@ -292,20 +292,20 @@ impl FunctionComponent { } #[inline] - fn view(&self, ctx: &::yew::html::Context) -> ::yew::html::HtmlResult { + fn view(&self, ctx: &::yew::html::Context) -> ::yew::html::HtmlResult { ::yew::functional::FunctionComponent::::render( &self.function_component, - ::yew::html::Context::::props(ctx) + ::yew::html::Context::props(ctx) ) } #[inline] - fn rendered(&mut self, _ctx: &::yew::html::Context, _first_render: ::std::primitive::bool) { + fn rendered(&mut self, _ctx: &::yew::html::Context) { ::yew::functional::FunctionComponent::::rendered(&self.function_component) } #[inline] - fn destroy(&mut self, _ctx: &::yew::html::Context) { + fn destroy(&mut self, _ctx: &::yew::html::Context) { ::yew::functional::FunctionComponent::::destroy(&self.function_component) } diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index fd948b194a4..2f65b2ff00c 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -84,22 +84,18 @@ impl ContextProvider { impl BaseComponent for ContextProvider { type Properties = ContextProviderProps; - fn create(ctx: &Context) -> Self { - let props = ctx.props(); + fn create(ctx: &Context) -> Self { + let props = ctx.props().downcast::>().unwrap(); Self { context: props.context.clone(), consumers: RefCell::new(Slab::new()), } } - fn destroy(&mut self, _ctx: &Context) {} + fn destroy(&mut self, _ctx: &Context) {} - fn rendered(&mut self, ctx: &Context, first_render: bool) { - if first_render { - return; - } - - let props = ctx.props(); + fn rendered(&mut self, ctx: &Context) { + let props = ctx.props().downcast::>().unwrap(); if self.context != props.context { self.context = props.context.clone(); @@ -111,7 +107,9 @@ impl BaseComponent for ContextProvider { None } - fn view(&self, ctx: &Context) -> HtmlResult { - Ok(html! { <>{ ctx.props().children.clone() } }) + fn view(&self, ctx: &Context) -> HtmlResult { + let props = ctx.props().downcast::>().unwrap(); + + Ok(html! { <>{ props.children.clone() } }) } } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 967753edc39..3b94a31c36b 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -22,6 +22,7 @@ use std::any::Any; use std::cell::RefCell; +use std::collections::HashMap; use std::fmt; use std::rc::Rc; @@ -303,6 +304,10 @@ pub trait FunctionProvider { fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult; } +thread_local! { + static FC_CONTEXTS: RefCell> = RefCell::default(); +} + /// A type that interacts [`FunctionProvider`] to provide lifecycle events to be bridged to /// [`BaseComponent`]. /// @@ -325,11 +330,11 @@ where T: FunctionProvider + 'static, { /// Creates a new function component. - pub fn new(ctx: &Context) -> Self + pub fn new(ctx: &Context) -> Self where T: BaseComponent + FunctionProvider + 'static, { - let scope = AnyScope::from(ctx.link().clone()); + let scope = ctx.link().clone(); let re_render = { let link = ctx.link().clone(); @@ -350,13 +355,14 @@ where } /// Renders a function component. - pub fn render(&self, props: &T::Properties) -> HtmlResult { + pub fn render(&self, props: Rc) -> HtmlResult { + let props = props.downcast().unwrap(); let mut hook_ctx = self.hook_ctx.borrow_mut(); hook_ctx.prepare_run(); #[allow(clippy::let_and_return)] - let result = T::run(&mut hook_ctx, props); + let result = T::run(&mut hook_ctx, &props); #[cfg(debug_assertions)] hook_ctx.assert_hook_context(result.is_ok()); diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 5045e1c2f93..17bbe2346a3 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -1,12 +1,13 @@ //! Component lifecycle module use std::any::Any; +use std::marker::PhantomData; use std::rc::Rc; #[cfg(feature = "csr")] use web_sys::Element; -use super::scope::{AnyScope, Scope}; +use super::scope::AnyScope; use super::BaseComponent; #[cfg(feature = "hydration")] use crate::dom_bundle::Fragment; @@ -137,7 +138,7 @@ where COMP: BaseComponent, { pub(crate) component: COMP, - pub(crate) context: Context, + pub(crate) context: Context, } /// A trait to provide common, @@ -147,7 +148,7 @@ where /// methods. pub(crate) trait Stateful { fn view(&self) -> HtmlResult; - fn rendered(&mut self, first_render: bool); + fn rendered(&mut self); fn destroy(&mut self); fn any_scope(&self) -> AnyScope; @@ -169,8 +170,8 @@ where self.component.view(&self.context) } - fn rendered(&mut self, first_render: bool) { - self.component.rendered(&self.context, first_render) + fn rendered(&mut self) { + self.component.rendered(&self.context) } fn destroy(&mut self) { @@ -178,7 +179,7 @@ where } fn any_scope(&self) -> AnyScope { - self.context.link().clone().into() + self.context.link().clone() } #[cfg(feature = "hydration")] @@ -187,9 +188,12 @@ where } fn props_changed(&mut self, props: Rc) -> bool { - match Rc::downcast::(props) { - Ok(m) if m != self.context.props => { - self.context.props = m; + match ( + props.downcast_ref::(), + self.context.props.downcast_ref(), + ) { + (Some(l), Some(r)) if l != r => { + self.context.props = props; true } _ => false, @@ -225,15 +229,15 @@ impl ComponentState { level = tracing::Level::DEBUG, name = "create", skip_all, - fields(component.id = scope.id), + fields(component.id = scope.get_id()), )] fn new( initial_render_state: ComponentRenderState, - scope: Scope, - props: Rc, + scope: AnyScope, + props: Rc, #[cfg(feature = "hydration")] prepared_state: Option, ) -> Self { - let comp_id = scope.id; + let comp_id = scope.get_id(); #[cfg(feature = "hydration")] let creation_mode = { match initial_render_state { @@ -296,17 +300,18 @@ impl ComponentState { pub(crate) struct CreateRunner { pub initial_render_state: ComponentRenderState, - pub props: Rc, - pub scope: Scope, + pub props: Rc, + pub scope: AnyScope, #[cfg(feature = "hydration")] pub prepared_state: Option, + pub _marker: PhantomData, } impl CreateRunner { pub fn run(self) { let mut current_state = self.scope.state.borrow_mut(); if current_state.is_none() { - *current_state = Some(ComponentState::new( + *current_state = Some(ComponentState::new::( self.initial_render_state, self.scope.clone(), self.props, @@ -463,7 +468,6 @@ impl ComponentState { let runner = RenderedRunner { state: shared_state.clone(), - first_render, }; scheduler::push_component_rendered( @@ -645,7 +649,6 @@ mod feat_csr { pub(crate) struct RenderedRunner { pub state: Shared>, - pub first_render: bool, } impl ComponentState { @@ -654,9 +657,9 @@ mod feat_csr { skip(self), fields(component.id = self.comp_id) )] - fn rendered(&mut self, first_render: bool) -> bool { + fn rendered(&mut self) -> bool { if self.suspension.is_none() { - self.inner.rendered(first_render); + self.inner.rendered(); } #[cfg(feature = "hydration")] @@ -673,7 +676,7 @@ mod feat_csr { impl RenderedRunner { pub fn run(self) { if let Some(state) = self.state.borrow_mut().as_mut() { - let has_pending_props = state.rendered(self.first_render); + let has_pending_props = state.rendered(); if has_pending_props { let runner = PropsUpdateRunner { diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 6d3db47544b..eb8be89d4fe 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -7,6 +7,7 @@ mod marker; mod properties; mod scope; +use std::any::Any; use std::rc::Rc; pub use children::*; @@ -30,9 +31,9 @@ pub(crate) enum RenderMode { /// The [`Component`]'s context. This contains component's [`Scope`] and props and /// is passed to every lifecycle method. #[derive(Debug)] -pub struct Context { - scope: Scope, - props: Rc, +pub struct Context { + props: Rc, + scope: AnyScope, #[cfg(feature = "hydration")] creation_mode: RenderMode, @@ -40,17 +41,17 @@ pub struct Context { prepared_state: Option, } -impl Context { +impl Context { /// The component scope #[inline] - pub fn link(&self) -> &Scope { + pub fn link(&self) -> &AnyScope { &self.scope } /// The component's props #[inline] - pub fn props(&self) -> &COMP::Properties { - &self.props + pub fn props(&self) -> Rc { + self.scope.any_props().unwrap() } #[cfg(feature = "hydration")] @@ -91,16 +92,16 @@ pub trait BaseComponent: Sized + 'static { type Properties: Properties; /// Creates a component. - fn create(ctx: &Context) -> Self; + fn create(ctx: &Context) -> Self; /// Returns a component layout to be rendered. - fn view(&self, ctx: &Context) -> HtmlResult; + fn view(&self, ctx: &Context) -> HtmlResult; /// Notified after a layout is rendered. - fn rendered(&mut self, ctx: &Context, first_render: bool); + fn rendered(&mut self, ctx: &Context); /// Notified before a component is destroyed. - fn destroy(&mut self, ctx: &Context); + fn destroy(&mut self, ctx: &Context); /// Prepares the server-side state. fn prepare_state(&self) -> Option; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 84b281be313..c1d768a1482 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -1,6 +1,8 @@ //! Component scope module use std::any::{Any, TypeId}; +use std::cell::RefCell; +use std::collections::HashMap; use std::marker::PhantomData; use std::ops::Deref; use std::rc::Rc; @@ -16,10 +18,19 @@ use crate::scheduler; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; +thread_local! { + static PROPS: RefCell>> = RefCell::default(); +} + /// Untyped scope used for accessing parent scope #[derive(Clone)] pub struct AnyScope { + id: usize, type_id: TypeId, + + #[cfg(any(feature = "csr", feature = "ssr"))] + pub(crate) state: Shared>, + parent: Option>, typed_scope: Rc, } @@ -33,7 +44,12 @@ impl fmt::Debug for AnyScope { impl From> for AnyScope { fn from(scope: Scope) -> Self { AnyScope { + id: scope.id, type_id: TypeId::of::(), + + #[cfg(any(feature = "csr", feature = "ssr"))] + state: scope.state.clone(), + parent: scope.parent.clone(), typed_scope: Rc::new(scope), } @@ -41,6 +57,24 @@ impl From> for AnyScope { } impl AnyScope { + pub(crate) fn get_id(&self) -> usize { + self.id + } + + /// Schedules a render. + pub(crate) fn schedule_render(&self) { + let runner = RenderRunner { + state: self.state.clone(), + }; + + scheduler::push_component_render(self.id, move || runner.run()); + scheduler::start(); + } + + pub(crate) fn any_props(&self) -> Option> { + PROPS.with(|m| m.borrow().get(&self.get_id()).cloned()) + } + /// Returns the parent scope pub fn get_parent(&self) -> Option<&AnyScope> { self.parent.as_deref() @@ -139,16 +173,6 @@ impl Scope { pub fn get_component(&self) -> Option + '_> { self.arch_get_component() } - - /// Schedules a render. - pub(crate) fn schedule_render(&self) { - let runner = RenderRunner { - state: self.state.clone(), - }; - - scheduler::push_component_render(self.id, move || runner.run()); - scheduler::start(); - } } #[cfg(feature = "ssr")] @@ -181,9 +205,10 @@ mod feat_ssr { CreateRunner { initial_render_state: state, props, - scope: self.clone(), + scope: self.clone().to_any(), #[cfg(feature = "hydration")] prepared_state: None, + _marker: PhantomData::, } .run(); RenderRunner { @@ -223,26 +248,6 @@ mod feat_ssr { } } -#[cfg(not(any(feature = "ssr", feature = "csr")))] -mod feat_no_csr_ssr { - use super::*; - - // Skeleton code to provide public methods when no renderer are enabled. - impl Scope { - pub(super) fn arch_get_component(&self) -> Option + '_> { - Option::<&COMP>::None - } - - pub(super) fn arch_send_message(&self, _msg: T) - where - T: Into, - { - } - - pub(super) fn arch_send_message_batch(&self, _messages: Vec) {} - } -} - #[cfg(any(feature = "ssr", feature = "csr"))] mod feat_csr_ssr { use std::cell::{Ref, RefCell}; @@ -314,7 +319,9 @@ mod feat_csr { #[cfg(test)] pub(crate) fn test() -> Self { Self { + id: 0, type_id: TypeId::of::<()>(), + state: Rc::default(), parent: None, typed_scope: Rc::new(()), } @@ -351,6 +358,9 @@ mod feat_csr { internal_ref.link(next_sibling.clone()); let stable_next_sibling = NodeRef::default(); stable_next_sibling.link(next_sibling); + + PROPS.with(|m| m.borrow_mut().insert(self.id, props.clone())); + let state = ComponentRenderState::Render { bundle, root, @@ -362,9 +372,10 @@ mod feat_csr { CreateRunner { initial_render_state: state, props, - scope: self.clone(), + scope: self.clone().to_any(), #[cfg(feature = "hydration")] prepared_state: None, + _marker: PhantomData::, } .run(); RenderRunner { @@ -505,8 +516,9 @@ mod feat_hydration { CreateRunner { initial_render_state: state, props, - scope: self.clone(), + scope: self.clone().to_any(), prepared_state, + _marker: PhantomData::, } .run(); RenderRunner { diff --git a/packages/yew/src/tests/layout_tests.rs b/packages/yew/src/tests/layout_tests.rs index e83fcb874e0..034acf318f5 100644 --- a/packages/yew/src/tests/layout_tests.rs +++ b/packages/yew/src/tests/layout_tests.rs @@ -13,15 +13,15 @@ struct Comp; impl BaseComponent for Comp { type Properties = (); - fn create(_: &Context) -> Self { + fn create(_: &Context) -> Self { unimplemented!() } - fn view(&self, _ctx: &Context) -> HtmlResult { + fn view(&self, _ctx: &Context) -> HtmlResult { unimplemented!() } - fn destroy(&mut self, _ctx: &Context) { + fn destroy(&mut self, _ctx: &Context) { unimplemented!() } @@ -29,7 +29,7 @@ impl BaseComponent for Comp { unimplemented!() } - fn rendered(&mut self, _ctx: &Context, _first_render: bool) { + fn rendered(&mut self, _ctx: &Context) { unimplemented!() } } From 0b73e2b9e984f7a11a466d3b989afc2996e5b695 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 13:15:24 +0900 Subject: [PATCH 08/47] Remove generics from FunctionComponent. --- packages/yew-macro/src/function_component.rs | 12 +++--- packages/yew/src/functional/mod.rs | 44 +++++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index afb8167737e..e8c237e7f86 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -287,13 +287,13 @@ impl FunctionComponent { fn create(ctx: &::yew::html::Context) -> Self { Self { _marker: ::std::marker::PhantomData, - function_component: ::yew::functional::FunctionComponent::::new(ctx), + function_component: ::yew::functional::FunctionComponent::new::(ctx), } } #[inline] fn view(&self, ctx: &::yew::html::Context) -> ::yew::html::HtmlResult { - ::yew::functional::FunctionComponent::::render( + ::yew::functional::FunctionComponent::render( &self.function_component, ::yew::html::Context::props(ctx) ) @@ -301,17 +301,17 @@ impl FunctionComponent { #[inline] fn rendered(&mut self, _ctx: &::yew::html::Context) { - ::yew::functional::FunctionComponent::::rendered(&self.function_component) + ::yew::functional::FunctionComponent::rendered(&self.function_component) } #[inline] fn destroy(&mut self, _ctx: &::yew::html::Context) { - ::yew::functional::FunctionComponent::::destroy(&self.function_component) + ::yew::functional::FunctionComponent::destroy(&self.function_component) } #[inline] fn prepare_state(&self) -> ::std::option::Option<::std::string::String> { - ::yew::functional::FunctionComponent::::prepare_state(&self.function_component) + ::yew::functional::FunctionComponent::prepare_state(&self.function_component) } } } @@ -375,7 +375,7 @@ impl FunctionComponent { #[allow(unused_parens)] #vis struct #component_name #generics #where_clause { _marker: ::std::marker::PhantomData<(#phantom_generics)>, - function_component: ::yew::functional::FunctionComponent, + function_component: ::yew::functional::FunctionComponent, } } } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 3b94a31c36b..5b8b6aed2d9 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -31,7 +31,7 @@ use wasm_bindgen::prelude::*; #[cfg(all(feature = "hydration", feature = "ssr"))] use crate::html::RenderMode; use crate::html::{AnyScope, BaseComponent, Context, HtmlResult}; -use crate::Properties; +use crate::{Html, Properties}; mod hooks; pub use hooks::*; @@ -308,6 +308,8 @@ thread_local! { static FC_CONTEXTS: RefCell> = RefCell::default(); } +type MakeHtml = Rc HtmlResult>; + /// A type that interacts [`FunctionProvider`] to provide lifecycle events to be bridged to /// [`BaseComponent`]. /// @@ -317,32 +319,38 @@ thread_local! { /// /// Use the `#[function_component]` macro instead. #[doc(hidden)] -pub struct FunctionComponent -where - T: FunctionProvider, -{ - _never: std::marker::PhantomData, +pub struct FunctionComponent { + run_once: MakeHtml, hook_ctx: RefCell, } -impl FunctionComponent -where - T: FunctionProvider + 'static, -{ +impl FunctionComponent { /// Creates a new function component. - pub fn new(ctx: &Context) -> Self + pub fn new(ctx: &Context) -> Self where T: BaseComponent + FunctionProvider + 'static, { + let run_once = Rc::new(|ctx: &mut HookContext, props: &dyn Any| { + let props = match props.downcast_ref() { + Some(m) => m, + None => return Ok(Html::default()), + }; + + T::run(ctx, props) + }); + + Self::new_any(ctx, run_once) + } + + fn new_any(ctx: &Context, run_once: MakeHtml) -> Self { let scope = ctx.link().clone(); let re_render = { let link = ctx.link().clone(); - Rc::new(move || link.schedule_render()) }; Self { - _never: std::marker::PhantomData::default(), + run_once, hook_ctx: HookContext::new( scope, re_render, @@ -356,13 +364,12 @@ where /// Renders a function component. pub fn render(&self, props: Rc) -> HtmlResult { - let props = props.downcast().unwrap(); let mut hook_ctx = self.hook_ctx.borrow_mut(); hook_ctx.prepare_run(); #[allow(clippy::let_and_return)] - let result = T::run(&mut hook_ctx, &props); + let result = (self.run_once)(&mut hook_ctx, &props); #[cfg(debug_assertions)] hook_ctx.assert_hook_context(result.is_ok()); @@ -389,11 +396,8 @@ where } } -impl fmt::Debug for FunctionComponent -where - T: FunctionProvider + 'static, -{ +impl fmt::Debug for FunctionComponent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("FunctionComponent<_>") + f.write_str("FunctionComponent") } } From 8c47f0d2a0844380eb0b4b1fa710b3bf995d1063 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 20:00:20 +0900 Subject: [PATCH 09/47] Rewrite context provider in function component. --- packages/yew/src/context.rs | 173 +++++++++++++++-------- packages/yew/src/functional/hooks/mod.rs | 14 ++ packages/yew/src/html/component/scope.rs | 7 +- packages/yew/src/suspense/component.rs | 11 +- 4 files changed, 135 insertions(+), 70 deletions(-) diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index 2f65b2ff00c..fe2ec1f0132 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -1,63 +1,53 @@ //! This module defines the `ContextProvider` component. +use std::any::Any; +use std::borrow::Borrow; use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; use slab::Slab; +use yew_macro::function_component; -use crate::html::Scope; -use crate::{html, BaseComponent, Callback, Children, Context, HtmlResult, Properties}; +use crate::html::AnyScope; +use crate::{ + html, use_component_id, use_effect_with_deps, use_memo, Callback, Children, Html, Properties, +}; -/// Props for [`ContextProvider`] -#[derive(Debug, Clone, PartialEq, Properties)] -pub struct ContextProviderProps { - /// Context value to be passed down - pub context: T, - /// Children - pub children: Children, -} - -/// The context provider component. -/// -/// Every child (direct or indirect) of this component may access the context value. -/// In order to consume contexts, [`Scope::context`][Scope::context] method is used, -/// In function components the `use_context` hook is used. #[derive(Debug)] -pub struct ContextProvider { +pub(crate) struct ContextStore { context: T, - consumers: RefCell>>, + consumers: Slab>, } -/// Owns the connection to a context provider. When dropped, the component will -/// no longer receive updates from the provider. -#[derive(Debug)] -pub struct ContextHandle { - provider: Scope>, - key: usize, -} - -impl Drop for ContextHandle { - fn drop(&mut self) { - if let Some(component) = self.provider.get_component() { - component.consumers.borrow_mut().remove(self.key); - } +impl ContextStore { + pub(crate) fn get(scope: &AnyScope) -> Option>>> { + CONTEXT_STORES.with(|m| { + m.borrow_mut() + .get(&scope.get_id()) + .cloned() + .and_then(|m| m.downcast().ok()) + }) } -} -impl ContextProvider { /// 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. pub(crate) fn subscribe_consumer( - &self, + this: Rc>, callback: Callback, - scope: Scope, ) -> (T, ContextHandle) { - let ctx = self.context.clone(); - let key = self.consumers.borrow_mut().insert(callback); + let (key, context) = { + let mut this = this.borrow_mut(); + let key = this.consumers.insert(callback); + let context = this.context.clone(); + + (key, context) + }; ( - ctx, + context, ContextHandle { - provider: scope, + provider: this, key, }, ) @@ -81,35 +71,96 @@ impl ContextProvider { } } -impl BaseComponent for ContextProvider { - type Properties = ContextProviderProps; - - fn create(ctx: &Context) -> Self { - let props = ctx.props().downcast::>().unwrap(); - Self { - context: props.context.clone(), - consumers: RefCell::new(Slab::new()), - } - } +thread_local! { + static CONTEXT_STORES: RefCell>> = RefCell::default(); +} - fn destroy(&mut self, _ctx: &Context) {} +/// Props for [`ContextProvider`] +#[derive(Debug, Clone, PartialEq, Properties)] +pub struct ContextProviderProps { + /// Context value to be passed down + pub context: T, + /// Children + pub children: Children, +} - fn rendered(&mut self, ctx: &Context) { - let props = ctx.props().downcast::>().unwrap(); +/// Owns the connection to a context provider. When dropped, the component will +/// no longer receive updates from the provider. +#[derive(Debug)] +pub struct ContextHandle { + provider: Rc>>, + key: usize, +} - if self.context != props.context { - self.context = props.context.clone(); - self.notify_consumers(); - } +impl Drop for ContextHandle { + fn drop(&mut self) { + let mut provider = self.provider.borrow_mut(); + provider.consumers.remove(self.key); } +} - fn prepare_state(&self) -> Option { - None +/// The context provider component. +/// +/// Every child (direct or indirect) of this component may access the context value. +/// In order to consume contexts, [`Scope::context`][Scope::context] method is used, +/// In function components the `use_context` hook is used. +#[function_component] +pub fn ContextProvider(props: &ContextProviderProps) -> Html +where + T: PartialEq + Clone + 'static, +{ + let ContextProviderProps { context, children } = props.clone(); + let comp_id = use_component_id(); + + { + let context = context.clone(); + use_memo( + |_| { + CONTEXT_STORES.with(|m| { + let mut m = m.borrow_mut(); + + m.insert( + comp_id, + Rc::new(RefCell::new(ContextStore { + context: context.clone(), + consumers: Slab::new(), + })), + ); + }); + }, + (), + ); } - fn view(&self, ctx: &Context) -> HtmlResult { - let props = ctx.props().downcast::>().unwrap(); + { + use_effect_with_deps( + move |context| { + let comp = CONTEXT_STORES.with(|m| { + m.borrow_mut() + .get(&comp_id) + .cloned() + .and_then(|m| m.downcast::>>().ok()) + .unwrap() + }); + let mut comp = comp.borrow_mut(); + + comp.context = context.clone(); + comp.notify_consumers(); + }, + context, + ); + } - Ok(html! { <>{ props.children.clone() } }) + { + use_effect_with_deps( + move |_| { + move || { + CONTEXT_STORES.with(|m| m.borrow_mut().remove(&comp_id)); + } + }, + (), + ); } + + html! {<>{children}} } diff --git a/packages/yew/src/functional/hooks/mod.rs b/packages/yew/src/functional/hooks/mod.rs index 5787cb56c98..7533b5ce7ee 100644 --- a/packages/yew/src/functional/hooks/mod.rs +++ b/packages/yew/src/functional/hooks/mod.rs @@ -56,3 +56,17 @@ impl Hook for BoxedHook<'_, T> { (self.inner)(ctx) } } + +pub(crate) fn use_component_id() -> impl Hook { + struct HookProvider {} + + impl Hook for HookProvider { + type Output = usize; + + fn run(self, ctx: &mut HookContext) -> Self::Output { + ctx.scope.get_id() + } + } + + HookProvider {} +} diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index c1d768a1482..03a6abb4cfe 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -13,7 +13,7 @@ use super::lifecycle::ComponentState; use super::lifecycle::RenderRunner; use super::BaseComponent; use crate::callback::Callback; -use crate::context::{ContextHandle, ContextProvider}; +use crate::context::{ContextHandle, ContextProvider, ContextStore}; use crate::scheduler; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; @@ -116,9 +116,8 @@ impl AnyScope { callback: Callback, ) -> Option<(T, ContextHandle)> { let scope = self.find_parent_scope::>()?; - let scope_clone = scope.clone(); - let component = scope.get_component()?; - Some(component.subscribe_consumer(callback, scope_clone)) + let store = ContextStore::::get(&scope.to_any())?; + Some(ContextStore::subscribe_consumer(store, callback)) } } diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index c9788f57ef1..241f517012a 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -17,7 +17,8 @@ mod feat_csr_ssr { use std::cell::RefCell; use super::*; - use crate::html::{Children, Html, Scope}; + use crate::context::ContextStore; + use crate::html::{Children, Html, Scope, Scoped}; use crate::suspense::Suspension; use crate::virtual_dom::{VNode, VSuspense}; use crate::{ @@ -102,8 +103,8 @@ mod feat_csr_ssr { provider: &Scope>, s: Suspension, ) { - if let Some(provider) = provider.get_component() { - let context = provider.get_context_value(); + if let Some(provider) = ContextStore::::get(&provider.to_any()) { + let context = provider.borrow().get_context_value(); context.dispatch(BaseSuspenseMsg::Resume(s)); } } @@ -112,8 +113,8 @@ mod feat_csr_ssr { provider: &Scope>, s: Suspension, ) { - if let Some(provider) = provider.get_component() { - let context = provider.get_context_value(); + if let Some(provider) = ContextStore::::get(&provider.to_any()) { + let context = provider.borrow().get_context_value(); context.dispatch(BaseSuspenseMsg::Suspend(s)); } } From 73103e2d02645a562d12003b8d9e95fa4b2c029e Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 20:31:27 +0900 Subject: [PATCH 10/47] Remove delegated lifecycle on components. --- packages/yew-macro/src/function_component.rs | 30 +--- packages/yew/src/html/component/lifecycle.rs | 174 ++++--------------- packages/yew/src/html/component/mod.rs | 18 +- packages/yew/src/html/component/scope.rs | 88 ++++++---- packages/yew/src/tests/layout_tests.rs | 20 +-- 5 files changed, 90 insertions(+), 240 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index e8c237e7f86..28ca39c2647 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -284,34 +284,8 @@ impl FunctionComponent { type Properties = #props_type; #[inline] - fn create(ctx: &::yew::html::Context) -> Self { - Self { - _marker: ::std::marker::PhantomData, - function_component: ::yew::functional::FunctionComponent::new::(ctx), - } - } - - #[inline] - fn view(&self, ctx: &::yew::html::Context) -> ::yew::html::HtmlResult { - ::yew::functional::FunctionComponent::render( - &self.function_component, - ::yew::html::Context::props(ctx) - ) - } - - #[inline] - fn rendered(&mut self, _ctx: &::yew::html::Context) { - ::yew::functional::FunctionComponent::rendered(&self.function_component) - } - - #[inline] - fn destroy(&mut self, _ctx: &::yew::html::Context) { - ::yew::functional::FunctionComponent::destroy(&self.function_component) - } - - #[inline] - fn prepare_state(&self) -> ::std::option::Option<::std::string::String> { - ::yew::functional::FunctionComponent::prepare_state(&self.function_component) + fn create(ctx: &::yew::html::Context) -> ::yew::functional::FunctionComponent { + ::yew::functional::FunctionComponent::new::(ctx) } } } diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 17bbe2346a3..3512e7b09b5 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -1,14 +1,12 @@ //! Component lifecycle module use std::any::Any; -use std::marker::PhantomData; use std::rc::Rc; #[cfg(feature = "csr")] use web_sys::Element; use super::scope::AnyScope; -use super::BaseComponent; #[cfg(feature = "hydration")] use crate::dom_bundle::Fragment; #[cfg(feature = "csr")] @@ -20,7 +18,7 @@ use crate::html::RenderMode; use crate::html::{Html, RenderError}; use crate::scheduler::{self, Shared}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; -use crate::{Callback, Context, ContextProvider, HtmlResult}; +use crate::{Callback, Context, ContextProvider, FunctionComponent}; pub(crate) enum ComponentRenderState { #[cfg(feature = "csr")] @@ -133,84 +131,9 @@ impl ComponentRenderState { } } -struct CompStateInner -where - COMP: BaseComponent, -{ - pub(crate) component: COMP, - pub(crate) context: Context, -} - -/// A trait to provide common, -/// generic free behaviour across all components to reduce code size. -/// -/// Mostly a thin wrapper that passes the context to a component's lifecycle -/// methods. -pub(crate) trait Stateful { - fn view(&self) -> HtmlResult; - fn rendered(&mut self); - fn destroy(&mut self); - - fn any_scope(&self) -> AnyScope; - - fn props_changed(&mut self, props: Rc) -> bool; - - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; - - #[cfg(feature = "hydration")] - fn creation_mode(&self) -> RenderMode; -} - -impl Stateful for CompStateInner -where - COMP: BaseComponent, -{ - fn view(&self) -> HtmlResult { - self.component.view(&self.context) - } - - fn rendered(&mut self) { - self.component.rendered(&self.context) - } - - fn destroy(&mut self) { - self.component.destroy(&self.context); - } - - fn any_scope(&self) -> AnyScope { - self.context.link().clone() - } - - #[cfg(feature = "hydration")] - fn creation_mode(&self) -> RenderMode { - self.context.creation_mode() - } - - fn props_changed(&mut self, props: Rc) -> bool { - match ( - props.downcast_ref::(), - self.context.props.downcast_ref(), - ) { - (Some(l), Some(r)) if l != r => { - self.context.props = props; - true - } - _ => false, - } - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - pub(crate) struct ComponentState { - pub(super) inner: Box, + pub(super) component: FunctionComponent, + pub(super) context: Context, pub(super) render_state: ComponentRenderState, @@ -231,39 +154,17 @@ impl ComponentState { skip_all, fields(component.id = scope.get_id()), )] - fn new( + fn new( + component: FunctionComponent, + context: Context, initial_render_state: ComponentRenderState, scope: AnyScope, - props: Rc, - #[cfg(feature = "hydration")] prepared_state: Option, ) -> Self { let comp_id = scope.get_id(); - #[cfg(feature = "hydration")] - let creation_mode = { - match initial_render_state { - ComponentRenderState::Render { .. } => RenderMode::Render, - ComponentRenderState::Hydration { .. } => RenderMode::Hydration, - #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => RenderMode::Ssr, - } - }; - - let context = Context { - scope, - props, - #[cfg(feature = "hydration")] - creation_mode, - #[cfg(feature = "hydration")] - prepared_state, - }; - - let inner = Box::new(CompStateInner { - component: COMP::create(&context), - context, - }); Self { - inner, + component, + context, render_state: initial_render_state, suspension: None, @@ -276,19 +177,9 @@ impl ComponentState { } } - pub(crate) fn downcast_comp_ref(&self) -> Option<&COMP> - where - COMP: BaseComponent + 'static, - { - self.inner - .as_any() - .downcast_ref::>() - .map(|m| &m.component) - } - fn resume_existing_suspension(&mut self) { if let Some(m) = self.suspension.take() { - let comp_scope = self.inner.any_scope(); + let comp_scope = self.context.link(); let suspense_scope = comp_scope .find_parent_scope::>() @@ -298,25 +189,22 @@ impl ComponentState { } } -pub(crate) struct CreateRunner { +pub(crate) struct CreateRunner { pub initial_render_state: ComponentRenderState, - pub props: Rc, pub scope: AnyScope, - #[cfg(feature = "hydration")] - pub prepared_state: Option, - pub _marker: PhantomData, + pub component: FunctionComponent, + pub context: Context, } -impl CreateRunner { +impl CreateRunner { pub fn run(self) { let mut current_state = self.scope.state.borrow_mut(); if current_state.is_none() { - *current_state = Some(ComponentState::new::( + *current_state = Some(ComponentState::new( + self.component, + self.context, self.initial_render_state, self.scope.clone(), - self.props, - #[cfg(feature = "hydration")] - self.prepared_state, )); } } @@ -334,7 +222,7 @@ impl ComponentState { fields(component.id = self.comp_id) )] fn destroy(mut self, parent_to_detach: bool) { - self.inner.destroy(); + self.component.destroy(); self.resume_existing_suspension(); match self.render_state { @@ -391,7 +279,7 @@ impl ComponentState { fields(component.id = self.comp_id) )] fn render(&mut self, shared_state: &Shared>) { - match self.inner.view() { + match self.component.render(self.context.props()) { Ok(vnode) => self.commit_render(shared_state, vnode), Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp), }; @@ -410,7 +298,7 @@ impl ComponentState { scheduler::push_component_render(self.comp_id, move || runner.run()); } else { // We schedule a render after current suspension is resumed. - let comp_scope = self.inner.any_scope(); + let comp_scope = self.context.link(); let suspense_scope = comp_scope .find_parent_scope::>() @@ -454,13 +342,13 @@ impl ComponentState { ref internal_ref, .. } => { - let scope = self.inner.any_scope(); + let scope = self.context.link(); #[cfg(feature = "hydration")] next_sibling.debug_assert_not_trapped(); let new_node_ref = - bundle.reconcile(root, &scope, parent, next_sibling.clone(), new_root); + bundle.reconcile(root, scope, parent, next_sibling.clone(), new_root); internal_ref.link(new_node_ref); let first_render = !self.has_rendered; @@ -485,12 +373,12 @@ impl ComponentState { ref next_sibling, ref root, } => { - let scope = self.inner.any_scope(); + let scope = self.context.link(); // This first node is not guaranteed to be correct here. // As it may be a comment node that is removed afterwards. // but we link it anyways. - let (node, bundle) = Bundle::hydrate(root, &scope, parent, fragment, new_root); + let (node, bundle) = Bundle::hydrate(root, scope, parent, fragment, new_root); // We trim all text nodes before checking as it's likely these are whitespaces. fragment.trim_start_text_nodes(parent); @@ -577,9 +465,11 @@ mod feat_csr { } } - let should_render = |props: Option>, state: &mut ComponentState| -> bool { - props.map(|m| state.inner.props_changed(m)).unwrap_or(false) - }; + let should_render = + |_props: Option>, _state: &mut ComponentState| -> bool { + // TODO: Add Props Change back. + true + }; #[cfg(feature = "hydration")] let should_render_hydration = @@ -588,7 +478,9 @@ mod feat_csr { match state.has_rendered { true => { state.pending_props = None; - state.inner.props_changed(props) + // state.inner.props_changed(props) + // TODO: Add Props Change back. + true } false => { state.pending_props = Some(props); @@ -604,7 +496,7 @@ mod feat_csr { let schedule_render = { #[cfg(feature = "hydration")] { - if self.inner.creation_mode() == RenderMode::Hydration { + if self.context.creation_mode() == RenderMode::Hydration { should_render_hydration(props, self) } else { should_render(props, self) @@ -659,7 +551,7 @@ mod feat_csr { )] fn rendered(&mut self) -> bool { if self.suspension.is_none() { - self.inner.rendered(); + self.component.rendered(); } #[cfg(feature = "hydration")] diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index eb8be89d4fe..f869f99c806 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -17,7 +17,7 @@ pub use properties::*; pub(crate) use scope::Scoped; pub use scope::{AnyScope, Scope}; -use super::HtmlResult; +use crate::FunctionComponent; #[cfg(feature = "hydration")] #[derive(Debug, Clone, Copy, PartialEq)] @@ -51,7 +51,7 @@ impl Context { /// The component's props #[inline] pub fn props(&self) -> Rc { - self.scope.any_props().unwrap() + self.props.clone() } #[cfg(feature = "hydration")] @@ -92,17 +92,5 @@ pub trait BaseComponent: Sized + 'static { type Properties: Properties; /// Creates a component. - fn create(ctx: &Context) -> Self; - - /// Returns a component layout to be rendered. - fn view(&self, ctx: &Context) -> HtmlResult; - - /// Notified after a layout is rendered. - fn rendered(&mut self, ctx: &Context); - - /// Notified before a component is destroyed. - fn destroy(&mut self, ctx: &Context); - - /// Prepares the server-side state. - fn prepare_state(&self) -> Option; + fn create(ctx: &Context) -> FunctionComponent; } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 03a6abb4cfe..0d4f0dadd7c 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -14,9 +14,11 @@ use super::lifecycle::RenderRunner; use super::BaseComponent; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; -use crate::scheduler; +#[cfg(feature = "hydration")] +use crate::html::RenderMode; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; +use crate::{scheduler, FunctionComponent}; thread_local! { static PROPS: RefCell>> = RefCell::default(); @@ -71,10 +73,6 @@ impl AnyScope { scheduler::start(); } - pub(crate) fn any_props(&self) -> Option> { - PROPS.with(|m| m.borrow().get(&self.get_id()).cloned()) - } - /// Returns the parent scope pub fn get_parent(&self) -> Option<&AnyScope> { self.parent.as_deref() @@ -169,7 +167,7 @@ impl Scope { } /// Returns the linked component if available - pub fn get_component(&self) -> Option + '_> { + pub fn get_component(&self) -> Option + '_> { self.arch_get_component() } } @@ -182,10 +180,11 @@ mod feat_ssr { use crate::html::component::lifecycle::{ ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; + use crate::html::RenderMode; use crate::platform::fmt::BufWriter; use crate::platform::pinned::oneshot; - use crate::scheduler; use crate::virtual_dom::Collectable; + use crate::{scheduler, Context}; impl Scope { pub(crate) async fn render_into_stream( @@ -201,13 +200,22 @@ mod feat_ssr { let (tx, rx) = oneshot::channel(); let state = ComponentRenderState::Ssr { sender: Some(tx) }; + let context = Context { + scope: self.to_any(), + props: props as Rc, + #[cfg(feature = "hydration")] + creation_mode: RenderMode::Ssr, + #[cfg(feature = "hydration")] + prepared_state: None, + }; + + let component = COMP::create(&context); + CreateRunner { initial_render_state: state, - props, scope: self.clone().to_any(), - #[cfg(feature = "hydration")] - prepared_state: None, - _marker: PhantomData::, + context, + component, } .run(); RenderRunner { @@ -253,6 +261,7 @@ mod feat_csr_ssr { use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; + use crate::FunctionComponent; static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -273,29 +282,13 @@ mod feat_csr_ssr { } } - #[rustversion::before(1.63)] - #[inline] - pub(super) fn arch_get_component(&self) -> Option + '_> { - self.state.try_borrow().ok().and_then(|state_ref| { - state_ref.as_ref()?; - Some(Ref::map(state_ref, |state| { - state - .as_ref() - .and_then(|m| m.downcast_comp_ref::()) - .unwrap() - })) - }) - } - - #[rustversion::since(1.63)] #[inline] - pub(super) fn arch_get_component(&self) -> Option + '_> { + pub(super) fn arch_get_component( + &self, + ) -> Option + '_> { self.state.try_borrow().ok().and_then(|state_ref| { // Ref::filter_map is only available since 1.63 - Ref::filter_map(state_ref, |state| { - state.as_ref().and_then(|m| m.downcast_comp_ref::()) - }) - .ok() + Ref::filter_map(state_ref, |state| state.as_ref().map(|m| &m.component)).ok() }) } } @@ -313,6 +306,7 @@ mod feat_csr { ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner, }; use crate::html::NodeRef; + use crate::Context; impl AnyScope { #[cfg(test)] @@ -368,13 +362,22 @@ mod feat_csr { next_sibling: stable_next_sibling, }; + let context = Context { + scope: self.to_any(), + props: props as Rc, + #[cfg(feature = "hydration")] + creation_mode: RenderMode::Render, + #[cfg(feature = "hydration")] + prepared_state: None, + }; + + let component = COMP::create(&context); + CreateRunner { initial_render_state: state, - props, scope: self.clone().to_any(), - #[cfg(feature = "hydration")] - prepared_state: None, - _marker: PhantomData::, + context, + component, } .run(); RenderRunner { @@ -449,6 +452,7 @@ mod feat_hydration { use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner}; use crate::html::NodeRef; use crate::virtual_dom::Collectable; + use crate::Context; impl Scope where @@ -512,12 +516,20 @@ mod feat_hydration { fragment, }; + let context = Context { + scope: self.to_any(), + props: props as Rc, + creation_mode: RenderMode::Hydration, + prepared_state, + }; + + let component = COMP::create(&context); + CreateRunner { initial_render_state: state, - props, scope: self.clone().to_any(), - prepared_state, - _marker: PhantomData::, + context, + component, } .run(); RenderRunner { diff --git a/packages/yew/src/tests/layout_tests.rs b/packages/yew/src/tests/layout_tests.rs index 034acf318f5..b1f4fa6319b 100644 --- a/packages/yew/src/tests/layout_tests.rs +++ b/packages/yew/src/tests/layout_tests.rs @@ -7,29 +7,13 @@ use yew::NodeRef; use crate::dom_bundle::{BSubtree, Bundle}; use crate::html::AnyScope; use crate::virtual_dom::VNode; -use crate::{scheduler, BaseComponent, Context, HtmlResult}; +use crate::{scheduler, BaseComponent, Context, FunctionComponent}; struct Comp; impl BaseComponent for Comp { type Properties = (); - fn create(_: &Context) -> Self { - unimplemented!() - } - - fn view(&self, _ctx: &Context) -> HtmlResult { - unimplemented!() - } - - fn destroy(&mut self, _ctx: &Context) { - unimplemented!() - } - - fn prepare_state(&self) -> Option { - unimplemented!() - } - - fn rendered(&mut self, _ctx: &Context) { + fn create(_: &Context) -> FunctionComponent { unimplemented!() } } From 3f7fc52b28748aaa98420cee1e0668a03ef06fb4 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 20:53:57 +0900 Subject: [PATCH 11/47] Add props check. --- packages/yew-macro/src/function_component.rs | 7 ++- packages/yew/src/functional/mod.rs | 59 ++++++++++++++------ packages/yew/src/html/component/lifecycle.rs | 21 ++++--- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index 28ca39c2647..319c83743ed 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -326,6 +326,12 @@ impl FunctionComponent { impl #impl_generics ::yew::functional::FunctionProvider for #component_name #ty_generics #where_clause { type Properties = #props_type; + fn create() -> Self { + Self { + _marker: ::std::marker::PhantomData, + } + } + fn run(#ctx_ident: &mut ::yew::functional::HookContext, #component_props: &Self::Properties) -> ::yew::html::HtmlResult { #func @@ -349,7 +355,6 @@ impl FunctionComponent { #[allow(unused_parens)] #vis struct #component_name #generics #where_clause { _marker: ::std::marker::PhantomData<(#phantom_generics)>, - function_component: ::yew::functional::FunctionComponent, } } } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 5b8b6aed2d9..898f730562c 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -297,6 +297,9 @@ pub trait FunctionProvider { /// Properties for the Function Component. type Properties: Properties + PartialEq; + /// Creates an instance of function provider. + fn create() -> Self; + /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the /// component. /// @@ -304,12 +307,39 @@ pub trait FunctionProvider { fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult; } +pub(crate) trait AnyFunctionProvider { + fn run(&self, ctx: &mut HookContext, props: &dyn Any) -> HtmlResult; + fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool; +} + +impl AnyFunctionProvider for T +where + T: FunctionProvider + 'static, +{ + fn run(&self, ctx: &mut HookContext, props: &dyn Any) -> HtmlResult { + let props = match props.downcast_ref() { + Some(m) => m, + None => return Ok(Html::default()), + }; + + T::run(ctx, props) + } + + fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool { + match ( + last_props.downcast_ref::(), + next_props.downcast_ref::(), + ) { + (Some(l), Some(r)) => l == r, + _ => false, + } + } +} + thread_local! { static FC_CONTEXTS: RefCell> = RefCell::default(); } -type MakeHtml = Rc HtmlResult>; - /// A type that interacts [`FunctionProvider`] to provide lifecycle events to be bridged to /// [`BaseComponent`]. /// @@ -320,7 +350,7 @@ type MakeHtml = Rc HtmlResult>; /// Use the `#[function_component]` macro instead. #[doc(hidden)] pub struct FunctionComponent { - run_once: MakeHtml, + inner: Rc, hook_ctx: RefCell, } @@ -328,21 +358,14 @@ impl FunctionComponent { /// Creates a new function component. pub fn new(ctx: &Context) -> Self where - T: BaseComponent + FunctionProvider + 'static, + T: Sized + BaseComponent + FunctionProvider + 'static, { - let run_once = Rc::new(|ctx: &mut HookContext, props: &dyn Any| { - let props = match props.downcast_ref() { - Some(m) => m, - None => return Ok(Html::default()), - }; - - T::run(ctx, props) - }); + let inner = Rc::new(::create()); - Self::new_any(ctx, run_once) + Self::new_any(ctx, inner) } - fn new_any(ctx: &Context, run_once: MakeHtml) -> Self { + fn new_any(ctx: &Context, inner: Rc) -> Self { let scope = ctx.link().clone(); let re_render = { let link = ctx.link().clone(); @@ -350,7 +373,7 @@ impl FunctionComponent { }; Self { - run_once, + inner, hook_ctx: HookContext::new( scope, re_render, @@ -362,6 +385,10 @@ impl FunctionComponent { } } + pub(crate) fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool { + self.inner.props_eq(last_props, next_props) + } + /// Renders a function component. pub fn render(&self, props: Rc) -> HtmlResult { let mut hook_ctx = self.hook_ctx.borrow_mut(); @@ -369,7 +396,7 @@ impl FunctionComponent { hook_ctx.prepare_run(); #[allow(clippy::let_and_return)] - let result = (self.run_once)(&mut hook_ctx, &props); + let result = self.inner.run(&mut hook_ctx, &props); #[cfg(debug_assertions)] hook_ctx.assert_hook_context(result.is_ok()); diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 3512e7b09b5..8729502ac29 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -465,11 +465,17 @@ mod feat_csr { } } - let should_render = - |_props: Option>, _state: &mut ComponentState| -> bool { - // TODO: Add Props Change back. - true - }; + let should_render = |props: Option>, state: &mut ComponentState| -> bool { + props + .and_then(|m| { + (!state.component.props_eq(&state.context.props(), &m)).then_some(m) + }) + .map(|m| { + state.context.props = m; + true + }) + .unwrap_or(false) + }; #[cfg(feature = "hydration")] let should_render_hydration = @@ -478,8 +484,9 @@ mod feat_csr { match state.has_rendered { true => { state.pending_props = None; - // state.inner.props_changed(props) - // TODO: Add Props Change back. + if !state.component.props_eq(&state.context.props(), &props) { + state.context.props = props; + } true } false => { From 7f7f2088293f112cd64e33c5b056bb74321d5b79 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 22:39:26 +0900 Subject: [PATCH 12/47] Remove more lifecycles. --- examples/Cargo.lock | 1 + examples/ssr_router/Cargo.toml | 1 + packages/yew/src/app_handle.rs | 12 -- packages/yew/src/functional/mod.rs | 17 +-- packages/yew/src/html/component/lifecycle.rs | 65 ++++------ packages/yew/src/html/component/mod.rs | 7 +- packages/yew/src/html/component/scope.rs | 100 ++++++-------- packages/yew/src/scheduler.rs | 129 ++----------------- packages/yew/src/suspense/component.rs | 10 +- 9 files changed, 93 insertions(+), 249 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 1936e732bdd..3c003684b52 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -2116,6 +2116,7 @@ dependencies = [ "tokio", "tower", "tower-http", + "tracing", "wasm-bindgen-futures", "wasm-logger", "yew", diff --git a/examples/ssr_router/Cargo.toml b/examples/ssr_router/Cargo.toml index ca20bb14b9d..f4a640d53c3 100644 --- a/examples/ssr_router/Cargo.toml +++ b/examples/ssr_router/Cargo.toml @@ -32,6 +32,7 @@ env_logger = "0.9" clap = { version = "3.1.7", features = ["derive"] } hyper = { version = "0.14", features = ["server", "http1"] } jemallocator = "0.5" +tracing = { version = "0.1", features = ["log-always"] } [features] ssr = ["yew/ssr"] diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index bcd330e9e73..9df13622ac1 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -1,6 +1,5 @@ //! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope. -use std::ops::Deref; use std::rc::Rc; use web_sys::Element; @@ -56,17 +55,6 @@ where } } -impl Deref for AppHandle -where - COMP: BaseComponent, -{ - type Target = Scope; - - fn deref(&self) -> &Self::Target { - &self.scope - } -} - /// Removes anything from the given element. fn clear_element(host: &Element) { while let Some(child) = host.last_child() { diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 898f730562c..d0f1b455c71 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -22,7 +22,6 @@ use std::any::Any; use std::cell::RefCell; -use std::collections::HashMap; use std::fmt; use std::rc::Rc; @@ -317,7 +316,7 @@ where T: FunctionProvider + 'static, { fn run(&self, ctx: &mut HookContext, props: &dyn Any) -> HtmlResult { - let props = match props.downcast_ref() { + let props = match props.downcast_ref::() { Some(m) => m, None => return Ok(Html::default()), }; @@ -336,10 +335,6 @@ where } } -thread_local! { - static FC_CONTEXTS: RefCell> = RefCell::default(); -} - /// A type that interacts [`FunctionProvider`] to provide lifecycle events to be bridged to /// [`BaseComponent`]. /// @@ -350,7 +345,7 @@ thread_local! { /// Use the `#[function_component]` macro instead. #[doc(hidden)] pub struct FunctionComponent { - inner: Rc, + inner: Box, hook_ctx: RefCell, } @@ -360,12 +355,12 @@ impl FunctionComponent { where T: Sized + BaseComponent + FunctionProvider + 'static, { - let inner = Rc::new(::create()); + let inner = Box::new(::create()); Self::new_any(ctx, inner) } - fn new_any(ctx: &Context, inner: Rc) -> Self { + fn new_any(ctx: &Context, inner: Box) -> Self { let scope = ctx.link().clone(); let re_render = { let link = ctx.link().clone(); @@ -390,13 +385,13 @@ impl FunctionComponent { } /// Renders a function component. - pub fn render(&self, props: Rc) -> HtmlResult { + pub fn render(&self, props: &dyn Any) -> HtmlResult { let mut hook_ctx = self.hook_ctx.borrow_mut(); hook_ctx.prepare_run(); #[allow(clippy::let_and_return)] - let result = self.inner.run(&mut hook_ctx, &props); + let result = self.inner.run(&mut hook_ctx, props); #[cfg(debug_assertions)] hook_ctx.assert_hook_context(result.is_ok()); diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 8729502ac29..f3eacd0f041 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -20,7 +20,7 @@ use crate::scheduler::{self, Shared}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; use crate::{Callback, Context, ContextProvider, FunctionComponent}; -pub(crate) enum ComponentRenderState { +pub(crate) enum Rendered { #[cfg(feature = "csr")] Render { bundle: Bundle, @@ -43,7 +43,7 @@ pub(crate) enum ComponentRenderState { }, } -impl std::fmt::Debug for ComponentRenderState { +impl std::fmt::Debug for Rendered { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(feature = "csr")] @@ -54,7 +54,7 @@ impl std::fmt::Debug for ComponentRenderState { ref next_sibling, ref internal_ref, } => f - .debug_struct("ComponentRenderState::Render") + .debug_struct("Rendered::Render") .field("bundle", bundle) .field("root", root) .field("parent", parent) @@ -70,7 +70,7 @@ impl std::fmt::Debug for ComponentRenderState { ref internal_ref, ref root, } => f - .debug_struct("ComponentRenderState::Hydration") + .debug_struct("Rendered::Hydration") .field("fragment", fragment) .field("root", root) .field("parent", parent) @@ -85,7 +85,7 @@ impl std::fmt::Debug for ComponentRenderState { None => "None", }; - f.debug_struct("ComponentRenderState::Ssr") + f.debug_struct("Rendered::Ssr") .field("sender", &sender_repr) .finish() } @@ -94,7 +94,7 @@ impl std::fmt::Debug for ComponentRenderState { } #[cfg(feature = "csr")] -impl ComponentRenderState { +impl Rendered { pub(crate) fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) { match self { #[cfg(feature = "csr")] @@ -135,7 +135,7 @@ pub(crate) struct ComponentState { pub(super) component: FunctionComponent, pub(super) context: Context, - pub(super) render_state: ComponentRenderState, + pub(super) render_state: Rendered, #[cfg(feature = "csr")] has_rendered: bool, @@ -157,7 +157,7 @@ impl ComponentState { fn new( component: FunctionComponent, context: Context, - initial_render_state: ComponentRenderState, + initial_render_state: Rendered, scope: AnyScope, ) -> Self { let comp_id = scope.get_id(); @@ -190,7 +190,7 @@ impl ComponentState { } pub(crate) struct CreateRunner { - pub initial_render_state: ComponentRenderState, + pub initial_render_state: Rendered, pub scope: AnyScope, pub component: FunctionComponent, pub context: Context, @@ -227,7 +227,7 @@ impl ComponentState { match self.render_state { #[cfg(feature = "csr")] - ComponentRenderState::Render { + Rendered::Render { bundle, ref parent, ref internal_ref, @@ -240,7 +240,7 @@ impl ComponentState { } // We need to detach the hydrate fragment if the component is not hydrated. #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { + Rendered::Hydration { ref root, fragment, ref parent, @@ -253,7 +253,7 @@ impl ComponentState { } #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => { + Rendered::Ssr { .. } => { let _ = parent_to_detach; } } @@ -279,7 +279,7 @@ impl ComponentState { fields(component.id = self.comp_id) )] fn render(&mut self, shared_state: &Shared>) { - match self.component.render(self.context.props()) { + match self.component.render(self.context.props().as_ref()) { Ok(vnode) => self.commit_render(shared_state, vnode), Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp), }; @@ -295,7 +295,7 @@ impl ComponentState { }; // schedule a render immediately if suspension is resumed. - scheduler::push_component_render(self.comp_id, move || runner.run()); + scheduler::push(move || runner.run()); } else { // We schedule a render after current suspension is resumed. let comp_scope = self.context.link(); @@ -304,15 +304,13 @@ impl ComponentState { .find_parent_scope::>() .expect("To suspend rendering, a component is required."); - let comp_id = self.comp_id; let shared_state = shared_state.clone(); suspension.listen(Callback::from(move |_| { let runner = RenderRunner { state: shared_state.clone(), }; - scheduler::push_component_render(comp_id, move || runner.run()); - scheduler::start(); + scheduler::push(move || runner.run()); })); if let Some(ref last_suspension) = self.suspension { @@ -334,7 +332,7 @@ impl ComponentState { match self.render_state { #[cfg(feature = "csr")] - ComponentRenderState::Render { + Rendered::Render { ref mut bundle, ref parent, ref root, @@ -351,22 +349,16 @@ impl ComponentState { bundle.reconcile(root, scope, parent, next_sibling.clone(), new_root); internal_ref.link(new_node_ref); - let first_render = !self.has_rendered; self.has_rendered = true; - let runner = RenderedRunner { + RenderedRunner { state: shared_state.clone(), - }; - - scheduler::push_component_rendered( - self.comp_id, - move || runner.run(), - first_render, - ); + } + .run(); } #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { + Rendered::Hydration { ref mut fragment, ref parent, ref internal_ref, @@ -387,7 +379,7 @@ impl ComponentState { internal_ref.link(node); - self.render_state = ComponentRenderState::Render { + self.render_state = Rendered::Render { root: root.clone(), bundle, parent: parent.clone(), @@ -397,7 +389,7 @@ impl ComponentState { } #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { ref mut sender } => { + Rendered::Ssr { ref mut sender } => { let _ = shared_state; if let Some(tx) = sender.take() { tx.send(new_root).unwrap(); @@ -442,7 +434,7 @@ mod feat_csr { // components. match self.render_state { #[cfg(feature = "csr")] - ComponentRenderState::Render { + Rendered::Render { next_sibling: ref current_next_sibling, .. } => { @@ -450,7 +442,7 @@ mod feat_csr { } #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { + Rendered::Hydration { next_sibling: ref current_next_sibling, .. } => { @@ -458,7 +450,7 @@ mod feat_csr { } #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => { + Rendered::Ssr { .. } => { #[cfg(debug_assertions)] panic!("properties do not change during SSR"); } @@ -468,7 +460,7 @@ mod feat_csr { let should_render = |props: Option>, state: &mut ComponentState| -> bool { props .and_then(|m| { - (!state.component.props_eq(&state.context.props(), &m)).then_some(m) + (!state.component.props_eq(state.context.props(), &m)).then_some(m) }) .map(|m| { state.context.props = m; @@ -484,7 +476,7 @@ mod feat_csr { match state.has_rendered { true => { state.pending_props = None; - if !state.component.props_eq(&state.context.props(), &props) { + if !state.component.props_eq(state.context.props(), &props) { state.context.props = props; } true @@ -539,8 +531,7 @@ mod feat_csr { }; if schedule_render { - scheduler::push_component_render(state.comp_id, move || runner.run()); - // Only run from the scheduler, so no need to call `scheduler::start()` + scheduler::push(move || runner.run()); } }; } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index f869f99c806..39c7c37cf59 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -13,9 +13,10 @@ use std::rc::Rc; pub use children::*; pub use marker::*; pub use properties::*; +pub use scope::AnyScope; +pub(crate) use scope::Scope; #[cfg(feature = "csr")] pub(crate) use scope::Scoped; -pub use scope::{AnyScope, Scope}; use crate::FunctionComponent; @@ -50,8 +51,8 @@ impl Context { /// The component's props #[inline] - pub fn props(&self) -> Rc { - self.props.clone() + pub fn props(&self) -> &Rc { + &self.props } #[cfg(feature = "hydration")] diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 0d4f0dadd7c..268839cc9f5 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -1,10 +1,9 @@ //! Component scope module use std::any::{Any, TypeId}; -use std::cell::RefCell; -use std::collections::HashMap; +// use std::cell::RefCell; +// use std::collections::HashMap; use std::marker::PhantomData; -use std::ops::Deref; use std::rc::Rc; use std::{fmt, iter}; @@ -16,13 +15,13 @@ use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; #[cfg(feature = "hydration")] use crate::html::RenderMode; +use crate::scheduler; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; -use crate::{scheduler, FunctionComponent}; -thread_local! { - static PROPS: RefCell>> = RefCell::default(); -} +// thread_local! { +// static PROPS: RefCell>> = RefCell::default(); +// } /// Untyped scope used for accessing parent scope #[derive(Clone)] @@ -69,8 +68,7 @@ impl AnyScope { state: self.state.clone(), }; - scheduler::push_component_render(self.id, move || runner.run()); - scheduler::start(); + scheduler::push(move || runner.run()); } /// Returns the parent scope @@ -88,21 +86,22 @@ impl AnyScope { /// # Panics /// /// If the self value can't be cast into the target type. - pub fn downcast(&self) -> Scope { + #[cfg(feature = "csr")] + pub(crate) fn downcast(&self) -> Scope { self.try_downcast::().unwrap() } /// Attempts to downcast into a typed scope /// /// Returns [`None`] if the self value can't be cast into the target type. - pub fn try_downcast(&self) -> Option> { + pub(crate) fn try_downcast(&self) -> Option> { self.typed_scope.downcast_ref::>().cloned() } /// Attempts to find a parent scope of a certain type /// /// Returns [`None`] if no parent scope with the specified type was found. - pub fn find_parent_scope(&self) -> Option> { + pub(crate) fn find_parent_scope(&self) -> Option> { iter::successors(Some(self), |scope| scope.get_parent()) .find_map(AnyScope::try_downcast::) } @@ -114,13 +113,13 @@ impl AnyScope { callback: Callback, ) -> Option<(T, ContextHandle)> { let scope = self.find_parent_scope::>()?; - let store = ContextStore::::get(&scope.to_any())?; + let store = ContextStore::::get(&AnyScope::from(scope))?; Some(ContextStore::subscribe_consumer(store, callback)) } } /// A context which allows sending messages to a component. -pub struct Scope { +pub(crate) struct Scope { _marker: PhantomData, parent: Option>, @@ -151,42 +150,31 @@ impl Clone for Scope { } } -impl Scope { - /// Returns the parent scope - pub fn get_parent(&self) -> Option<&AnyScope> { - self.parent.as_deref() - } - - /// Accesses a value provided by a parent `ContextProvider` component of the - /// same type. - pub fn context( - &self, - callback: Callback, - ) -> Option<(T, ContextHandle)> { - AnyScope::from(self.clone()).context(callback) - } - - /// Returns the linked component if available - pub fn get_component(&self) -> Option + '_> { - self.arch_get_component() - } -} - #[cfg(feature = "ssr")] mod feat_ssr { + use std::cell::Ref; use std::fmt::Write; + use std::ops::Deref; use super::*; - use crate::html::component::lifecycle::{ - ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, - }; + use crate::functional::FunctionComponent; + use crate::html::component::lifecycle::{CreateRunner, DestroyRunner, RenderRunner, Rendered}; + #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::platform::fmt::BufWriter; use crate::platform::pinned::oneshot; use crate::virtual_dom::Collectable; - use crate::{scheduler, Context}; + use crate::Context; impl Scope { + /// Returns the linked component if available + pub(crate) fn get_component(&self) -> Option + '_> { + self.state.try_borrow().ok().and_then(|state_ref| { + // Ref::filter_map is only available since 1.63 + Ref::filter_map(state_ref, |state| state.as_ref().map(|m| &m.component)).ok() + }) + } + pub(crate) async fn render_into_stream( &self, w: &mut BufWriter, @@ -198,10 +186,10 @@ mod feat_ssr { // If the content of this channel is ready before it is awaited, it is // similar to taking the value from a mutex lock. let (tx, rx) = oneshot::channel(); - let state = ComponentRenderState::Ssr { sender: Some(tx) }; + let state = Rendered::Ssr { sender: Some(tx) }; let context = Context { - scope: self.to_any(), + scope: AnyScope::from(self.clone()), props: props as Rc, #[cfg(feature = "hydration")] creation_mode: RenderMode::Ssr, @@ -213,7 +201,7 @@ mod feat_ssr { CreateRunner { initial_render_state: state, - scope: self.clone().to_any(), + scope: AnyScope::from(self.clone()), context, component, } @@ -250,18 +238,16 @@ mod feat_ssr { parent_to_detach: false, } .run(); - scheduler::start(); } } } #[cfg(any(feature = "ssr", feature = "csr"))] mod feat_csr_ssr { - use std::cell::{Ref, RefCell}; + use std::cell::RefCell; use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; - use crate::FunctionComponent; static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -281,16 +267,6 @@ mod feat_csr_ssr { id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst), } } - - #[inline] - pub(super) fn arch_get_component( - &self, - ) -> Option + '_> { - self.state.try_borrow().ok().and_then(|state_ref| { - // Ref::filter_map is only available since 1.63 - Ref::filter_map(state_ref, |state| state.as_ref().map(|m| &m.component)).ok() - }) - } } } @@ -303,7 +279,7 @@ mod feat_csr { use super::*; use crate::dom_bundle::{BSubtree, Bundle}; use crate::html::component::lifecycle::{ - ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner, + CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner, Rendered, }; use crate::html::NodeRef; use crate::Context; @@ -352,9 +328,9 @@ mod feat_csr { let stable_next_sibling = NodeRef::default(); stable_next_sibling.link(next_sibling); - PROPS.with(|m| m.borrow_mut().insert(self.id, props.clone())); + // PROPS.with(|m| m.borrow_mut().insert(self.id, props.clone())); - let state = ComponentRenderState::Render { + let state = Rendered::Render { bundle, root, internal_ref, @@ -394,7 +370,7 @@ mod feat_csr { pub(crate) trait Scoped { fn to_any(&self) -> AnyScope; /// Get the render state if it hasn't already been destroyed - fn render_state(&self) -> Option>; + fn render_state(&self) -> Option>; /// Shift the node associated with this scope to a new place fn shift_node(&self, parent: Element, next_sibling: NodeRef); /// Process an event to destroy a component @@ -407,7 +383,7 @@ mod feat_csr { self.clone().into() } - fn render_state(&self) -> Option> { + fn render_state(&self) -> Option> { let state_ref = self.state.borrow(); // check that component hasn't been destroyed @@ -449,7 +425,7 @@ mod feat_hydration { use super::*; use crate::dom_bundle::{BSubtree, Fragment}; - use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner}; + use crate::html::component::lifecycle::{CreateRunner, RenderRunner, Rendered}; use crate::html::NodeRef; use crate::virtual_dom::Collectable; use crate::Context; @@ -508,7 +484,7 @@ mod feat_hydration { _ => None, }; - let state = ComponentRenderState::Hydration { + let state = Rendered::Hydration { parent, root, internal_ref, diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index dc3610d509e..2c1d9d854c1 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -1,9 +1,10 @@ //! This module contains a scheduler. use std::cell::RefCell; -use std::collections::BTreeMap; use std::rc::Rc; +use crate::platform::spawn_local; + /// Alias for `Rc>` pub type Shared = Rc>; @@ -25,47 +26,12 @@ impl FifoQueue { } } -#[derive(Default)] -struct TopologicalQueue { - /// The Binary Tree Map guarantees components with lower id (parent) is rendered first - inner: BTreeMap, -} - -impl TopologicalQueue { - #[cfg(any(feature = "ssr", feature = "csr"))] - fn push(&mut self, component_id: usize, task: Runnable) { - self.inner.insert(component_id, task); - } - - /// Take a single entry, preferring parents over children - fn pop_topmost(&mut self) -> Option { - // To be replaced with BTreeMap::pop_first once it is stable. - let key = *self.inner.keys().next()?; - self.inner.remove(&key) - } - - /// Drain all entries, such that children are queued before parents - fn drain_post_order_into(&mut self, queue: &mut Vec) { - if self.inner.is_empty() { - return; - } - let rendered = std::mem::take(&mut self.inner); - // Children rendered lifecycle happen before parents. - queue.extend(rendered.into_values().rev()); - } -} - /// This is a global scheduler suitable to schedule and run any tasks. #[derive(Default)] #[allow(missing_debug_implementations)] // todo struct Scheduler { // Main queue main: FifoQueue, - - render: TopologicalQueue, - - rendered_first: TopologicalQueue, - rendered: TopologicalQueue, } /// Execute closure with a mutable reference to the scheduler @@ -93,44 +59,6 @@ where start(); } -#[cfg(any(feature = "ssr", feature = "csr"))] -mod feat_csr_ssr { - use super::*; - /// Push a component render [Runnable]s to be executed - pub(crate) fn push_component_render(component_id: usize, render: F) - where - F: FnOnce() + 'static, - { - with(|s| { - s.render.push(component_id, Box::new(render)); - }); - } -} - -#[cfg(any(feature = "ssr", feature = "csr"))] -pub(crate) use feat_csr_ssr::*; - -#[cfg(feature = "csr")] -mod feat_csr { - use super::*; - - pub(crate) fn push_component_rendered(component_id: usize, rendered: F, first_render: bool) - where - F: FnOnce() + 'static, - { - with(|s| { - if first_render { - s.rendered_first.push(component_id, Box::new(rendered)); - } else { - s.rendered.push(component_id, Box::new(rendered)); - } - }); - } -} - -#[cfg(feature = "csr")] -pub(crate) use feat_csr::*; - /// Execute any pending [Runnable]s pub(crate) fn start_now() { #[tracing::instrument(level = tracing::Level::DEBUG)] @@ -160,33 +88,14 @@ pub(crate) fn start_now() { }); } -#[cfg(target_arch = "wasm32")] -mod arch { - use crate::platform::spawn_local; - - /// We delay the start of the scheduler to the end of the micro task queue. - /// So any messages that needs to be queued can be queued. - pub(crate) fn start() { - spawn_local(async { - super::start_now(); - }); - } -} - -#[cfg(not(target_arch = "wasm32"))] -mod arch { - // Delayed rendering is not very useful in the context of server-side rendering. - // There are no event listeners or other high priority events that need to be - // processed and we risk of having a future un-finished. - // Until scheduler is future-capable which means we can join inside a future, - // it can remain synchronous. - pub(crate) fn start() { - super::start_now(); - } +/// We delay the start of the scheduler to the end of the micro task queue. +/// So any messages that needs to be queued can be queued. +pub(crate) fn start() { + spawn_local(async { + start_now(); + }); } -pub(crate) use arch::*; - impl Scheduler { /// Fill vector with tasks to be executed according to Runnable type execution priority /// @@ -194,30 +103,8 @@ impl Scheduler { /// non-typical usage (like scheduling renders in [crate::Component::create()] or /// [crate::Component::rendered()] calls). fn fill_queue(&mut self, to_run: &mut Vec) { - // Children rendered lifecycle happen before parents. - self.rendered_first.drain_post_order_into(to_run); - // Likely to cause duplicate renders via component updates, so placed before them self.main.drain_into(to_run); - - // Run after all possible updates to avoid duplicate renders. - // - // Should be processed one at time, because they can spawn more create and first render - // events for their children. - if !to_run.is_empty() { - return; - } - - // Should be processed one at time, because they can spawn more create and rendered events - // for their children. - if let Some(r) = self.render.pop_topmost() { - to_run.push(r); - return; - } - // These typically do nothing and don't spawn any other events - can be batched. - // Should be run only after all renders have finished. - // Children rendered lifecycle happen before parents. - self.rendered.drain_post_order_into(to_run); } } diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 241f517012a..8e70d51c44f 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -18,7 +18,7 @@ mod feat_csr_ssr { use super::*; use crate::context::ContextStore; - use crate::html::{Children, Html, Scope, Scoped}; + use crate::html::{AnyScope, Children, Html, Scope}; use crate::suspense::Suspension; use crate::virtual_dom::{VNode, VSuspense}; use crate::{ @@ -103,7 +103,9 @@ mod feat_csr_ssr { provider: &Scope>, s: Suspension, ) { - if let Some(provider) = ContextStore::::get(&provider.to_any()) { + if let Some(provider) = + ContextStore::::get(&AnyScope::from(provider.clone())) + { let context = provider.borrow().get_context_value(); context.dispatch(BaseSuspenseMsg::Resume(s)); } @@ -113,7 +115,9 @@ mod feat_csr_ssr { provider: &Scope>, s: Suspension, ) { - if let Some(provider) = ContextStore::::get(&provider.to_any()) { + if let Some(provider) = + ContextStore::::get(&AnyScope::from(provider.clone())) + { let context = provider.borrow().get_context_value(); context.dispatch(BaseSuspenseMsg::Suspend(s)); } From f3db00d05440d79dbdf6a785b934909d9ad7f959 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 8 Nov 2022 23:34:40 +0900 Subject: [PATCH 13/47] Remove extra render cycles. --- packages/yew/src/app_handle.rs | 60 ++++++++++++-------- packages/yew/src/html/component/lifecycle.rs | 54 +++++------------- packages/yew/src/renderer.rs | 1 + tools/Cargo.lock | 36 ++++++------ 4 files changed, 71 insertions(+), 80 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 9df13622ac1..7fb0548c811 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -6,6 +6,7 @@ use web_sys::Element; use crate::dom_bundle::BSubtree; use crate::html::{BaseComponent, NodeRef, Scope, Scoped}; +use crate::scheduler; /// An instance of an application. #[cfg(feature = "csr")] @@ -34,13 +35,19 @@ where scope: Scope::new(None), }; let hosting_root = BSubtree::create_root(&host); - app.scope.mount_in_place( - hosting_root, - host, - NodeRef::default(), - NodeRef::default(), - props, - ); + + { + let scope = app.scope.clone(); + scheduler::push(move || { + scope.mount_in_place( + hosting_root, + host, + NodeRef::default(), + NodeRef::default(), + props, + ); + }); + } app } @@ -51,7 +58,10 @@ where skip_all, )] pub fn destroy(self) { - self.scope.destroy(false) + let scope = self.scope; + scheduler::push(move || { + scope.destroy(false); + }); } } @@ -84,21 +94,25 @@ mod feat_hydration { let mut fragment = Fragment::collect_children(&host); let hosting_root = BSubtree::create_root(&host); - app.scope.hydrate_in_place( - hosting_root, - host.clone(), - &mut fragment, - NodeRef::default(), - Rc::clone(&props), - ); - #[cfg(debug_assertions)] // Fix trapped next_sibling at the root - app.scope.reuse(props, NodeRef::default()); - - // We remove all remaining nodes, this mimics the clear_element behaviour in - // mount_with_props. - for node in fragment.iter() { - host.remove_child(node).unwrap(); - } + let scope = app.scope.clone(); + + scheduler::push(move || { + scope.hydrate_in_place( + hosting_root, + host.clone(), + &mut fragment, + NodeRef::default(), + Rc::clone(&props), + ); + #[cfg(debug_assertions)] // Fix trapped next_sibling at the root + scope.reuse(props, NodeRef::default()); + + // We remove all remaining nodes, this mimics the clear_element behaviour in + // mount_with_props. + for node in fragment.iter() { + host.remove_child(node).unwrap(); + } + }); app } diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index f3eacd0f041..cf265abdeb4 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -290,12 +290,7 @@ impl ComponentState { // suspension to parent element. if suspension.resumed() { - let runner = RenderRunner { - state: shared_state.clone(), - }; - - // schedule a render immediately if suspension is resumed. - scheduler::push(move || runner.run()); + self.render(shared_state); } else { // We schedule a render after current suspension is resumed. let comp_scope = self.context.link(); @@ -351,10 +346,14 @@ impl ComponentState { self.has_rendered = true; - RenderedRunner { - state: shared_state.clone(), + let has_pending_props = self.rendered(); + if has_pending_props { + let should_render = self.changed(None, None); + + if should_render { + self.render(shared_state); + } } - .run(); } #[cfg(feature = "hydration")] @@ -427,7 +426,11 @@ mod feat_csr { skip(self), fields(component.id = self.comp_id) )] - fn changed(&mut self, props: Option>, next_sibling: Option) -> bool { + pub(super) fn changed( + &mut self, + props: Option>, + next_sibling: Option, + ) -> bool { if let Some(next_sibling) = next_sibling { // When components are updated, their siblings were likely also updated // We also need to shift the bundle so next sibling will be synced to child @@ -526,28 +529,20 @@ mod feat_csr { if let Some(state) = shared_state.borrow_mut().as_mut() { let schedule_render = state.changed(props, next_sibling); - let runner = RenderRunner { - state: shared_state.clone(), - }; - if schedule_render { - scheduler::push(move || runner.run()); + state.render(&shared_state); } }; } } - pub(crate) struct RenderedRunner { - pub state: Shared>, - } - impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), fields(component.id = self.comp_id) )] - fn rendered(&mut self) -> bool { + pub(super) fn rendered(&mut self) -> bool { if self.suspension.is_none() { self.component.rendered(); } @@ -562,24 +557,6 @@ mod feat_csr { } } } - - impl RenderedRunner { - pub fn run(self) { - if let Some(state) = self.state.borrow_mut().as_mut() { - let has_pending_props = state.rendered(); - - if has_pending_props { - let runner = PropsUpdateRunner { - state: self.state.clone(), - props: None, - next_sibling: None, - }; - - scheduler::push(move || runner.run()); - } - } - } - } } #[cfg(feature = "csr")] @@ -726,7 +703,6 @@ mod tests { NodeRef::default(), Rc::new(props), ); - crate::scheduler::start_now(); assert_eq!(&lifecycle.borrow_mut().deref()[..], expected); } diff --git a/packages/yew/src/renderer.rs b/packages/yew/src/renderer.rs index 442268a281c..8c52cdd696d 100644 --- a/packages/yew/src/renderer.rs +++ b/packages/yew/src/renderer.rs @@ -89,6 +89,7 @@ where /// Renders the application. pub fn render(self) -> AppHandle { set_default_panic_hook(); + AppHandle::::mount_with_props(self.root, Rc::new(self.props)) } } diff --git a/tools/Cargo.lock b/tools/Cargo.lock index 830b26deeb3..3739e4756a7 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -129,9 +129,9 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "41ca34107f97baef6cfb231b32f36115781856b8f8208e8c580e0bcaea374842" dependencies = [ "jobserver", ] @@ -270,9 +270,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ "cc", "cxxbridge-flags", @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" dependencies = [ "cc", "codespan-reporting", @@ -297,15 +297,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", @@ -814,9 +814,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -927,9 +927,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itoa" @@ -1443,9 +1443,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1454,9 +1454,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" From bc08fb8928af955ac1d026a361608eb6db4ba427 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 00:16:40 +0900 Subject: [PATCH 14/47] Remove all runners. --- packages/yew/src/html/component/lifecycle.rs | 147 +++++++------------ packages/yew/src/html/component/scope.rs | 87 +++-------- 2 files changed, 73 insertions(+), 161 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index cf265abdeb4..24c7bd2976f 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -152,15 +152,10 @@ impl ComponentState { level = tracing::Level::DEBUG, name = "create", skip_all, - fields(component.id = scope.get_id()), + fields(component.id = context.link().get_id()), )] - fn new( - component: FunctionComponent, - context: Context, - initial_render_state: Rendered, - scope: AnyScope, - ) -> Self { - let comp_id = scope.get_id(); + fn new(component: FunctionComponent, context: Context, initial_render_state: Rendered) -> Self { + let comp_id = context.scope.get_id(); Self { component, @@ -177,6 +172,50 @@ impl ComponentState { } } + pub fn run_create( + context: Context, + component: FunctionComponent, + initial_render_state: Rendered, + ) { + let state = context.scope.state.clone(); + let mut current_state = state.borrow_mut(); + + if current_state.is_none() { + let mut self_ = Self::new(component, context, initial_render_state); + self_.render(&state); + + // We are safe to assign afterwards as we mutably borrow the state and don't release it + // until this function returns. + *current_state = Some(self_); + } + } + + pub fn run_render(scope: &AnyScope) { + if let Some(state) = scope.state.borrow_mut().as_mut() { + state.render(&scope.state); + } + } + + pub fn run_update_props( + scope: &AnyScope, + props: Option>, + next_sibling: Option, + ) { + if let Some(state) = scope.state.borrow_mut().as_mut() { + let schedule_render = state.changed(props, next_sibling); + + if schedule_render { + state.render(&scope.state); + } + } + } + + pub fn run_destroy(scope: &AnyScope, parent_to_detach: bool) { + if let Some(state) = scope.state.borrow_mut().take() { + state.destroy(parent_to_detach); + } + } + fn resume_existing_suspension(&mut self) { if let Some(m) = self.suspension.take() { let comp_scope = self.context.link(); @@ -189,32 +228,6 @@ impl ComponentState { } } -pub(crate) struct CreateRunner { - pub initial_render_state: Rendered, - pub scope: AnyScope, - pub component: FunctionComponent, - pub context: Context, -} - -impl CreateRunner { - pub fn run(self) { - let mut current_state = self.scope.state.borrow_mut(); - if current_state.is_none() { - *current_state = Some(ComponentState::new( - self.component, - self.context, - self.initial_render_state, - self.scope.clone(), - )); - } - } -} - -pub(crate) struct DestroyRunner { - pub state: Shared>, - pub parent_to_detach: bool, -} - impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, @@ -260,18 +273,6 @@ impl ComponentState { } } -impl DestroyRunner { - pub fn run(self) { - if let Some(state) = self.state.borrow_mut().take() { - state.destroy(self.parent_to_detach); - } - } -} - -pub(crate) struct RenderRunner { - pub state: Shared>, -} - impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, @@ -299,14 +300,13 @@ impl ComponentState { .find_parent_scope::>() .expect("To suspend rendering, a component is required."); - let shared_state = shared_state.clone(); - suspension.listen(Callback::from(move |_| { - let runner = RenderRunner { - state: shared_state.clone(), - }; - - scheduler::push(move || runner.run()); - })); + { + let scope = self.context.link().clone(); + suspension.listen(Callback::from(move |_| { + let scope = scope.clone(); + scheduler::push(move || ComponentState::run_render(&scope)); + })); + } if let Some(ref last_suspension) = self.suspension { if &suspension != last_suspension { @@ -398,28 +398,10 @@ impl ComponentState { } } -impl RenderRunner { - pub fn run(self) { - let mut state = self.state.borrow_mut(); - let state = match state.as_mut() { - None => return, // skip for components that have already been destroyed - Some(state) => state, - }; - - state.render(&self.state); - } -} - #[cfg(feature = "csr")] mod feat_csr { use super::*; - pub(crate) struct PropsUpdateRunner { - pub state: Shared>, - pub props: Option>, - pub next_sibling: Option, - } - impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, @@ -518,24 +500,6 @@ mod feat_csr { } } - impl PropsUpdateRunner { - pub fn run(self) { - let Self { - next_sibling, - props, - state: shared_state, - } = self; - - if let Some(state) = shared_state.borrow_mut().as_mut() { - let schedule_render = state.changed(props, next_sibling); - - if schedule_render { - state.render(&shared_state); - } - }; - } - } - impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, @@ -559,9 +523,6 @@ mod feat_csr { } } -#[cfg(feature = "csr")] -pub(super) use feat_csr::*; - #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 268839cc9f5..f4a7665d03a 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -9,7 +9,6 @@ use std::{fmt, iter}; #[cfg(any(feature = "csr", feature = "ssr"))] use super::lifecycle::ComponentState; -use super::lifecycle::RenderRunner; use super::BaseComponent; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; @@ -64,11 +63,8 @@ impl AnyScope { /// Schedules a render. pub(crate) fn schedule_render(&self) { - let runner = RenderRunner { - state: self.state.clone(), - }; - - scheduler::push(move || runner.run()); + let scope = self.clone(); + scheduler::push(move || ComponentState::run_render(&scope)); } /// Returns the parent scope @@ -158,7 +154,7 @@ mod feat_ssr { use super::*; use crate::functional::FunctionComponent; - use crate::html::component::lifecycle::{CreateRunner, DestroyRunner, RenderRunner, Rendered}; + use crate::html::component::lifecycle::Rendered; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::platform::fmt::BufWriter; @@ -188,8 +184,10 @@ mod feat_ssr { let (tx, rx) = oneshot::channel(); let state = Rendered::Ssr { sender: Some(tx) }; + let scope = AnyScope::from(self.clone()); + let context = Context { - scope: AnyScope::from(self.clone()), + scope: scope.clone(), props: props as Rc, #[cfg(feature = "hydration")] creation_mode: RenderMode::Ssr, @@ -199,17 +197,7 @@ mod feat_ssr { let component = COMP::create(&context); - CreateRunner { - initial_render_state: state, - scope: AnyScope::from(self.clone()), - context, - component, - } - .run(); - RenderRunner { - state: self.state.clone(), - } - .run(); + ComponentState::run_create(context, component, state); let collectable = Collectable::for_component::(); @@ -233,11 +221,7 @@ mod feat_ssr { collectable.write_close_tag(w); } - DestroyRunner { - state: self.state.clone(), - parent_to_detach: false, - } - .run(); + ComponentState::run_destroy(&scope, false); } } } @@ -278,9 +262,7 @@ mod feat_csr { use super::*; use crate::dom_bundle::{BSubtree, Bundle}; - use crate::html::component::lifecycle::{ - CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner, Rendered, - }; + use crate::html::component::lifecycle::Rendered; use crate::html::NodeRef; use crate::Context; @@ -295,19 +277,10 @@ mod feat_csr { typed_scope: Rc::new(()), } } - } - fn schedule_props_update( - state: Shared>, - props: Rc, - next_sibling: NodeRef, - ) { - PropsUpdateRunner { - state, - props: Some(props), - next_sibling: Some(next_sibling), + fn schedule_props_update(&self, props: Rc, next_sibling: NodeRef) { + ComponentState::run_update_props(self, Some(props), Some(next_sibling)); } - .run(); } impl Scope @@ -349,21 +322,11 @@ mod feat_csr { let component = COMP::create(&context); - CreateRunner { - initial_render_state: state, - scope: self.clone().to_any(), - context, - component, - } - .run(); - RenderRunner { - state: self.state.clone(), - } - .run(); + ComponentState::run_create(context, component, state); } pub(crate) fn reuse(&self, props: Rc, next_sibling: NodeRef) { - schedule_props_update(self.state.clone(), props, next_sibling) + self.to_any().schedule_props_update(props, next_sibling) } } @@ -396,11 +359,7 @@ mod feat_csr { /// Process an event to destroy a component fn destroy(self, parent_to_detach: bool) { - DestroyRunner { - state: self.state, - parent_to_detach, - } - .run() + ComponentState::run_destroy(&self.to_any(), parent_to_detach); } fn destroy_boxed(self: Box, parent_to_detach: bool) { @@ -425,7 +384,7 @@ mod feat_hydration { use super::*; use crate::dom_bundle::{BSubtree, Fragment}; - use crate::html::component::lifecycle::{CreateRunner, RenderRunner, Rendered}; + use crate::html::component::lifecycle::Rendered; use crate::html::NodeRef; use crate::virtual_dom::Collectable; use crate::Context; @@ -492,8 +451,10 @@ mod feat_hydration { fragment, }; + let scope = self.to_any(); + let context = Context { - scope: self.to_any(), + scope, props: props as Rc, creation_mode: RenderMode::Hydration, prepared_state, @@ -501,17 +462,7 @@ mod feat_hydration { let component = COMP::create(&context); - CreateRunner { - initial_render_state: state, - scope: self.clone().to_any(), - context, - component, - } - .run(); - RenderRunner { - state: self.state.clone(), - } - .run(); + ComponentState::run_create(context, component, state); } } } From 6ab3edd8c28237f677411328d7094e0200887afd Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 00:36:04 +0900 Subject: [PATCH 15/47] Ssr no longer needs any lifecycle. --- packages/yew/src/html/component/lifecycle.rs | 234 +++++++------------ packages/yew/src/html/component/mod.rs | 2 +- packages/yew/src/html/component/scope.rs | 52 ++--- 3 files changed, 102 insertions(+), 186 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 24c7bd2976f..99615f7e68c 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -3,25 +3,20 @@ use std::any::Any; use std::rc::Rc; -#[cfg(feature = "csr")] use web_sys::Element; use super::scope::AnyScope; #[cfg(feature = "hydration")] use crate::dom_bundle::Fragment; -#[cfg(feature = "csr")] use crate::dom_bundle::{BSubtree, Bundle}; -#[cfg(feature = "csr")] -use crate::html::NodeRef; #[cfg(feature = "hydration")] use crate::html::RenderMode; -use crate::html::{Html, RenderError}; +use crate::html::{Html, NodeRef, RenderError}; use crate::scheduler::{self, Shared}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; use crate::{Callback, Context, ContextProvider, FunctionComponent}; pub(crate) enum Rendered { - #[cfg(feature = "csr")] Render { bundle: Bundle, root: BSubtree, @@ -37,16 +32,11 @@ pub(crate) enum Rendered { next_sibling: NodeRef, internal_ref: NodeRef, }, - #[cfg(feature = "ssr")] - Ssr { - sender: Option>, - }, } impl std::fmt::Debug for Rendered { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - #[cfg(feature = "csr")] Self::Render { ref bundle, root, @@ -77,27 +67,13 @@ impl std::fmt::Debug for Rendered { .field("next_sibling", next_sibling) .field("internal_ref", internal_ref) .finish(), - - #[cfg(feature = "ssr")] - Self::Ssr { ref sender } => { - let sender_repr = match sender { - Some(_) => "Some(_)", - None => "None", - }; - - f.debug_struct("Rendered::Ssr") - .field("sender", &sender_repr) - .finish() - } } } } -#[cfg(feature = "csr")] impl Rendered { pub(crate) fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) { match self { - #[cfg(feature = "csr")] Self::Render { bundle, parent, @@ -121,12 +97,6 @@ impl Rendered { *parent = next_parent; next_sibling.link(next_next_sibling); } - - #[cfg(feature = "ssr")] - Self::Ssr { .. } => { - #[cfg(debug_assertions)] - panic!("shifting is not possible during SSR"); - } } } } @@ -137,7 +107,6 @@ pub(crate) struct ComponentState { pub(super) render_state: Rendered, - #[cfg(feature = "csr")] has_rendered: bool, #[cfg(feature = "hydration")] pending_props: Option>, @@ -163,7 +132,6 @@ impl ComponentState { render_state: initial_render_state, suspension: None, - #[cfg(feature = "csr")] has_rendered: false, #[cfg(feature = "hydration")] pending_props: None, @@ -226,9 +194,7 @@ impl ComponentState { resume_suspension(&suspense_scope, m); } } -} -impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), @@ -239,7 +205,6 @@ impl ComponentState { self.resume_existing_suspension(); match self.render_state { - #[cfg(feature = "csr")] Rendered::Render { bundle, ref parent, @@ -264,16 +229,9 @@ impl ComponentState { internal_ref.set(None); } - - #[cfg(feature = "ssr")] - Rendered::Ssr { .. } => { - let _ = parent_to_detach; - } } } -} -impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip_all, @@ -326,7 +284,6 @@ impl ComponentState { self.resume_existing_suspension(); match self.render_state { - #[cfg(feature = "csr")] Rendered::Render { ref mut bundle, ref parent, @@ -366,9 +323,6 @@ impl ComponentState { } => { let scope = self.context.link(); - // This first node is not guaranteed to be correct here. - // As it may be a comment node that is removed afterwards. - // but we link it anyways. let (node, bundle) = Bundle::hydrate(root, scope, parent, fragment, new_root); // We trim all text nodes before checking as it's likely these are whitespaces. @@ -386,139 +340,113 @@ impl ComponentState { next_sibling: next_sibling.clone(), }; } - - #[cfg(feature = "ssr")] - Rendered::Ssr { ref mut sender } => { - let _ = shared_state; - if let Some(tx) = sender.take() { - tx.send(new_root).unwrap(); - } - } }; } -} - -#[cfg(feature = "csr")] -mod feat_csr { - use super::*; - impl ComponentState { - #[tracing::instrument( + #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), fields(component.id = self.comp_id) )] - pub(super) fn changed( - &mut self, - props: Option>, - next_sibling: Option, - ) -> bool { - if let Some(next_sibling) = next_sibling { - // When components are updated, their siblings were likely also updated - // We also need to shift the bundle so next sibling will be synced to child - // components. - match self.render_state { - #[cfg(feature = "csr")] - Rendered::Render { - next_sibling: ref current_next_sibling, - .. - } => { - current_next_sibling.link(next_sibling); - } - - #[cfg(feature = "hydration")] - Rendered::Hydration { - next_sibling: ref current_next_sibling, - .. - } => { - current_next_sibling.link(next_sibling); - } + pub(super) fn changed( + &mut self, + props: Option>, + next_sibling: Option, + ) -> bool { + if let Some(next_sibling) = next_sibling { + // When components are updated, their siblings were likely also updated + // We also need to shift the bundle so next sibling will be synced to child + // components. + match self.render_state { + Rendered::Render { + next_sibling: ref current_next_sibling, + .. + } => { + current_next_sibling.link(next_sibling); + } - #[cfg(feature = "ssr")] - Rendered::Ssr { .. } => { - #[cfg(debug_assertions)] - panic!("properties do not change during SSR"); - } + #[cfg(feature = "hydration")] + Rendered::Hydration { + next_sibling: ref current_next_sibling, + .. + } => { + current_next_sibling.link(next_sibling); } } + } - let should_render = |props: Option>, state: &mut ComponentState| -> bool { - props - .and_then(|m| { - (!state.component.props_eq(state.context.props(), &m)).then_some(m) - }) - .map(|m| { - state.context.props = m; - true - }) - .unwrap_or(false) - }; + let should_render = |props: Option>, state: &mut ComponentState| -> bool { + props + .and_then(|m| (!state.component.props_eq(state.context.props(), &m)).then_some(m)) + .map(|m| { + state.context.props = m; + true + }) + .unwrap_or(false) + }; - #[cfg(feature = "hydration")] - let should_render_hydration = - |props: Option>, state: &mut ComponentState| -> bool { - if let Some(props) = props.or_else(|| state.pending_props.take()) { - match state.has_rendered { - true => { - state.pending_props = None; - if !state.component.props_eq(state.context.props(), &props) { - state.context.props = props; - } - true - } - false => { - state.pending_props = Some(props); - false + #[cfg(feature = "hydration")] + let should_render_hydration = + |props: Option>, state: &mut ComponentState| -> bool { + if let Some(props) = props.or_else(|| state.pending_props.take()) { + match state.has_rendered { + true => { + state.pending_props = None; + if !state.component.props_eq(state.context.props(), &props) { + state.context.props = props; } + true + } + false => { + state.pending_props = Some(props); + false } - } else { - false } - }; + } else { + false + } + }; - // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = { - #[cfg(feature = "hydration")] - { - if self.context.creation_mode() == RenderMode::Hydration { - should_render_hydration(props, self) - } else { - should_render(props, self) - } + // Only trigger changed if props were changed / next sibling has changed. + let schedule_render = { + #[cfg(feature = "hydration")] + { + if self.context.creation_mode() == RenderMode::Hydration { + should_render_hydration(props, self) + } else { + should_render(props, self) } + } - #[cfg(not(feature = "hydration"))] - should_render(props, self) - }; + #[cfg(not(feature = "hydration"))] + should_render(props, self) + }; - tracing::trace!( - "props_update(has_rendered={} schedule_render={})", - self.has_rendered, - schedule_render - ); + tracing::trace!( + "props_update(has_rendered={} schedule_render={})", + self.has_rendered, schedule_render - } + ); + schedule_render } - impl ComponentState { - #[tracing::instrument( + #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), fields(component.id = self.comp_id) )] - pub(super) fn rendered(&mut self) -> bool { - if self.suspension.is_none() { - self.component.rendered(); - } + pub(super) fn rendered(&mut self) -> bool { + if self.suspension.is_none() { + self.component.rendered(); + } - #[cfg(feature = "hydration")] - { - self.pending_props.is_some() - } - #[cfg(not(feature = "hydration"))] - { - false - } + #[cfg(feature = "hydration")] + { + self.pending_props.is_some() + } + #[cfg(not(feature = "hydration"))] + { + false } } } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 39c7c37cf59..88f0acb67d9 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -1,7 +1,7 @@ //! Components wrapped with context including properties, state, and link mod children; -#[cfg(any(feature = "csr", feature = "ssr"))] +#[cfg(feature = "csr")] mod lifecycle; mod marker; mod properties; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index f4a7665d03a..dbe69d2d79f 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -7,7 +7,7 @@ use std::marker::PhantomData; use std::rc::Rc; use std::{fmt, iter}; -#[cfg(any(feature = "csr", feature = "ssr"))] +#[cfg(feature = "csr")] use super::lifecycle::ComponentState; use super::BaseComponent; use crate::callback::Callback; @@ -28,7 +28,7 @@ pub struct AnyScope { id: usize, type_id: TypeId, - #[cfg(any(feature = "csr", feature = "ssr"))] + #[cfg(feature = "csr")] pub(crate) state: Shared>, parent: Option>, @@ -47,7 +47,7 @@ impl From> for AnyScope { id: scope.id, type_id: TypeId::of::(), - #[cfg(any(feature = "csr", feature = "ssr"))] + #[cfg(feature = "csr")] state: scope.state.clone(), parent: scope.parent.clone(), @@ -63,8 +63,11 @@ impl AnyScope { /// Schedules a render. pub(crate) fn schedule_render(&self) { - let scope = self.clone(); - scheduler::push(move || ComponentState::run_render(&scope)); + #[cfg(feature = "csr")] + { + let scope = self.clone(); + scheduler::push(move || ComponentState::run_render(&scope)); + } } /// Returns the parent scope @@ -119,7 +122,7 @@ pub(crate) struct Scope { _marker: PhantomData, parent: Option>, - #[cfg(any(feature = "csr", feature = "ssr"))] + #[cfg(feature = "csr")] pub(crate) state: Shared>, pub(crate) id: usize, @@ -138,7 +141,7 @@ impl Clone for Scope { parent: self.parent.clone(), - #[cfg(any(feature = "csr", feature = "ssr"))] + #[cfg(feature = "csr")] state: self.state.clone(), id: self.id, @@ -148,29 +151,17 @@ impl Clone for Scope { #[cfg(feature = "ssr")] mod feat_ssr { - use std::cell::Ref; use std::fmt::Write; - use std::ops::Deref; use super::*; - use crate::functional::FunctionComponent; - use crate::html::component::lifecycle::Rendered; + use crate::html::RenderError; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::platform::fmt::BufWriter; - use crate::platform::pinned::oneshot; use crate::virtual_dom::Collectable; use crate::Context; impl Scope { - /// Returns the linked component if available - pub(crate) fn get_component(&self) -> Option + '_> { - self.state.try_borrow().ok().and_then(|state_ref| { - // Ref::filter_map is only available since 1.63 - Ref::filter_map(state_ref, |state| state.as_ref().map(|m| &m.component)).ok() - }) - } - pub(crate) async fn render_into_stream( &self, w: &mut BufWriter, @@ -181,9 +172,6 @@ mod feat_ssr { // // If the content of this channel is ready before it is awaited, it is // similar to taking the value from a mutex lock. - let (tx, rx) = oneshot::channel(); - let state = Rendered::Ssr { sender: Some(tx) }; - let scope = AnyScope::from(self.clone()); let context = Context { @@ -197,21 +185,24 @@ mod feat_ssr { let component = COMP::create(&context); - ComponentState::run_create(context, component, state); - let collectable = Collectable::for_component::(); if hydratable { collectable.write_open_tag(w); } - let html = rx.await.unwrap(); + let html = loop { + match component.render(context.props().as_ref()) { + Ok(m) => break m, + Err(RenderError::Suspended(e)) => e.await, + } + }; let self_any_scope = AnyScope::from(self.clone()); html.render_into_stream(w, &self_any_scope, hydratable) .await; - if let Some(prepared_state) = self.get_component().unwrap().prepare_state() { + if let Some(prepared_state) = component.prepare_state() { let _ = w.write_str(r#""#); @@ -220,8 +211,6 @@ mod feat_ssr { if hydratable { collectable.write_close_tag(w); } - - ComponentState::run_destroy(&scope, false); } } } @@ -240,12 +229,11 @@ mod feat_csr_ssr { pub(crate) fn new(parent: Option) -> Self { let parent = parent.map(Rc::new); - let state = Rc::new(RefCell::new(None)); - Scope { _marker: PhantomData, - state, + #[cfg(feature = "csr")] + state: Rc::new(RefCell::new(None)), parent, id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst), From 159cbd8baa3227d5df17ded42320505a9f010f4f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 00:56:08 +0900 Subject: [PATCH 16/47] Remove has_rendered. --- packages/yew/src/html/component/lifecycle.rs | 106 +++++++------------ 1 file changed, 37 insertions(+), 69 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 99615f7e68c..97693bc9919 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -12,7 +12,7 @@ use crate::dom_bundle::{BSubtree, Bundle}; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::html::{Html, NodeRef, RenderError}; -use crate::scheduler::{self, Shared}; +use crate::scheduler::{self}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; use crate::{Callback, Context, ContextProvider, FunctionComponent}; @@ -107,7 +107,6 @@ pub(crate) struct ComponentState { pub(super) render_state: Rendered, - has_rendered: bool, #[cfg(feature = "hydration")] pending_props: Option>, @@ -132,7 +131,6 @@ impl ComponentState { render_state: initial_render_state, suspension: None, - has_rendered: false, #[cfg(feature = "hydration")] pending_props: None, @@ -150,7 +148,7 @@ impl ComponentState { if current_state.is_none() { let mut self_ = Self::new(component, context, initial_render_state); - self_.render(&state); + self_.render(); // We are safe to assign afterwards as we mutably borrow the state and don't release it // until this function returns. @@ -160,7 +158,7 @@ impl ComponentState { pub fn run_render(scope: &AnyScope) { if let Some(state) = scope.state.borrow_mut().as_mut() { - state.render(&scope.state); + state.render(); } } @@ -170,11 +168,7 @@ impl ComponentState { next_sibling: Option, ) { if let Some(state) = scope.state.borrow_mut().as_mut() { - let schedule_render = state.changed(props, next_sibling); - - if schedule_render { - state.render(&scope.state); - } + state.changed(props, next_sibling); } } @@ -237,19 +231,19 @@ impl ComponentState { skip_all, fields(component.id = self.comp_id) )] - fn render(&mut self, shared_state: &Shared>) { + fn render(&mut self) { match self.component.render(self.context.props().as_ref()) { - Ok(vnode) => self.commit_render(shared_state, vnode), - Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp), + Ok(vnode) => self.commit_render(vnode), + Err(RenderError::Suspended(susp)) => self.suspend(susp), }; } - fn suspend(&mut self, shared_state: &Shared>, suspension: Suspension) { + fn suspend(&mut self, suspension: Suspension) { // Currently suspended, we re-use previous root node and send // suspension to parent element. if suspension.resumed() { - self.render(shared_state); + self.render(); } else { // We schedule a render after current suspension is resumed. let comp_scope = self.context.link(); @@ -278,7 +272,7 @@ impl ComponentState { } } - fn commit_render(&mut self, shared_state: &Shared>, new_root: Html) { + fn commit_render(&mut self, new_root: Html) { // Currently not suspended, we remove any previous suspension and update // normally. self.resume_existing_suspension(); @@ -301,15 +295,9 @@ impl ComponentState { bundle.reconcile(root, scope, parent, next_sibling.clone(), new_root); internal_ref.link(new_node_ref); - self.has_rendered = true; - let has_pending_props = self.rendered(); if has_pending_props { - let should_render = self.changed(None, None); - - if should_render { - self.render(shared_state); - } + self.changed(None, None); } } @@ -348,11 +336,7 @@ impl ComponentState { skip(self), fields(component.id = self.comp_id) )] - pub(super) fn changed( - &mut self, - props: Option>, - next_sibling: Option, - ) -> bool { + pub(super) fn changed(&mut self, props: Option>, next_sibling: Option) { if let Some(next_sibling) = next_sibling { // When components are updated, their siblings were likely also updated // We also need to shift the bundle so next sibling will be synced to child @@ -375,59 +359,43 @@ impl ComponentState { } } - let should_render = |props: Option>, state: &mut ComponentState| -> bool { - props - .and_then(|m| (!state.component.props_eq(state.context.props(), &m)).then_some(m)) - .map(|m| { - state.context.props = m; - true - }) - .unwrap_or(false) - }; - - #[cfg(feature = "hydration")] - let should_render_hydration = - |props: Option>, state: &mut ComponentState| -> bool { - if let Some(props) = props.or_else(|| state.pending_props.take()) { - match state.has_rendered { - true => { - state.pending_props = None; - if !state.component.props_eq(state.context.props(), &props) { - state.context.props = props; + // Only trigger changed if props were changed / next sibling has changed. + let schedule_render = '_block: { + #[cfg(feature = "hydration")] + if self.context.creation_mode() == RenderMode::Hydration { + break '_block if let Some(props) = props.or_else(|| self.pending_props.take()) { + match self.render_state { + Rendered::Render { .. } => { + self.pending_props = None; + if !self.component.props_eq(self.context.props(), &props) { + self.context.props = props; } true } - false => { - state.pending_props = Some(props); + Rendered::Hydration { .. } => { + self.pending_props = Some(props); false } } } else { false - } - }; - - // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = { - #[cfg(feature = "hydration")] - { - if self.context.creation_mode() == RenderMode::Hydration { - should_render_hydration(props, self) - } else { - should_render(props, self) - } + }; } - #[cfg(not(feature = "hydration"))] - should_render(props, self) + props + .and_then(|m| (!self.component.props_eq(self.context.props(), &m)).then_some(m)) + .map(|m| { + self.context.props = m; + true + }) + .unwrap_or(false) }; - tracing::trace!( - "props_update(has_rendered={} schedule_render={})", - self.has_rendered, - schedule_render - ); - schedule_render + tracing::trace!("props_update(schedule_render={})", schedule_render); + + if schedule_render { + self.render() + } } #[tracing::instrument( From a5e31fc81d5c18f439795674702fd63b2433775f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 01:36:41 +0900 Subject: [PATCH 17/47] Remove duplication. --- packages/yew/src/html/component/lifecycle.rs | 256 +++++++------------ packages/yew/src/html/component/scope.rs | 64 +++-- packages/yew/src/scheduler.rs | 6 +- 3 files changed, 127 insertions(+), 199 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 97693bc9919..079d5b006c5 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -12,107 +12,47 @@ use crate::dom_bundle::{BSubtree, Bundle}; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::html::{Html, NodeRef, RenderError}; -use crate::scheduler::{self}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; -use crate::{Callback, Context, ContextProvider, FunctionComponent}; +use crate::{scheduler, Callback, Context, ContextProvider, FunctionComponent}; -pub(crate) enum Rendered { - Render { - bundle: Bundle, - root: BSubtree, - parent: Element, - next_sibling: NodeRef, - internal_ref: NodeRef, - }, +pub(crate) enum Realized { + Bundle(Bundle), #[cfg(feature = "hydration")] - Hydration { - fragment: Fragment, - root: BSubtree, - parent: Element, - next_sibling: NodeRef, - internal_ref: NodeRef, - }, + Fragement(Fragment), } -impl std::fmt::Debug for Rendered { +impl std::fmt::Debug for Realized { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Render { - ref bundle, - root, - ref parent, - ref next_sibling, - ref internal_ref, - } => f + Self::Bundle(ref bundle) => f .debug_struct("Rendered::Render") .field("bundle", bundle) - .field("root", root) - .field("parent", parent) - .field("next_sibling", next_sibling) - .field("internal_ref", internal_ref) .finish(), #[cfg(feature = "hydration")] - Self::Hydration { - ref fragment, - ref parent, - ref next_sibling, - ref internal_ref, - ref root, - } => f + Self::Fragement(ref fragment) => f .debug_struct("Rendered::Hydration") .field("fragment", fragment) - .field("root", root) - .field("parent", parent) - .field("next_sibling", next_sibling) - .field("internal_ref", internal_ref) .finish(), } } } -impl Rendered { - pub(crate) fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) { - match self { - Self::Render { - bundle, - parent, - next_sibling, - .. - } => { - bundle.shift(&next_parent, next_next_sibling.clone()); - - *parent = next_parent; - next_sibling.link(next_next_sibling); - } - #[cfg(feature = "hydration")] - Self::Hydration { - fragment, - parent, - next_sibling, - .. - } => { - fragment.shift(&next_parent, next_next_sibling.clone()); - - *parent = next_parent; - next_sibling.link(next_next_sibling); - } - } - } -} - pub(crate) struct ComponentState { pub(super) component: FunctionComponent, pub(super) context: Context, - pub(super) render_state: Rendered, + pub(super) rendered: Realized, + + root: BSubtree, + parent: Element, + next_sibling: NodeRef, + internal_ref: NodeRef, #[cfg(feature = "hydration")] pending_props: Option>, suspension: Option, - - pub(crate) comp_id: usize, } impl ComponentState { @@ -122,32 +62,53 @@ impl ComponentState { skip_all, fields(component.id = context.link().get_id()), )] - fn new(component: FunctionComponent, context: Context, initial_render_state: Rendered) -> Self { - let comp_id = context.scope.get_id(); - + fn new( + component: FunctionComponent, + context: Context, + initial_render_state: Realized, + root: BSubtree, + parent: Element, + next_sibling: NodeRef, + internal_ref: NodeRef, + ) -> Self { Self { component, context, - render_state: initial_render_state, + rendered: initial_render_state, suspension: None, + root, + parent, + next_sibling, + internal_ref, + #[cfg(feature = "hydration")] pending_props: None, - - comp_id, } } pub fn run_create( context: Context, component: FunctionComponent, - initial_render_state: Rendered, + initial_render_state: Realized, + root: BSubtree, + parent: Element, + next_sibling: NodeRef, + internal_ref: NodeRef, ) { let state = context.scope.state.clone(); let mut current_state = state.borrow_mut(); if current_state.is_none() { - let mut self_ = Self::new(component, context, initial_render_state); + let mut self_ = Self::new( + component, + context, + initial_render_state, + root, + parent, + next_sibling, + internal_ref, + ); self_.render(); // We are safe to assign afterwards as we mutably borrow the state and don't release it @@ -189,47 +150,48 @@ impl ComponentState { } } + pub fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) { + match self.rendered { + Realized::Bundle(ref mut bundle) => { + bundle.shift(&next_parent, next_next_sibling.clone()); + } + #[cfg(feature = "hydration")] + Realized::Fragement(ref mut fragment) => { + fragment.shift(&next_parent, next_next_sibling.clone()); + } + } + + self.parent = next_parent; + self.next_sibling.link(next_next_sibling); + } + #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.comp_id) + fields(component.id = self.context.link().get_id()) )] fn destroy(mut self, parent_to_detach: bool) { self.component.destroy(); self.resume_existing_suspension(); - match self.render_state { - Rendered::Render { - bundle, - ref parent, - ref internal_ref, - ref root, - .. - } => { - bundle.detach(root, parent, parent_to_detach); - - internal_ref.set(None); + match self.rendered { + Realized::Bundle(bundle) => { + bundle.detach(&self.root, &self.parent, parent_to_detach); } // We need to detach the hydrate fragment if the component is not hydrated. #[cfg(feature = "hydration")] - Rendered::Hydration { - ref root, - fragment, - ref parent, - ref internal_ref, - .. - } => { - fragment.detach(root, parent, parent_to_detach); - - internal_ref.set(None); + Realized::Fragement(fragment) => { + fragment.detach(&self.root, &self.parent, parent_to_detach); } } + + self.internal_ref.set(None); } #[tracing::instrument( level = tracing::Level::DEBUG, skip_all, - fields(component.id = self.comp_id) + fields(component.id = self.context.link().get_id()) )] fn render(&mut self) { match self.component.render(self.context.props().as_ref()) { @@ -277,23 +239,21 @@ impl ComponentState { // normally. self.resume_existing_suspension(); - match self.render_state { - Rendered::Render { - ref mut bundle, - ref parent, - ref root, - ref next_sibling, - ref internal_ref, - .. - } => { + match self.rendered { + Realized::Bundle(ref mut bundle) => { let scope = self.context.link(); #[cfg(feature = "hydration")] - next_sibling.debug_assert_not_trapped(); + self.next_sibling.debug_assert_not_trapped(); - let new_node_ref = - bundle.reconcile(root, scope, parent, next_sibling.clone(), new_root); - internal_ref.link(new_node_ref); + let new_node_ref = bundle.reconcile( + &self.root, + scope, + &self.parent, + self.next_sibling.clone(), + new_root, + ); + self.internal_ref.link(new_node_ref); let has_pending_props = self.rendered(); if has_pending_props { @@ -302,61 +262,35 @@ impl ComponentState { } #[cfg(feature = "hydration")] - Rendered::Hydration { - ref mut fragment, - ref parent, - ref internal_ref, - ref next_sibling, - ref root, - } => { + Realized::Fragement(ref mut fragment) => { let scope = self.context.link(); - let (node, bundle) = Bundle::hydrate(root, scope, parent, fragment, new_root); + let (node, bundle) = + Bundle::hydrate(&self.root, scope, &self.parent, fragment, new_root); // We trim all text nodes before checking as it's likely these are whitespaces. - fragment.trim_start_text_nodes(parent); + fragment.trim_start_text_nodes(&self.parent); assert!(fragment.is_empty(), "expected end of component, found node"); - internal_ref.link(node); + self.internal_ref.link(node); - self.render_state = Rendered::Render { - root: root.clone(), - bundle, - parent: parent.clone(), - internal_ref: internal_ref.clone(), - next_sibling: next_sibling.clone(), - }; + self.rendered = Realized::Bundle(bundle); } }; } #[tracing::instrument( - level = tracing::Level::DEBUG, - skip(self), - fields(component.id = self.comp_id) - )] + level = tracing::Level::DEBUG, + skip(self), + fields(component.id = self.context.link().get_id()) + )] pub(super) fn changed(&mut self, props: Option>, next_sibling: Option) { if let Some(next_sibling) = next_sibling { // When components are updated, their siblings were likely also updated // We also need to shift the bundle so next sibling will be synced to child // components. - match self.render_state { - Rendered::Render { - next_sibling: ref current_next_sibling, - .. - } => { - current_next_sibling.link(next_sibling); - } - - #[cfg(feature = "hydration")] - Rendered::Hydration { - next_sibling: ref current_next_sibling, - .. - } => { - current_next_sibling.link(next_sibling); - } - } + self.next_sibling.link(next_sibling); } // Only trigger changed if props were changed / next sibling has changed. @@ -364,15 +298,15 @@ impl ComponentState { #[cfg(feature = "hydration")] if self.context.creation_mode() == RenderMode::Hydration { break '_block if let Some(props) = props.or_else(|| self.pending_props.take()) { - match self.render_state { - Rendered::Render { .. } => { + match self.rendered { + Realized::Bundle { .. } => { self.pending_props = None; if !self.component.props_eq(self.context.props(), &props) { self.context.props = props; } true } - Rendered::Hydration { .. } => { + Realized::Fragement { .. } => { self.pending_props = Some(props); false } @@ -399,10 +333,10 @@ impl ComponentState { } #[tracing::instrument( - level = tracing::Level::DEBUG, - skip(self), - fields(component.id = self.comp_id) - )] + level = tracing::Level::DEBUG, + skip(self), + fields(component.id = self.context.link().get_id()) + )] pub(super) fn rendered(&mut self) -> bool { if self.suspension.is_none() { self.component.rendered(); diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index dbe69d2d79f..5721c18e161 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -1,8 +1,8 @@ //! Component scope module use std::any::{Any, TypeId}; -// use std::cell::RefCell; -// use std::collections::HashMap; +#[cfg(feature = "csr")] +use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; use std::{fmt, iter}; @@ -15,12 +15,6 @@ use crate::context::{ContextHandle, ContextProvider, ContextStore}; #[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::scheduler; -#[cfg(any(feature = "csr", feature = "ssr"))] -use crate::scheduler::Shared; - -// thread_local! { -// static PROPS: RefCell>> = RefCell::default(); -// } /// Untyped scope used for accessing parent scope #[derive(Clone)] @@ -29,7 +23,7 @@ pub struct AnyScope { type_id: TypeId, #[cfg(feature = "csr")] - pub(crate) state: Shared>, + pub(crate) state: Rc>>, parent: Option>, typed_scope: Rc, @@ -123,7 +117,7 @@ pub(crate) struct Scope { parent: Option>, #[cfg(feature = "csr")] - pub(crate) state: Shared>, + pub(crate) state: Rc>>, pub(crate) id: usize, } @@ -250,7 +244,7 @@ mod feat_csr { use super::*; use crate::dom_bundle::{BSubtree, Bundle}; - use crate::html::component::lifecycle::Rendered; + use crate::html::component::lifecycle::Realized; use crate::html::NodeRef; use crate::Context; @@ -289,15 +283,7 @@ mod feat_csr { let stable_next_sibling = NodeRef::default(); stable_next_sibling.link(next_sibling); - // PROPS.with(|m| m.borrow_mut().insert(self.id, props.clone())); - - let state = Rendered::Render { - bundle, - root, - internal_ref, - parent, - next_sibling: stable_next_sibling, - }; + let state = Realized::Bundle(bundle); let context = Context { scope: self.to_any(), @@ -310,7 +296,15 @@ mod feat_csr { let component = COMP::create(&context); - ComponentState::run_create(context, component, state); + ComponentState::run_create( + context, + component, + state, + root, + parent, + stable_next_sibling, + internal_ref, + ); } pub(crate) fn reuse(&self, props: Rc, next_sibling: NodeRef) { @@ -321,7 +315,7 @@ mod feat_csr { pub(crate) trait Scoped { fn to_any(&self) -> AnyScope; /// Get the render state if it hasn't already been destroyed - fn render_state(&self) -> Option>; + fn render_state(&self) -> Option>; /// Shift the node associated with this scope to a new place fn shift_node(&self, parent: Element, next_sibling: NodeRef); /// Process an event to destroy a component @@ -334,14 +328,14 @@ mod feat_csr { self.clone().into() } - fn render_state(&self) -> Option> { + fn render_state(&self) -> Option> { let state_ref = self.state.borrow(); // check that component hasn't been destroyed state_ref.as_ref()?; Some(Ref::map(state_ref, |state_ref| { - &state_ref.as_ref().unwrap().render_state + &state_ref.as_ref().unwrap().rendered })) } @@ -357,7 +351,7 @@ mod feat_csr { fn shift_node(&self, parent: Element, next_sibling: NodeRef) { let mut state_ref = self.state.borrow_mut(); if let Some(render_state) = state_ref.as_mut() { - render_state.render_state.shift(parent, next_sibling) + render_state.shift(parent, next_sibling) } } } @@ -372,7 +366,7 @@ mod feat_hydration { use super::*; use crate::dom_bundle::{BSubtree, Fragment}; - use crate::html::component::lifecycle::Rendered; + use crate::html::component::lifecycle::Realized; use crate::html::NodeRef; use crate::virtual_dom::Collectable; use crate::Context; @@ -431,13 +425,7 @@ mod feat_hydration { _ => None, }; - let state = Rendered::Hydration { - parent, - root, - internal_ref, - next_sibling: NodeRef::new_debug_trapped(), - fragment, - }; + let state = Realized::Fragement(fragment); let scope = self.to_any(); @@ -450,7 +438,15 @@ mod feat_hydration { let component = COMP::create(&context); - ComponentState::run_create(context, component, state); + ComponentState::run_create( + context, + component, + state, + root, + parent, + NodeRef::new_debug_trapped(), + internal_ref, + ); } } } diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 2c1d9d854c1..b3f807f2200 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -1,13 +1,9 @@ //! This module contains a scheduler. use std::cell::RefCell; -use std::rc::Rc; use crate::platform::spawn_local; -/// Alias for `Rc>` -pub type Shared = Rc>; - type Runnable = Box; #[derive(Default)] @@ -21,6 +17,7 @@ impl FifoQueue { self.inner.push(task); } + #[inline(always)] fn drain_into(&mut self, queue: &mut Vec) { queue.append(&mut self.inner); } @@ -49,6 +46,7 @@ fn with(f: impl FnOnce(&mut Scheduler) -> R) -> R { } /// Push a generic [Runnable] to be executed +#[inline(always)] pub fn push(runnable: F) where F: FnOnce() + 'static, From 2319f4ea73fe0476115ec635e4b07ceaa218a558 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 01:56:22 +0900 Subject: [PATCH 18/47] Fix feature soundness. --- packages/yew/src/context.rs | 1 + packages/yew/src/functional/mod.rs | 3 + packages/yew/src/html/component/scope.rs | 4 +- packages/yew/src/suspense/component.rs | 108 +++++++++++++---------- packages/yew/src/suspense/mod.rs | 2 +- 5 files changed, 70 insertions(+), 48 deletions(-) diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index fe2ec1f0132..c6703200248 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -66,6 +66,7 @@ impl ContextStore { } } + #[cfg(feature = "csr")] pub(crate) fn get_context_value(&self) -> T { self.context.clone() } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index d0f1b455c71..506e4a70a4f 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -308,6 +308,7 @@ pub trait FunctionProvider { pub(crate) trait AnyFunctionProvider { fn run(&self, ctx: &mut HookContext, props: &dyn Any) -> HtmlResult; + #[cfg(feature = "csr")] fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool; } @@ -324,6 +325,7 @@ where T::run(ctx, props) } + #[cfg(feature = "csr")] fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool { match ( last_props.downcast_ref::(), @@ -380,6 +382,7 @@ impl FunctionComponent { } } + #[cfg(feature = "csr")] pub(crate) fn props_eq(&self, last_props: &dyn Any, next_props: &dyn Any) -> bool { self.inner.props_eq(last_props, next_props) } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 5721c18e161..539352f0e17 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -14,7 +14,6 @@ use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; #[cfg(feature = "hydration")] use crate::html::RenderMode; -use crate::scheduler; /// Untyped scope used for accessing parent scope #[derive(Clone)] @@ -59,6 +58,8 @@ impl AnyScope { pub(crate) fn schedule_render(&self) { #[cfg(feature = "csr")] { + use crate::scheduler; + let scope = self.clone(); scheduler::push(move || ComponentState::run_render(&scope)); } @@ -211,7 +212,6 @@ mod feat_ssr { #[cfg(any(feature = "ssr", feature = "csr"))] mod feat_csr_ssr { - use std::cell::RefCell; use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index 8e70d51c44f..fa028fb2cde 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -12,36 +12,92 @@ pub struct SuspenseProps { pub fallback: Html, } +#[cfg(feature = "csr")] +mod feat_csr { + + use super::*; + use crate::context::ContextStore; + use crate::html::{AnyScope, Scope}; + use crate::suspense::Suspension; + use crate::ContextProvider; + + #[cfg(feature = "csr")] + pub(crate) fn resume_suspension( + provider: &Scope>, + s: Suspension, + ) { + if let Some(provider) = + ContextStore::::get(&AnyScope::from(provider.clone())) + { + let context = provider.borrow().get_context_value(); + context.dispatch(SuspensionsAction::Resume(s)); + } + } + + #[cfg(feature = "csr")] + pub(crate) fn suspend_suspension( + provider: &Scope>, + s: Suspension, + ) { + if let Some(provider) = + ContextStore::::get(&AnyScope::from(provider.clone())) + { + let context = provider.borrow().get_context_value(); + context.dispatch(SuspensionsAction::Suspend(s)); + } + } +} + +#[cfg(feature = "csr")] +pub(crate) use feat_csr::*; + #[cfg(any(feature = "csr", feature = "ssr"))] mod feat_csr_ssr { + use std::cell::RefCell; use super::*; - use crate::context::ContextStore; - use crate::html::{AnyScope, Children, Html, Scope}; + use crate::html::{Children, Html}; use crate::suspense::Suspension; use crate::virtual_dom::{VNode, VSuspense}; use crate::{ function_component, html, use_reducer, ContextProvider, Reducible, UseReducerDispatcher, }; + pub(crate) type DispatchSuspension = UseReducerDispatcher; + + #[derive(Properties, PartialEq, Debug, Clone)] + pub(crate) struct BaseSuspenseProps { + pub children: Children, + pub fallback: Option, + } + + #[derive(Debug)] + pub(crate) enum SuspensionsAction { + #[cfg(feature = "csr")] + Suspend(Suspension), + #[cfg(feature = "csr")] + Resume(Suspension), + } + #[derive(Default)] pub(crate) struct Suspensions { inner: RefCell>, } impl Reducible for Suspensions { - type Action = BaseSuspenseMsg; + type Action = SuspensionsAction; - fn reduce(self: std::rc::Rc, action: Self::Action) -> std::rc::Rc { + fn reduce(self: std::rc::Rc, _action: Self::Action) -> std::rc::Rc { + #[cfg(feature = "csr")] { let mut inner = self.inner.borrow_mut(); - match action { - BaseSuspenseMsg::Resume(m) => { + match _action { + SuspensionsAction::Resume(m) => { inner.retain(|n| &m != n); } - BaseSuspenseMsg::Suspend(m) => { + SuspensionsAction::Suspend(m) => { if m.resumed() { drop(inner); return self; @@ -55,14 +111,6 @@ mod feat_csr_ssr { } } - pub(crate) type DispatchSuspension = UseReducerDispatcher; - - #[derive(Properties, PartialEq, Debug, Clone)] - pub(crate) struct BaseSuspenseProps { - pub children: Children, - pub fallback: Option, - } - #[function_component] pub(crate) fn BaseSuspense(props: &BaseSuspenseProps) -> Html { let suspensions = use_reducer(Suspensions::default); @@ -99,36 +147,6 @@ mod feat_csr_ssr { } } - pub(crate) fn resume_suspension( - provider: &Scope>, - s: Suspension, - ) { - if let Some(provider) = - ContextStore::::get(&AnyScope::from(provider.clone())) - { - let context = provider.borrow().get_context_value(); - context.dispatch(BaseSuspenseMsg::Resume(s)); - } - } - - pub(crate) fn suspend_suspension( - provider: &Scope>, - s: Suspension, - ) { - if let Some(provider) = - ContextStore::::get(&AnyScope::from(provider.clone())) - { - let context = provider.borrow().get_context_value(); - context.dispatch(BaseSuspenseMsg::Suspend(s)); - } - } - - #[derive(Debug)] - pub(crate) enum BaseSuspenseMsg { - Suspend(Suspension), - Resume(Suspension), - } - /// Suspend rendering and show a fallback UI until the underlying task completes. #[function_component] pub fn Suspense(props: &SuspenseProps) -> Html { diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs index 6157b74f40b..b74dc036b4e 100644 --- a/packages/yew/src/suspense/mod.rs +++ b/packages/yew/src/suspense/mod.rs @@ -4,7 +4,7 @@ mod component; mod hooks; mod suspension; -#[cfg(any(feature = "csr", feature = "ssr"))] +#[cfg(feature = "csr")] pub(crate) use component::{resume_suspension, suspend_suspension, DispatchSuspension}; pub use component::{Suspense, SuspenseProps}; pub use hooks::*; From 01e56a30be04ed76f55ccc5304d5e5fe8d712e57 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 9 Nov 2022 14:10:19 +0900 Subject: [PATCH 19/47] Remove typed scope. --- packages/yew/src/app_handle.rs | 22 +- packages/yew/src/context.rs | 6 +- packages/yew/src/dom_bundle/bcomp.rs | 20 +- packages/yew/src/dom_bundle/blist.rs | 16 +- packages/yew/src/dom_bundle/bnode.rs | 10 +- packages/yew/src/dom_bundle/bportal.rs | 12 +- packages/yew/src/dom_bundle/bsuspense.rs | 10 +- packages/yew/src/dom_bundle/btag/mod.rs | 16 +- packages/yew/src/dom_bundle/btext.rs | 10 +- packages/yew/src/dom_bundle/mod.rs | 6 +- packages/yew/src/dom_bundle/traits.rs | 12 +- packages/yew/src/functional/hooks/mod.rs | 2 +- packages/yew/src/functional/mod.rs | 6 +- packages/yew/src/html/component/lifecycle.rs | 40 +-- packages/yew/src/html/component/mod.rs | 7 +- packages/yew/src/html/component/scope.rs | 249 ++++++++----------- packages/yew/src/html/conversion.rs | 2 +- packages/yew/src/server_renderer.rs | 4 +- packages/yew/src/suspense/component.rs | 21 +- packages/yew/src/tests/layout_tests.rs | 4 +- packages/yew/src/virtual_dom/vcomp.rs | 61 +++-- packages/yew/src/virtual_dom/vlist.rs | 6 +- packages/yew/src/virtual_dom/vnode.rs | 6 +- packages/yew/src/virtual_dom/vsuspense.rs | 4 +- packages/yew/src/virtual_dom/vtag.rs | 4 +- packages/yew/src/virtual_dom/vtext.rs | 4 +- 26 files changed, 268 insertions(+), 292 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 7fb0548c811..a14f30a2777 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -1,5 +1,6 @@ //! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope. +use std::marker::PhantomData; use std::rc::Rc; use web_sys::Element; @@ -13,7 +14,8 @@ use crate::scheduler; #[derive(Debug)] pub struct AppHandle { /// `Scope` holder - pub(crate) scope: Scope, + pub(crate) scope: Scope, + _marker: PhantomData, } impl AppHandle @@ -32,18 +34,20 @@ where pub(crate) fn mount_with_props(host: Element, props: Rc) -> Self { clear_element(&host); let app = Self { - scope: Scope::new(None), + scope: Scope::new::(None), + _marker: PhantomData, }; let hosting_root = BSubtree::create_root(&host); { let scope = app.scope.clone(); scheduler::push(move || { - scope.mount_in_place( + scope.to_any().mount( hosting_root, host, NodeRef::default(), NodeRef::default(), + |ctx| COMP::create(ctx), props, ); }); @@ -76,6 +80,7 @@ fn clear_element(host: &Element) { mod feat_hydration { use super::*; use crate::dom_bundle::Fragment; + use crate::virtual_dom::Collectable; impl AppHandle where @@ -88,7 +93,8 @@ mod feat_hydration { )] pub(crate) fn hydrate_with_props(host: Element, props: Rc) -> Self { let app = Self { - scope: Scope::new(None), + scope: Scope::new::(None), + _marker: PhantomData, }; let mut fragment = Fragment::collect_children(&host); @@ -97,15 +103,17 @@ mod feat_hydration { let scope = app.scope.clone(); scheduler::push(move || { - scope.hydrate_in_place( + scope.to_any().hydrate( hosting_root, host.clone(), &mut fragment, NodeRef::default(), - Rc::clone(&props), + |ctx| COMP::create(ctx), + props.clone(), + || Collectable::for_component::(), ); #[cfg(debug_assertions)] // Fix trapped next_sibling at the root - scope.reuse(props, NodeRef::default()); + scope.to_any().reuse(props, NodeRef::default()); // We remove all remaining nodes, this mimics the clear_element behaviour in // mount_with_props. diff --git a/packages/yew/src/context.rs b/packages/yew/src/context.rs index c6703200248..e196e7be087 100644 --- a/packages/yew/src/context.rs +++ b/packages/yew/src/context.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use slab::Slab; use yew_macro::function_component; -use crate::html::AnyScope; +use crate::html::Scope; use crate::{ html, use_component_id, use_effect_with_deps, use_memo, Callback, Children, Html, Properties, }; @@ -21,10 +21,10 @@ pub(crate) struct ContextStore { } impl ContextStore { - pub(crate) fn get(scope: &AnyScope) -> Option>>> { + pub(crate) fn get(scope: &Scope) -> Option>>> { CONTEXT_STORES.with(|m| { m.borrow_mut() - .get(&scope.get_id()) + .get(&scope.id()) .cloned() .and_then(|m| m.downcast().ok()) }) diff --git a/packages/yew/src/dom_bundle/bcomp.rs b/packages/yew/src/dom_bundle/bcomp.rs index f9a406bfa2c..6c053130a91 100644 --- a/packages/yew/src/dom_bundle/bcomp.rs +++ b/packages/yew/src/dom_bundle/bcomp.rs @@ -7,7 +7,7 @@ use std::fmt; use web_sys::Element; use super::{BNode, BSubtree, Reconcilable, ReconcileTarget}; -use crate::html::{AnyScope, Scoped}; +use crate::html::{Scope, Scoped}; use crate::virtual_dom::{Key, VComp}; use crate::NodeRef; @@ -30,9 +30,7 @@ impl BComp { impl fmt::Debug for BComp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BComp") - .field("root", &self.scope.as_ref().render_state()) - .finish() + f.debug_struct("BComp").finish_non_exhaustive() } } @@ -54,7 +52,7 @@ impl Reconcilable for VComp { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -88,7 +86,7 @@ impl Reconcilable for VComp { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -107,7 +105,7 @@ impl Reconcilable for VComp { fn reconcile( self, _root: &BSubtree, - _parent_scope: &AnyScope, + _parent_scope: &Scope, _parent: &Element, next_sibling: NodeRef, bcomp: &mut Self::Bundle, @@ -129,7 +127,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { @@ -330,8 +328,8 @@ mod tests { } } - fn setup_parent() -> (BSubtree, AnyScope, Element) { - let scope = AnyScope::test(); + fn setup_parent() -> (BSubtree, Scope, Element) { + let scope = Scope::test(); let parent = document().create_element("div").unwrap(); let root = BSubtree::create_root(&parent); @@ -340,7 +338,7 @@ mod tests { (root, scope, parent) } - fn get_html(node: Html, root: &BSubtree, scope: &AnyScope, parent: &Element) -> String { + fn get_html(node: Html, root: &BSubtree, scope: &Scope, parent: &Element) -> String { // clear parent parent.set_inner_html(""); diff --git a/packages/yew/src/dom_bundle/blist.rs b/packages/yew/src/dom_bundle/blist.rs index 40908699571..bee92d699cc 100644 --- a/packages/yew/src/dom_bundle/blist.rs +++ b/packages/yew/src/dom_bundle/blist.rs @@ -9,7 +9,7 @@ use web_sys::Element; use super::{test_log, BNode, BSubtree}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; -use crate::html::{AnyScope, NodeRef}; +use crate::html::{NodeRef, Scope}; use crate::virtual_dom::{Key, VList, VNode, VText}; /// This struct represents a mounted [VList] @@ -34,7 +34,7 @@ impl Deref for BList { #[derive(Clone)] struct NodeWriter<'s> { root: &'s BSubtree, - parent_scope: &'s AnyScope, + parent_scope: &'s Scope, parent: &'s Element, next_sibling: NodeRef, } @@ -146,7 +146,7 @@ impl BList { /// Diff and patch unkeyed child lists fn apply_unkeyed( root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, lefts: Vec, @@ -187,7 +187,7 @@ impl BList { /// middle. fn apply_keyed( root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, left_vdoms: Vec, @@ -397,7 +397,7 @@ impl Reconcilable for VList { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -409,7 +409,7 @@ impl Reconcilable for VList { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -423,7 +423,7 @@ impl Reconcilable for VList { fn reconcile( mut self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, blist: &mut BList, @@ -474,7 +474,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 1791729726a..fde974df655 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -6,7 +6,7 @@ use web_sys::{Element, Node}; use super::{BComp, BList, BPortal, BSubtree, BSuspense, BTag, BText}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; -use crate::html::{AnyScope, NodeRef}; +use crate::html::{NodeRef, Scope}; use crate::virtual_dom::{Key, VNode}; /// The bundle implementation to [VNode]. @@ -86,7 +86,7 @@ impl Reconcilable for VNode { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -126,7 +126,7 @@ impl Reconcilable for VNode { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -137,7 +137,7 @@ impl Reconcilable for VNode { fn reconcile( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -245,7 +245,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { diff --git a/packages/yew/src/dom_bundle/bportal.rs b/packages/yew/src/dom_bundle/bportal.rs index cc98cca8ef9..717b9364ff7 100644 --- a/packages/yew/src/dom_bundle/bportal.rs +++ b/packages/yew/src/dom_bundle/bportal.rs @@ -4,7 +4,7 @@ use web_sys::Element; use super::{test_log, BNode, BSubtree}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; -use crate::html::{AnyScope, NodeRef}; +use crate::html::{NodeRef, Scope}; use crate::virtual_dom::{Key, VPortal}; /// The bundle implementation to [VPortal]. @@ -39,7 +39,7 @@ impl Reconcilable for VPortal { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, host_next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -69,7 +69,7 @@ impl Reconcilable for VPortal { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -85,7 +85,7 @@ impl Reconcilable for VPortal { fn reconcile( self, _root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, portal: &mut Self::Bundle, @@ -211,8 +211,8 @@ mod layout_tests { diff_layouts(layouts) } - fn setup_parent_with_portal() -> (BSubtree, AnyScope, Element, Element) { - let scope = AnyScope::test(); + fn setup_parent_with_portal() -> (BSubtree, Scope, Element, Element) { + let scope = Scope::test(); let parent = document().create_element("div").unwrap(); let portal_host = document().create_element("div").unwrap(); let root = BSubtree::create_root(&parent); diff --git a/packages/yew/src/dom_bundle/bsuspense.rs b/packages/yew/src/dom_bundle/bsuspense.rs index d9ecb216063..34e80cc4ccb 100644 --- a/packages/yew/src/dom_bundle/bsuspense.rs +++ b/packages/yew/src/dom_bundle/bsuspense.rs @@ -6,7 +6,7 @@ use web_sys::Element; #[cfg(feature = "hydration")] use super::Fragment; use super::{BNode, BSubtree, Reconcilable, ReconcileTarget}; -use crate::html::AnyScope; +use crate::html::Scope; use crate::virtual_dom::{Key, VSuspense}; use crate::NodeRef; @@ -76,7 +76,7 @@ impl Reconcilable for VSuspense { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -124,7 +124,7 @@ impl Reconcilable for VSuspense { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -141,7 +141,7 @@ impl Reconcilable for VSuspense { fn reconcile( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, suspense: &mut Self::Bundle, @@ -235,7 +235,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index d504ea0386b..ed5ba327218 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -14,7 +14,7 @@ use wasm_bindgen::JsCast; use web_sys::{Element, HtmlTextAreaElement as TextAreaElement}; use super::{insert_node, BList, BNode, BSubtree, Reconcilable, ReconcileTarget}; -use crate::html::AnyScope; +use crate::html::Scope; use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, SVG_NAMESPACE}; use crate::virtual_dom::{Attributes, Key, VTag}; use crate::NodeRef; @@ -108,7 +108,7 @@ impl Reconcilable for VTag { fn attach( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -157,7 +157,7 @@ impl Reconcilable for VTag { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -196,7 +196,7 @@ impl Reconcilable for VTag { fn reconcile( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, _parent: &Element, _next_sibling: NodeRef, tag: &mut Self::Bundle, @@ -301,7 +301,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { @@ -392,15 +392,15 @@ mod tests { use super::*; use crate::dom_bundle::{BNode, Reconcilable, ReconcileTarget}; - use crate::html::AnyScope; + use crate::html::Scope; use crate::virtual_dom::vtag::{HTML_NAMESPACE, SVG_NAMESPACE}; use crate::virtual_dom::{AttrValue, VNode, VTag}; use crate::{html, Html, NodeRef}; wasm_bindgen_test_configure!(run_in_browser); - fn setup_parent() -> (BSubtree, AnyScope, Element) { - let scope = AnyScope::test(); + fn setup_parent() -> (BSubtree, Scope, Element) { + let scope = Scope::test(); let parent = document().create_element("div").unwrap(); let root = BSubtree::create_root(&parent); diff --git a/packages/yew/src/dom_bundle/btext.rs b/packages/yew/src/dom_bundle/btext.rs index e482883f6cb..b1cba242fb8 100644 --- a/packages/yew/src/dom_bundle/btext.rs +++ b/packages/yew/src/dom_bundle/btext.rs @@ -4,7 +4,7 @@ use gloo::utils::document; use web_sys::{Element, Text as TextNode}; use super::{insert_node, BNode, BSubtree, Reconcilable, ReconcileTarget}; -use crate::html::AnyScope; +use crate::html::Scope; use crate::virtual_dom::{AttrValue, VText}; use crate::NodeRef; @@ -42,7 +42,7 @@ impl Reconcilable for VText { fn attach( self, _root: &BSubtree, - _parent_scope: &AnyScope, + _parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { @@ -57,7 +57,7 @@ impl Reconcilable for VText { fn reconcile_node( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -71,7 +71,7 @@ impl Reconcilable for VText { fn reconcile( self, _root: &BSubtree, - _parent_scope: &AnyScope, + _parent_scope: &Scope, _parent: &Element, _next_sibling: NodeRef, btext: &mut Self::Bundle, @@ -103,7 +103,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle) { diff --git a/packages/yew/src/dom_bundle/mod.rs b/packages/yew/src/dom_bundle/mod.rs index a8cac1c900d..ad408785acd 100644 --- a/packages/yew/src/dom_bundle/mod.rs +++ b/packages/yew/src/dom_bundle/mod.rs @@ -7,7 +7,7 @@ use web_sys::Element; -use crate::html::{AnyScope, NodeRef}; +use crate::html::{NodeRef, Scope}; use crate::virtual_dom::VNode; mod bcomp; @@ -59,7 +59,7 @@ impl Bundle { pub fn reconcile( &mut self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, next_node: VNode, @@ -87,7 +87,7 @@ mod feat_hydration { /// Creates a bundle by hydrating a virtual dom layout. pub fn hydrate( root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, node: VNode, diff --git a/packages/yew/src/dom_bundle/traits.rs b/packages/yew/src/dom_bundle/traits.rs index 10712be2c04..2ed9fc50ebe 100644 --- a/packages/yew/src/dom_bundle/traits.rs +++ b/packages/yew/src/dom_bundle/traits.rs @@ -1,7 +1,7 @@ use web_sys::Element; use super::{BNode, BSubtree}; -use crate::html::{AnyScope, NodeRef}; +use crate::html::{NodeRef, Scope}; /// A Reconcile Target. /// @@ -36,7 +36,7 @@ pub(super) trait Reconcilable { self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle); @@ -59,7 +59,7 @@ pub(super) trait Reconcilable { self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -68,7 +68,7 @@ pub(super) trait Reconcilable { fn reconcile( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut Self::Bundle, @@ -79,7 +79,7 @@ pub(super) trait Reconcilable { self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, next_sibling: NodeRef, bundle: &mut BNode, @@ -111,7 +111,7 @@ mod feat_hydration { fn hydrate( self, root: &BSubtree, - parent_scope: &AnyScope, + parent_scope: &Scope, parent: &Element, fragment: &mut Fragment, ) -> (NodeRef, Self::Bundle); diff --git a/packages/yew/src/functional/hooks/mod.rs b/packages/yew/src/functional/hooks/mod.rs index 7533b5ce7ee..3fbbb7c1fb4 100644 --- a/packages/yew/src/functional/hooks/mod.rs +++ b/packages/yew/src/functional/hooks/mod.rs @@ -64,7 +64,7 @@ pub(crate) fn use_component_id() -> impl Hook { type Output = usize; fn run(self, ctx: &mut HookContext) -> Self::Output { - ctx.scope.get_id() + ctx.scope.id() } } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 506e4a70a4f..5238dd495b4 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -29,7 +29,7 @@ use wasm_bindgen::prelude::*; #[cfg(all(feature = "hydration", feature = "ssr"))] use crate::html::RenderMode; -use crate::html::{AnyScope, BaseComponent, Context, HtmlResult}; +use crate::html::{BaseComponent, Context, HtmlResult, Scope}; use crate::{Html, Properties}; mod hooks; @@ -80,7 +80,7 @@ pub(crate) trait Effect { /// A hook context to be passed to hooks. pub struct HookContext { - pub(crate) scope: AnyScope, + pub(crate) scope: Scope, #[cfg(all(feature = "hydration", feature = "ssr"))] creation_mode: RenderMode, re_render: ReRender, @@ -103,7 +103,7 @@ pub struct HookContext { impl HookContext { fn new( - scope: AnyScope, + scope: Scope, re_render: ReRender, #[cfg(all(feature = "hydration", feature = "ssr"))] creation_mode: RenderMode, #[cfg(feature = "hydration")] prepared_state: Option<&str>, diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 079d5b006c5..8a58be53019 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use web_sys::Element; -use super::scope::AnyScope; +use super::scope::Scope; #[cfg(feature = "hydration")] use crate::dom_bundle::Fragment; use crate::dom_bundle::{BSubtree, Bundle}; @@ -60,7 +60,7 @@ impl ComponentState { level = tracing::Level::DEBUG, name = "create", skip_all, - fields(component.id = context.link().get_id()), + fields(component.id = context.link().id()), )] fn new( component: FunctionComponent, @@ -96,8 +96,8 @@ impl ComponentState { next_sibling: NodeRef, internal_ref: NodeRef, ) { - let state = context.scope.state.clone(); - let mut current_state = state.borrow_mut(); + let scope = context.scope.clone(); + let mut current_state = scope.state_cell().borrow_mut(); if current_state.is_none() { let mut self_ = Self::new( @@ -117,24 +117,30 @@ impl ComponentState { } } - pub fn run_render(scope: &AnyScope) { - if let Some(state) = scope.state.borrow_mut().as_mut() { + pub fn run_render(scope: &Scope) { + if let Some(state) = scope.state_cell().borrow_mut().as_mut() { state.render(); } } + pub fn run_shift(scope: &Scope, next_parent: Element, next_sibling: NodeRef) { + if let Some(state) = scope.state_cell().borrow_mut().as_mut() { + state.shift(next_parent, next_sibling); + } + } + pub fn run_update_props( - scope: &AnyScope, + scope: &Scope, props: Option>, next_sibling: Option, ) { - if let Some(state) = scope.state.borrow_mut().as_mut() { + if let Some(state) = scope.state_cell().borrow_mut().as_mut() { state.changed(props, next_sibling); } } - pub fn run_destroy(scope: &AnyScope, parent_to_detach: bool) { - if let Some(state) = scope.state.borrow_mut().take() { + pub fn run_destroy(scope: &Scope, parent_to_detach: bool) { + if let Some(state) = scope.state_cell().borrow_mut().take() { state.destroy(parent_to_detach); } } @@ -146,7 +152,7 @@ impl ComponentState { let suspense_scope = comp_scope .find_parent_scope::>() .unwrap(); - resume_suspension(&suspense_scope, m); + resume_suspension(suspense_scope, m); } } @@ -168,7 +174,7 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.context.link().get_id()) + fields(component.id = self.context.link().id()) )] fn destroy(mut self, parent_to_detach: bool) { self.component.destroy(); @@ -191,7 +197,7 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip_all, - fields(component.id = self.context.link().get_id()) + fields(component.id = self.context.link().id()) )] fn render(&mut self) { match self.component.render(self.context.props().as_ref()) { @@ -225,12 +231,12 @@ impl ComponentState { if let Some(ref last_suspension) = self.suspension { if &suspension != last_suspension { // We remove previous suspension from the suspense. - resume_suspension(&suspense_scope, last_suspension.clone()) + resume_suspension(suspense_scope, last_suspension.clone()) } } self.suspension = Some(suspension.clone()); - suspend_suspension(&suspense_scope, suspension); + suspend_suspension(suspense_scope, suspension); } } @@ -283,7 +289,7 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.context.link().get_id()) + fields(component.id = self.context.link().id()) )] pub(super) fn changed(&mut self, props: Option>, next_sibling: Option) { if let Some(next_sibling) = next_sibling { @@ -335,7 +341,7 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.context.link().get_id()) + fields(component.id = self.context.link().id()) )] pub(super) fn rendered(&mut self) -> bool { if self.suspension.is_none() { diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 88f0acb67d9..1bc4cfc6293 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -13,8 +13,7 @@ use std::rc::Rc; pub use children::*; pub use marker::*; pub use properties::*; -pub use scope::AnyScope; -pub(crate) use scope::Scope; +pub use scope::Scope; #[cfg(feature = "csr")] pub(crate) use scope::Scoped; @@ -34,7 +33,7 @@ pub(crate) enum RenderMode { #[derive(Debug)] pub struct Context { props: Rc, - scope: AnyScope, + scope: Scope, #[cfg(feature = "hydration")] creation_mode: RenderMode, @@ -45,7 +44,7 @@ pub struct Context { impl Context { /// The component scope #[inline] - pub fn link(&self) -> &AnyScope { + pub fn link(&self) -> &Scope { &self.scope } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 539352f0e17..c75033eb303 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -3,7 +3,6 @@ use std::any::{Any, TypeId}; #[cfg(feature = "csr")] use std::cell::RefCell; -use std::marker::PhantomData; use std::rc::Rc; use std::{fmt, iter}; @@ -15,43 +14,31 @@ use crate::context::{ContextHandle, ContextProvider, ContextStore}; #[cfg(feature = "hydration")] use crate::html::RenderMode; -/// Untyped scope used for accessing parent scope -#[derive(Clone)] -pub struct AnyScope { +struct ScopeInner { id: usize, type_id: TypeId, #[cfg(feature = "csr")] - pub(crate) state: Rc>>, + pub(crate) state: RefCell>, - parent: Option>, - typed_scope: Rc, + parent: Option, } -impl fmt::Debug for AnyScope { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("AnyScope<_>") - } +/// Untyped scope used for accessing parent scope +#[derive(Clone)] +pub struct Scope { + inner: Rc, } -impl From> for AnyScope { - fn from(scope: Scope) -> Self { - AnyScope { - id: scope.id, - type_id: TypeId::of::(), - - #[cfg(feature = "csr")] - state: scope.state.clone(), - - parent: scope.parent.clone(), - typed_scope: Rc::new(scope), - } +impl fmt::Debug for Scope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("AnyScope<_>") } } -impl AnyScope { - pub(crate) fn get_id(&self) -> usize { - self.id +impl Scope { + pub(crate) fn id(&self) -> usize { + self.inner.id } /// Schedules a render. @@ -66,38 +53,31 @@ impl AnyScope { } /// Returns the parent scope - pub fn get_parent(&self) -> Option<&AnyScope> { - self.parent.as_deref() + pub fn parent(&self) -> Option<&Scope> { + self.inner.parent.as_ref() } - /// Returns the type of the linked component - pub fn get_type_id(&self) -> &TypeId { - &self.type_id + pub(crate) fn state_cell(&self) -> &RefCell> { + &self.inner.state } - /// Attempts to downcast into a typed scope - /// - /// # Panics - /// - /// If the self value can't be cast into the target type. - #[cfg(feature = "csr")] - pub(crate) fn downcast(&self) -> Scope { - self.try_downcast::().unwrap() + /// Returns the type of the linked component + pub fn type_id(&self) -> TypeId { + self.inner.type_id } - /// Attempts to downcast into a typed scope + /// Attempts checks the component type of current scope /// /// Returns [`None`] if the self value can't be cast into the target type. - pub(crate) fn try_downcast(&self) -> Option> { - self.typed_scope.downcast_ref::>().cloned() + pub(crate) fn is_scope_of(&self) -> bool { + self.type_id() == TypeId::of::() } /// Attempts to find a parent scope of a certain type /// /// Returns [`None`] if no parent scope with the specified type was found. - pub(crate) fn find_parent_scope(&self) -> Option> { - iter::successors(Some(self), |scope| scope.get_parent()) - .find_map(AnyScope::try_downcast::) + pub(crate) fn find_parent_scope(&self) -> Option<&Scope> { + iter::successors(Some(self), |scope| scope.parent()).find(|m| m.is_scope_of::()) } /// Accesses a value provided by a parent `ContextProvider` component of the @@ -107,42 +87,42 @@ impl AnyScope { callback: Callback, ) -> Option<(T, ContextHandle)> { let scope = self.find_parent_scope::>()?; - let store = ContextStore::::get(&AnyScope::from(scope))?; + let store = ContextStore::::get(scope)?; Some(ContextStore::subscribe_consumer(store, callback)) } } /// A context which allows sending messages to a component. -pub(crate) struct Scope { - _marker: PhantomData, - parent: Option>, +// pub(crate) struct Scope { +// _marker: PhantomData, +// parent: Option>, - #[cfg(feature = "csr")] - pub(crate) state: Rc>>, +// #[cfg(feature = "csr")] +// pub(crate) state: Rc>>, - pub(crate) id: usize, -} +// pub(crate) id: usize, +// } -impl fmt::Debug for Scope { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Scope<_>") - } -} +// impl fmt::Debug for Scope { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.write_str("Scope<_>") +// } +// } -impl Clone for Scope { - fn clone(&self) -> Self { - Scope { - _marker: PhantomData, +// impl Clone for Scope { +// fn clone(&self) -> Self { +// Scope { +// _marker: PhantomData, - parent: self.parent.clone(), +// parent: self.parent.clone(), - #[cfg(feature = "csr")] - state: self.state.clone(), +// #[cfg(feature = "csr")] +// state: self.state.clone(), - id: self.id, - } - } -} +// id: self.id, +// } +// } +// } #[cfg(feature = "ssr")] mod feat_ssr { @@ -156,21 +136,22 @@ mod feat_ssr { use crate::virtual_dom::Collectable; use crate::Context; - impl Scope { - pub(crate) async fn render_into_stream( + impl Scope { + pub(crate) async fn render_into_stream( &self, w: &mut BufWriter, props: Rc, hydratable: bool, - ) { + ) where + COMP: BaseComponent, + { // Rust's Future implementation is stack-allocated and incurs zero runtime-cost. // // If the content of this channel is ready before it is awaited, it is // similar to taking the value from a mutex lock. - let scope = AnyScope::from(self.clone()); let context = Context { - scope: scope.clone(), + scope: self.clone(), props: props as Rc, #[cfg(feature = "hydration")] creation_mode: RenderMode::Ssr, @@ -193,9 +174,7 @@ mod feat_ssr { } }; - let self_any_scope = AnyScope::from(self.clone()); - html.render_into_stream(w, &self_any_scope, hydratable) - .await; + html.render_into_stream(w, self, hydratable).await; if let Some(prepared_state) = component.prepare_state() { let _ = w.write_str(r#""#); From c67d1c9d384b5d2c5d5806e68f935d2db6fe198d Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 20:32:57 +0900 Subject: [PATCH 38/47] Merge BaseComponent and Component. --- packages/yew-macro/src/function_component.rs | 2 +- .../yew-macro/src/html_tree/html_component.rs | 2 +- packages/yew/src/app_handle.rs | 8 ++++---- packages/yew/src/html/component/intrinsic.rs | 8 ++++---- packages/yew/src/html/component/marker.rs | 4 ++-- packages/yew/src/html/component/mod.rs | 2 +- packages/yew/src/html/component/scope.rs | 6 +++--- packages/yew/src/html/conversion.rs | 14 ++++++------- packages/yew/src/lib.rs | 4 ++-- packages/yew/src/renderer.rs | 12 +++++------ packages/yew/src/server_renderer.rs | 20 +++++++++---------- packages/yew/src/virtual_dom/vcomp.rs | 18 ++++++++--------- packages/yew/src/virtual_dom/vnode.rs | 4 ++-- 13 files changed, 52 insertions(+), 52 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index abc926abb8f..fb0ad8208f2 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -303,7 +303,7 @@ impl FunctionComponent { // we cannot disable any lints here because it will be applied to the function body // as well. #(#component_impl_attrs)* - impl #impl_generics ::yew::functional::Component for #component_name #ty_generics #where_clause { + impl #impl_generics ::yew::html::Component for #component_name #ty_generics #where_clause { type Properties = #props_type; fn create() -> Self { diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 4f557134c36..041f7ee3bb8 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -124,7 +124,7 @@ impl ToTokens for HtmlComponent { } = self; let ty_span = ty.span().resolved_at(Span::call_site()); - let props_ty = quote_spanned!(ty_span=> <#ty as ::yew::html::BaseComponent>::Properties); + let props_ty = quote_spanned!(ty_span=> <#ty as ::yew::html::Component>::Properties); let children_renderer = if children.is_empty() { None } else { diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 232474030c3..0eb7353f946 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -6,13 +6,13 @@ use std::rc::Rc; use web_sys::Element; use crate::dom_bundle::BSubtree; -use crate::html::{BaseComponent, ComponentIntrinsic, NodeRef, Scope}; +use crate::html::{Component, ComponentIntrinsic, NodeRef, Scope}; use crate::scheduler; /// An instance of an application. #[cfg(feature = "csr")] #[derive(Debug)] -pub struct AppHandle { +pub struct AppHandle { /// `Scope` holder pub(crate) scope: Scope, _marker: PhantomData, @@ -20,7 +20,7 @@ pub struct AppHandle { impl AppHandle where - COMP: BaseComponent, + COMP: Component, { /// The main entry point of a Yew program which also allows passing properties. It works /// similarly to the `program` function in Elm. You should provide an initial model, `update` @@ -100,7 +100,7 @@ mod feat_hydration { impl AppHandle where - COMP: BaseComponent, + COMP: Component, { #[tracing::instrument( level = tracing::Level::DEBUG, diff --git a/packages/yew/src/html/component/intrinsic.rs b/packages/yew/src/html/component/intrinsic.rs index 1566382cc47..90c506941c4 100644 --- a/packages/yew/src/html/component/intrinsic.rs +++ b/packages/yew/src/html/component/intrinsic.rs @@ -7,7 +7,7 @@ use futures::future::{FutureExt, LocalBoxFuture}; #[cfg(feature = "csr")] use web_sys::Element; -use super::BaseComponent; +use super::Component; #[cfg(any(feature = "csr", feature = "ssr"))] use super::Scope; #[cfg(feature = "csr")] @@ -66,11 +66,11 @@ pub(crate) trait Intrinsical { ) -> Scope; } -pub(crate) struct ComponentIntrinsic { +pub(crate) struct ComponentIntrinsic { props: COMP::Properties, } -impl ComponentIntrinsic { +impl ComponentIntrinsic { pub fn new(props: COMP::Properties) -> Self { Self { props } } @@ -80,7 +80,7 @@ impl ComponentIntrinsic { } } -impl Intrinsical for ComponentIntrinsic { +impl Intrinsical for ComponentIntrinsic { fn as_any(&self) -> &dyn Any { self as &dyn Any } diff --git a/packages/yew/src/html/component/marker.rs b/packages/yew/src/html/component/marker.rs index 9a970b6d242..8e557c4de3f 100644 --- a/packages/yew/src/html/component/marker.rs +++ b/packages/yew/src/html/component/marker.rs @@ -1,6 +1,6 @@ //! Primitive Components & Properties Types -use crate::html::{BaseComponent, ChildrenProps, Html}; +use crate::html::{ChildrenProps, Component, Html}; use crate::{function_component, html}; /// A Component to represent a component that does not exist in current implementation. @@ -140,7 +140,7 @@ use crate::{function_component, html}; #[function_component] pub fn PhantomComponent(props: &ChildrenProps) -> Html where - T: BaseComponent, + T: Component, { html! { <>{props.children.clone()} } } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 424c90d333c..bfa7fd0b86e 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -73,4 +73,4 @@ impl Context { } } -pub use crate::functional::Component as BaseComponent; +pub use crate::functional::Component; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 068a5e068bf..02d3e212903 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -8,7 +8,7 @@ use std::{fmt, iter}; #[cfg(feature = "csr")] use super::lifecycle::ComponentState; -use super::BaseComponent; +use super::Component; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; #[cfg(feature = "hydration")] @@ -65,14 +65,14 @@ impl Scope { /// Attempts checks the component type of current scope /// /// Returns [`None`] if the self value can't be cast into the target type. - pub(crate) fn is_scope_of(&self) -> bool { + pub(crate) fn is_scope_of(&self) -> bool { self.type_id() == TypeId::of::() } /// Attempts to find a parent scope of a certain type /// /// Returns [`None`] if no parent scope with the specified type was found. - pub(crate) fn find_parent_scope(&self) -> Option<&Scope> { + pub(crate) fn find_parent_scope(&self) -> Option<&Scope> { iter::successors(Some(self), |scope| scope.parent()).find(|m| m.is_scope_of::()) } diff --git a/packages/yew/src/html/conversion.rs b/packages/yew/src/html/conversion.rs index 8c0a1e7f897..46f8b98f2f5 100644 --- a/packages/yew/src/html/conversion.rs +++ b/packages/yew/src/html/conversion.rs @@ -4,7 +4,7 @@ use implicit_clone::unsync::{IArray, IMap}; pub use implicit_clone::ImplicitClone; use super::super::callback::Callback; -use super::{BaseComponent, Children, ChildrenRenderer, NodeRef, Scope}; +use super::{Children, ChildrenRenderer, Component, NodeRef, Scope}; use crate::virtual_dom::{AttrValue, VChild, VNode}; impl ImplicitClone for NodeRef {} @@ -83,7 +83,7 @@ where impl IntoPropValue>> for VChild where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> ChildrenRenderer> { @@ -93,7 +93,7 @@ where impl IntoPropValue>>> for VChild where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> Option>> { @@ -103,7 +103,7 @@ where impl IntoPropValue>>> for Option> where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> Option>> { @@ -113,7 +113,7 @@ where impl IntoPropValue>> for Vec> where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> ChildrenRenderer> { @@ -123,7 +123,7 @@ where impl IntoPropValue>>> for Vec> where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> Option>> { @@ -133,7 +133,7 @@ where impl IntoPropValue>>> for Option>> where - T: BaseComponent, + T: Component, { #[inline] fn into_prop_value(self) -> Option>> { diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 5c453a78092..b28cdb64cd6 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -335,8 +335,8 @@ pub mod prelude { pub use crate::events::*; pub use crate::functional::*; pub use crate::html::{ - create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Html, HtmlResult, - NodeRef, Properties, + create_portal, Children, ChildrenWithProps, Classes, Component, Html, HtmlResult, NodeRef, + Properties, }; pub use crate::macros::{classes, html, html_nested}; pub use crate::suspense::Suspense; diff --git a/packages/yew/src/renderer.rs b/packages/yew/src/renderer.rs index 08b9c52a95f..56b291aee97 100644 --- a/packages/yew/src/renderer.rs +++ b/packages/yew/src/renderer.rs @@ -4,7 +4,7 @@ use std::panic::PanicInfo; use web_sys::Element; use crate::app_handle::AppHandle; -use crate::html::BaseComponent; +use crate::html::Component; thread_local! { static PANIC_HOOK_IS_SET: Cell = Cell::new(false); @@ -33,7 +33,7 @@ fn set_default_panic_hook() { #[must_use = "Renderer does nothing unless render() is called."] pub struct Renderer where - COMP: BaseComponent + 'static, + COMP: Component + 'static, { root: Element, props: COMP::Properties, @@ -41,7 +41,7 @@ where impl Default for Renderer where - COMP: BaseComponent + 'static, + COMP: Component + 'static, COMP::Properties: Default, { fn default() -> Self { @@ -51,7 +51,7 @@ where impl Renderer where - COMP: BaseComponent + 'static, + COMP: Component + 'static, COMP::Properties: Default, { /// Creates a [Renderer] that renders into the document body with default properties. @@ -67,7 +67,7 @@ where impl Renderer where - COMP: BaseComponent + 'static, + COMP: Component + 'static, { /// Creates a [Renderer] that renders into the document body with custom properties. pub fn with_props(props: COMP::Properties) -> Self { @@ -99,7 +99,7 @@ mod feat_hydration { impl Renderer where - COMP: BaseComponent + 'static, + COMP: Component + 'static, { /// Hydrates the application. pub fn hydrate(self) -> AppHandle { diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 1514eb2cede..81d5d5a7720 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -6,7 +6,7 @@ use futures::pin_mut; use futures::stream::{Stream, StreamExt}; use tracing::Instrument; -use crate::html::{BaseComponent, ComponentIntrinsic, Scope}; +use crate::html::{Component, ComponentIntrinsic, Scope}; use crate::platform::fmt::BufStream; use crate::platform::{LocalHandle, Runtime}; @@ -23,7 +23,7 @@ use crate::platform::{LocalHandle, Runtime}; #[derive(Debug)] pub struct LocalServerRenderer where - COMP: BaseComponent, + COMP: Component, { props: COMP::Properties, hydratable: bool, @@ -31,7 +31,7 @@ where impl Default for LocalServerRenderer where - COMP: BaseComponent, + COMP: Component, COMP::Properties: Default, { fn default() -> Self { @@ -41,7 +41,7 @@ where impl LocalServerRenderer where - COMP: BaseComponent, + COMP: Component, COMP::Properties: Default, { /// Creates a [LocalServerRenderer] with default properties. @@ -52,7 +52,7 @@ where impl LocalServerRenderer where - COMP: BaseComponent, + COMP: Component, { /// Creates a [LocalServerRenderer] with custom properties. pub fn with_props(props: COMP::Properties) -> Self { @@ -125,7 +125,7 @@ where #[cfg(feature = "ssr")] pub struct ServerRenderer where - COMP: BaseComponent, + COMP: Component, { create_props: Box COMP::Properties>, hydratable: bool, @@ -134,7 +134,7 @@ where impl fmt::Debug for ServerRenderer where - COMP: BaseComponent, + COMP: Component, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("ServerRenderer<_>") @@ -143,7 +143,7 @@ where impl Default for ServerRenderer where - COMP: BaseComponent, + COMP: Component, COMP::Properties: Default, { fn default() -> Self { @@ -153,7 +153,7 @@ where impl ServerRenderer where - COMP: BaseComponent, + COMP: Component, COMP::Properties: Default, { /// Creates a [ServerRenderer] with default properties. @@ -164,7 +164,7 @@ where impl ServerRenderer where - COMP: BaseComponent, + COMP: Component, { /// Creates a [ServerRenderer] with custom properties. /// diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 41268b9e52f..17988ffe2af 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -5,7 +5,7 @@ use std::fmt; use std::rc::Rc; use super::Key; -use crate::html::{BaseComponent, ComponentIntrinsic, Intrinsical}; +use crate::html::{Component, ComponentIntrinsic, Intrinsical}; /// A virtual component. pub struct VComp { @@ -38,14 +38,14 @@ impl Clone for VComp { } /// A virtual child component. -pub struct VChild { +pub struct VChild { /// The component properties intrinsic: Rc>, /// Reference to the mounted node key: Option, } -impl Clone for VChild { +impl Clone for VChild { fn clone(&self) -> Self { VChild { intrinsic: Rc::clone(&self.intrinsic), @@ -54,7 +54,7 @@ impl Clone for VChild { } } -impl PartialEq for VChild +impl PartialEq for VChild where COMP::Properties: PartialEq, { @@ -65,7 +65,7 @@ where impl VChild where - COMP: BaseComponent, + COMP: Component, { /// Creates a child component that can be accessed and modified by its parent. pub fn new(props: COMP::Properties, key: Option) -> Self { @@ -78,7 +78,7 @@ where impl From> for VComp where - COMP: BaseComponent, + COMP: Component, { fn from(vchild: VChild) -> Self { VComp::with_intrinsic::(vchild.intrinsic, vchild.key) @@ -89,7 +89,7 @@ impl VComp { /// Creates a new `VComp` instance. pub fn new(props: COMP::Properties, key: Option) -> Self where - COMP: BaseComponent, + COMP: Component, { VComp { type_id: TypeId::of::(), @@ -102,7 +102,7 @@ impl VComp { /// Creates a new `VComp` instance. pub(crate) fn with_intrinsic(intrinsic: I, key: Option) -> Self where - COMP: BaseComponent, + COMP: Component, I: Into>>, { VComp { @@ -120,7 +120,7 @@ impl PartialEq for VComp { } } -impl fmt::Debug for VChild { +impl fmt::Debug for VChild { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("VChild<_>") } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 6316195934a..a55ab497f67 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -7,7 +7,7 @@ use std::iter::FromIterator; use web_sys::Node; use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText}; -use crate::html::BaseComponent; +use crate::html::Component; use crate::virtual_dom::VRaw; use crate::AttrValue; @@ -138,7 +138,7 @@ impl From for VNode { impl From> for VNode where - COMP: BaseComponent, + COMP: Component, { fn from(vchild: VChild) -> Self { VNode::VComp(VComp::from(vchild)) From 581aa169d39cab72438d785bf66bddafcf2b8cfb Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 21:09:35 +0900 Subject: [PATCH 39/47] Remove Context. --- packages/yew/src/functional/mod.rs | 25 ++-- packages/yew/src/html/component/intrinsic.rs | 2 - packages/yew/src/html/component/lifecycle.rs | 114 ++++++++++--------- packages/yew/src/html/component/mod.rs | 50 -------- packages/yew/src/html/component/scope.rs | 60 +++++----- tools/Cargo.lock | 12 ++ 6 files changed, 114 insertions(+), 149 deletions(-) diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index f8a59deac08..f4e1f51e98a 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -28,7 +28,7 @@ use wasm_bindgen::prelude::*; #[cfg(all(feature = "hydration", feature = "ssr"))] use crate::html::RenderMode; -use crate::html::{Context, HtmlResult, Scope}; +use crate::html::{HtmlResult, Scope}; use crate::Properties; mod hooks; @@ -98,12 +98,16 @@ pub struct HookContext { } impl HookContext { - pub(crate) fn new(ctx: &Context) -> Self { + pub(crate) fn new( + scope: Scope, + #[cfg(feature = "hydration")] creation_mode: RenderMode, + #[cfg(feature = "hydration")] prepared_state: Option<&str>, + ) -> Self { HookContext { - scope: ctx.link().clone(), + scope, #[cfg(all(feature = "hydration", feature = "ssr"))] - creation_mode: ctx.creation_mode(), + creation_mode, states: Vec::new(), @@ -113,7 +117,7 @@ impl HookContext { #[cfg(feature = "hydration")] prepared_states_data: { - match ctx.prepared_state() { + match prepared_state { Some(m) => m.split(',').map(Rc::from).collect(), None => Vec::new(), } @@ -127,11 +131,11 @@ impl HookContext { } } - pub(crate) fn scope(&self) -> &Scope { + fn scope(&self) -> &Scope { &self.scope } - pub(crate) fn next_state(&mut self, initializer: impl FnOnce() -> T) -> Rc + fn next_state(&mut self, initializer: impl FnOnce() -> T) -> Rc where T: 'static, { @@ -152,7 +156,7 @@ impl HookContext { state.downcast().unwrap_throw() } - pub(crate) fn next_effect(&mut self, initializer: impl FnOnce() -> T) -> Rc + fn next_effect(&mut self, initializer: impl FnOnce() -> T) -> Rc where T: 'static + Effect, { @@ -168,10 +172,7 @@ impl HookContext { } #[cfg(any(feature = "hydration", feature = "ssr"))] - pub(crate) fn next_prepared_state( - &mut self, - initializer: impl FnOnce(Option<&str>) -> T, - ) -> Rc + fn next_prepared_state(&mut self, initializer: impl FnOnce(Option<&str>) -> T) -> Rc where T: 'static + PreparedState, { diff --git a/packages/yew/src/html/component/intrinsic.rs b/packages/yew/src/html/component/intrinsic.rs index 90c506941c4..4d2ead0a338 100644 --- a/packages/yew/src/html/component/intrinsic.rs +++ b/packages/yew/src/html/component/intrinsic.rs @@ -26,12 +26,10 @@ use crate::HtmlResult; pub(crate) trait Intrinsical { fn as_any(&self) -> &dyn Any; fn type_id(&self) -> TypeId; - #[cfg(any(feature = "hydration", feature = "ssr"))] fn create_collectable(&self) -> Collectable; fn intrinsic_eq(&self, other: &dyn Intrinsical) -> bool; - fn render(&self, ctx: &mut HookContext) -> HtmlResult; #[cfg(feature = "csr")] diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index ecceca27281..fcc48973085 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -6,15 +6,15 @@ use web_sys::Element; use super::scope::Scope; use crate::dom_bundle::{DomSlot, Realized}; -#[cfg(feature = "hydration")] -use crate::html::RenderMode; -use crate::html::{Context, Html, Intrinsical, NodeRef, RenderError}; +use crate::html::{Html, Intrinsical, NodeRef, RenderError}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; use crate::{scheduler, Callback, ContextProvider, HookContext}; pub(crate) struct ComponentState { - pub(super) component: HookContext, - pub(super) context: Context, + pub(super) ctx: HookContext, + + intrinsic: Rc, + scope: Scope, pub slot: DomSlot, @@ -29,12 +29,14 @@ impl ComponentState { level = tracing::Level::DEBUG, name = "create", skip_all, - fields(component.id = context.link().id()), + fields(component.id = scope.id()), )] - fn new(context: Context, slot: DomSlot) -> Self { + fn new(ctx: HookContext, scope: Scope, intrinsic: Rc, slot: DomSlot) -> Self { Self { - component: HookContext::new(&context), - context, + ctx, + intrinsic, + scope, + suspension: None, slot, @@ -44,12 +46,17 @@ impl ComponentState { } } - pub fn run_create(context: Context, slot: DomSlot) { - let scope = context.scope.clone(); - let mut current_state = scope.state_cell().borrow_mut(); + pub fn run_create( + ctx: HookContext, + scope: Scope, + intrinsic: Rc, + slot: DomSlot, + ) { + let scope_ = scope.clone(); + let mut current_state = scope_.state_cell().borrow_mut(); if current_state.is_none() { - let mut self_ = Self::new(context, slot); + let mut self_ = Self::new(ctx, scope, intrinsic, slot); self_.render(); // We are safe to assign afterwards as we mutably borrow the state and don't release it @@ -88,9 +95,8 @@ impl ComponentState { fn resume_existing_suspension(&mut self) { if let Some(m) = self.suspension.take() { - let comp_scope = self.context.link(); - - let suspense_scope = comp_scope + let suspense_scope = self + .scope .find_parent_scope::>() .unwrap(); resume_suspension(suspense_scope, m); @@ -115,10 +121,10 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.context.link().id()) + fields(component.id = self.scope.id()) )] fn destroy(mut self, parent_to_detach: bool) { - self.component.destroy(); + self.ctx.destroy(); self.resume_existing_suspension(); match self.slot.content { @@ -138,10 +144,10 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip_all, - fields(component.id = self.context.link().id()) + fields(component.id = self.scope.id()) )] fn render(&mut self) { - match self.context.intrisic().render(&mut self.component) { + match self.intrinsic.render(&mut self.ctx) { Ok(vnode) => self.commit_render(vnode), Err(RenderError::Suspended(susp)) => self.suspend(susp), }; @@ -155,14 +161,13 @@ impl ComponentState { self.render(); } else { // We schedule a render after current suspension is resumed. - let comp_scope = self.context.link(); - - let suspense_scope = comp_scope + let suspense_scope = self + .scope .find_parent_scope::>() .expect("To suspend rendering, a component is required."); { - let scope = self.context.link().clone(); + let scope = self.scope.clone(); suspension.listen(Callback::from(move |_| { let scope = scope.clone(); scheduler::push(move || ComponentState::run_render(&scope)); @@ -188,11 +193,9 @@ impl ComponentState { match self.slot.content { Realized::Bundle(ref mut bundle) => { - let scope = self.context.link(); - let new_node_ref = bundle.reconcile( &self.slot.root, - scope, + &self.scope, &self.slot.parent, self.slot.next_sibling.clone(), new_root, @@ -209,11 +212,9 @@ impl ComponentState { Realized::Fragement(ref mut fragment) => { use crate::dom_bundle::Bundle; - let scope = self.context.link(); - let (node, bundle) = Bundle::hydrate( &self.slot.root, - scope, + &self.scope, &self.slot.parent, fragment, new_root, @@ -234,7 +235,7 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self, mountable), - fields(component.id = self.context.link().id()) + fields(component.id = self.scope.id()) )] pub(super) fn changed( &mut self, @@ -251,15 +252,15 @@ impl ComponentState { // Only trigger changed if props were changed / next sibling has changed. let schedule_render = '_block: { #[cfg(feature = "hydration")] - if self.context.creation_mode() == RenderMode::Hydration { + { break '_block if let Some(mountable) = mountable.or_else(|| self.pending_mountable.take()) { match self.slot.content { Realized::Bundle { .. } => { self.pending_mountable = None; - if !self.context.mountable.intrinsic_eq(mountable.as_ref()) { - self.context.mountable = mountable; + if !self.intrinsic.intrinsic_eq(mountable.as_ref()) { + self.intrinsic = mountable; } true } @@ -273,13 +274,16 @@ impl ComponentState { }; } - mountable - .and_then(|m| (!self.context.mountable.intrinsic_eq(m.as_ref())).then_some(m)) - .map(|m| { - self.context.mountable = m; - true - }) - .unwrap_or(false) + #[cfg(not(feature = "hydration"))] + { + mountable + .and_then(|m| (!self.intrinsic.intrinsic_eq(m.as_ref())).then_some(m)) + .map(|m| { + self.intrinsic = m; + true + }) + .unwrap_or(false) + } }; tracing::trace!("props_update(schedule_render={})", schedule_render); @@ -292,11 +296,11 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.context.link().id()) + fields(component.id = self.scope.id()) )] pub(super) fn rendered(&mut self) -> bool { if self.suspension.is_none() { - self.component.rendered(); + self.ctx.rendered(); } #[cfg(feature = "hydration")] @@ -339,26 +343,30 @@ mod tests { type Message = (); type Properties = ChildProps; - fn create(_ctx: &Context) -> Self { + fn create(_ctx: &PreRenderContext) -> Self { Child {} } - fn rendered(&mut self, ctx: &Context, _first_render: bool) { + fn rendered(&mut self, ctx: &PreRenderContext, _first_render: bool) { ctx.props() .lifecycle .borrow_mut() .push("child rendered".into()); } - fn update(&mut self, _ctx: &Context, _: Self::Message) -> bool { + fn update(&mut self, _ctx: &PreRenderContext, _: Self::Message) -> bool { false } - fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { + fn changed( + &mut self, + _ctx: &PreRenderContext, + _old_props: &Self::Properties, + ) -> bool { false } - fn view(&self, _ctx: &Context) -> Html { + fn view(&self, _ctx: &PreRenderContext) -> Html { html! {} } } @@ -382,7 +390,7 @@ mod tests { type Message = bool; type Properties = Props; - fn create(ctx: &Context) -> Self { + fn create(ctx: &PreRenderContext) -> Self { ctx.props().lifecycle.borrow_mut().push("create".into()); #[cfg(target_arch = "wasm32")] if let Some(msg) = ctx.props().create_message { @@ -393,7 +401,7 @@ mod tests { } } - fn rendered(&mut self, ctx: &Context, first_render: bool) { + fn rendered(&mut self, ctx: &PreRenderContext, first_render: bool) { if let Some(msg) = ctx.props().rendered_message.borrow_mut().take() { ctx.link().send_message(msg); } @@ -403,7 +411,7 @@ mod tests { .push(format!("rendered({})", first_render)); } - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + fn update(&mut self, ctx: &PreRenderContext, msg: Self::Message) -> bool { if let Some(msg) = ctx.props().update_message.borrow_mut().take() { ctx.link().send_message(msg); } @@ -414,13 +422,13 @@ mod tests { msg } - fn changed(&mut self, ctx: &Context, _old_props: &Self::Properties) -> bool { + fn changed(&mut self, ctx: &PreRenderContext, _old_props: &Self::Properties) -> bool { self.lifecycle = Rc::clone(&ctx.props().lifecycle); self.lifecycle.borrow_mut().push("change".into()); false } - fn view(&self, ctx: &Context) -> Html { + fn view(&self, ctx: &PreRenderContext) -> Html { if let Some(msg) = ctx.props().view_message.borrow_mut().take() { ctx.link().send_message(msg); } diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index bfa7fd0b86e..6c96b5a4ff8 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -8,9 +8,6 @@ mod marker; mod properties; mod scope; -use std::fmt; -use std::rc::Rc; - pub use children::*; pub(crate) use intrinsic::{ComponentIntrinsic, Intrinsical}; pub use marker::*; @@ -26,51 +23,4 @@ pub(crate) enum RenderMode { Ssr, } -/// The [`Component`]'s context. This contains component's [`Scope`] and props and -/// is passed to every lifecycle method. -pub(crate) struct Context { - mountable: Rc, - scope: Scope, - #[cfg(feature = "hydration")] - creation_mode: RenderMode, - - #[cfg(feature = "hydration")] - prepared_state: Option, -} - -impl fmt::Debug for Context { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Context") - } -} - -impl Context { - /// The component scope - #[inline] - pub fn link(&self) -> &Scope { - &self.scope - } - - pub fn intrisic(&self) -> &dyn Intrinsical { - self.mountable.as_ref() - } - - #[cfg(feature = "hydration")] - pub(crate) fn creation_mode(&self) -> RenderMode { - self.creation_mode - } - - /// The component's prepared state - #[cfg(feature = "hydration")] - pub fn prepared_state(&self) -> Option<&str> { - #[cfg(not(feature = "hydration"))] - let state = None; - - #[cfg(feature = "hydration")] - let state = self.prepared_state.as_deref(); - - state - } -} - pub use crate::functional::Component; diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 02d3e212903..99d5e9f8d1e 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -93,17 +93,17 @@ mod feat_ssr { use std::fmt::Write; use super::*; + use crate::functional::HookContext; #[cfg(feature = "hydration")] use crate::html::RenderMode; - use crate::html::{Context, Intrinsical, RenderError}; + use crate::html::{Intrinsical, RenderError}; use crate::platform::fmt::BufWriter; - use crate::HookContext; impl Scope { - pub(crate) async fn render_into_stream( - &self, + pub(crate) async fn render_into_stream<'a>( + &'a self, mountable: Rc, - w: &mut BufWriter, + w: &'a mut BufWriter, hydratable: bool, ) { // Rust's Future implementation is stack-allocated and incurs zero runtime-cost. @@ -111,16 +111,13 @@ mod feat_ssr { // If the content of this channel is ready before it is awaited, it is // similar to taking the value from a mutex lock. - let context = Context { - scope: self.clone(), - mountable: mountable.clone(), + let mut ctx = HookContext::new( + self.clone(), #[cfg(feature = "hydration")] - creation_mode: RenderMode::Ssr, + RenderMode::Ssr, #[cfg(feature = "hydration")] - prepared_state: None, - }; - - let mut hook_ctx = HookContext::new(&context); + None, + ); let collectable = mountable.create_collectable(); if hydratable { @@ -128,7 +125,7 @@ mod feat_ssr { } let html = loop { - match mountable.render(&mut hook_ctx) { + match mountable.render(&mut ctx) { Ok(m) => break m, Err(RenderError::Suspended(e)) => e.await, } @@ -136,7 +133,7 @@ mod feat_ssr { html.render_into_stream(w, self, hydratable).await; - if let Some(prepared_state) = hook_ctx.prepare_state() { + if let Some(prepared_state) = ctx.prepare_state() { let _ = w.write_str(r#""#); @@ -182,7 +179,8 @@ mod feat_csr { use super::*; use crate::dom_bundle::{BSubtree, DomSlot}; - use crate::html::{Context, Intrinsical, NodeRef}; + use crate::html::{Intrinsical, NodeRef}; + use crate::HookContext; impl Scope { #[cfg(test)] @@ -224,16 +222,15 @@ mod feat_csr { .next_sibling(stable_next_sibling) .build(); - let context = Context { - scope: self.clone(), - mountable: mountable.clone(), + let ctx = HookContext::new( + self.clone(), #[cfg(feature = "hydration")] - creation_mode: RenderMode::Render, + RenderMode::Render, #[cfg(feature = "hydration")] - prepared_state: None, - }; + None, + ); - ComponentState::run_create(context, slot); + ComponentState::run_create(ctx, self.clone(), mountable, slot); } /// Process an event to destroy a component @@ -254,7 +251,8 @@ mod feat_hydration { use super::*; use crate::dom_bundle::{BSubtree, DomSlot, Fragment, Realized}; - use crate::html::{Context, Intrinsical, NodeRef}; + use crate::html::{Intrinsical, NodeRef}; + use crate::HookContext; impl Scope { /// Hydrates the component. @@ -289,13 +287,6 @@ mod feat_hydration { _ => None, }; - let context = Context { - scope: self.clone(), - mountable: mountable.clone(), - creation_mode: RenderMode::Hydration, - prepared_state, - }; - let slot = DomSlot::builder() .content(Realized::Fragement(fragment)) .root(root) @@ -303,7 +294,12 @@ mod feat_hydration { .internal_ref(internal_ref) .build(); - ComponentState::run_create(context, slot); + let ctx = HookContext::new( + self.clone(), + RenderMode::Hydration, + prepared_state.as_deref(), + ); + ComponentState::run_create(ctx, self.clone(), mountable, slot); } } } diff --git a/tools/Cargo.lock b/tools/Cargo.lock index 3739e4756a7..f914a61050d 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -1935,6 +1935,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "typed-builder" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a126a40dbff39e8320900cd61b8de053a2706e1f782cd27145792feb8fd41e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -2303,6 +2314,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "typed-builder", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", From e32bff059aad401cc1eb88787a689f15b672e019 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 21:21:41 +0900 Subject: [PATCH 40/47] Fix features. --- packages/yew/src/functional/mod.rs | 5 ++++- packages/yew/src/html/component/mod.rs | 3 +-- packages/yew/src/html/component/scope.rs | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index f4e1f51e98a..10a2d26a60e 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -98,9 +98,10 @@ pub struct HookContext { } impl HookContext { + #[cfg(any(feature = "csr", feature = "ssr"))] pub(crate) fn new( scope: Scope, - #[cfg(feature = "hydration")] creation_mode: RenderMode, + #[cfg(all(feature = "hydration", feature = "ssr"))] creation_mode: RenderMode, #[cfg(feature = "hydration")] prepared_state: Option<&str>, ) -> Self { HookContext { @@ -241,12 +242,14 @@ impl HookContext { } } + #[cfg(feature = "csr")] pub(crate) fn rendered(&self) { for effect in self.effects.iter() { effect.rendered(); } } + #[cfg(feature = "csr")] pub(crate) fn destroy(&mut self) { // We clear the effects as these are also references to states. self.effects.clear(); diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 6c96b5a4ff8..c70251b777f 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -14,12 +14,11 @@ pub use marker::*; pub use properties::*; pub use scope::Scope; -#[cfg(feature = "hydration")] +#[cfg(all(feature = "hydration", feature = "ssr"))] #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum RenderMode { Hydration, Render, - #[cfg(feature = "ssr")] Ssr, } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 99d5e9f8d1e..563477d087c 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -11,7 +11,7 @@ use super::lifecycle::ComponentState; use super::Component; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider, ContextStore}; -#[cfg(feature = "hydration")] +#[cfg(all(feature = "hydration", feature = "ssr"))] use crate::html::RenderMode; struct ScopeInner { @@ -224,7 +224,7 @@ mod feat_csr { let ctx = HookContext::new( self.clone(), - #[cfg(feature = "hydration")] + #[cfg(all(feature = "hydration", feature = "ssr"))] RenderMode::Render, #[cfg(feature = "hydration")] None, @@ -296,6 +296,7 @@ mod feat_hydration { let ctx = HookContext::new( self.clone(), + #[cfg(feature = "ssr")] RenderMode::Hydration, prepared_state.as_deref(), ); From 2c9083a3a8b095c626b4e758aef02086a6ed06a7 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 21:33:13 +0900 Subject: [PATCH 41/47] Fix variable names. --- packages/yew/src/app_handle.rs | 18 ++++++------ packages/yew/src/dom_bundle/bcomp.rs | 12 ++++---- packages/yew/src/html/component/lifecycle.rs | 30 +++++++++----------- packages/yew/src/html/component/scope.rs | 24 ++++++++-------- packages/yew/src/server_renderer.rs | 6 ++-- packages/yew/src/virtual_dom/vcomp.rs | 12 ++++---- 6 files changed, 50 insertions(+), 52 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 0eb7353f946..f8e2ffd8151 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -33,10 +33,10 @@ where )] pub(crate) fn mount_with_props(host: Element, props: COMP::Properties) -> Self { clear_element(&host); - let mountable = Rc::new(ComponentIntrinsic::::new(props)); + let intrinsic = Rc::new(ComponentIntrinsic::::new(props)); let app = Self { - scope: Scope::new(mountable.as_ref(), None), + scope: Scope::new(intrinsic.as_ref(), None), _marker: PhantomData, }; let hosting_root = BSubtree::create_root(&host); @@ -45,7 +45,7 @@ where let scope = app.scope.clone(); scheduler::push(move || { scope.mount( - mountable, + intrinsic, hosting_root, host, NodeRef::default(), @@ -69,8 +69,8 @@ where skip_all, )] pub fn update(&mut self, new_props: COMP::Properties) { - let mountable = Rc::new(ComponentIntrinsic::::new(new_props)); - self.scope.reuse(mountable, NodeRef::default()) + let intrinsic = Rc::new(ComponentIntrinsic::::new(new_props)); + self.scope.reuse(intrinsic, NodeRef::default()) } /// Schedule the app for destruction @@ -108,10 +108,10 @@ mod feat_hydration { skip(props), )] pub(crate) fn hydrate_with_props(host: Element, props: COMP::Properties) -> Self { - let mountable = Rc::new(ComponentIntrinsic::::new(props)); + let intrinsic = Rc::new(ComponentIntrinsic::::new(props)); let app = Self { - scope: Scope::new(mountable.as_ref(), None), + scope: Scope::new(intrinsic.as_ref(), None), _marker: PhantomData, }; @@ -122,14 +122,14 @@ mod feat_hydration { scheduler::push(move || { scope.hydrate( - mountable.clone(), + intrinsic.clone(), hosting_root, host.clone(), &mut fragment, NodeRef::default(), ); #[cfg(debug_assertions)] // Fix trapped next_sibling at the root - scope.reuse(mountable, NodeRef::default()); + scope.reuse(intrinsic, NodeRef::default()); // We remove all remaining nodes, this mimics the clear_element behaviour in // mount_with_props. diff --git a/packages/yew/src/dom_bundle/bcomp.rs b/packages/yew/src/dom_bundle/bcomp.rs index ed1d0f27ffa..c5e6587fd09 100644 --- a/packages/yew/src/dom_bundle/bcomp.rs +++ b/packages/yew/src/dom_bundle/bcomp.rs @@ -57,13 +57,13 @@ impl Reconcilable for VComp { ) -> (NodeRef, Self::Bundle) { let VComp { type_id, - mountable, + intrinsic, key, .. } = self; let internal_ref = NodeRef::default(); - let scope = mountable.mount( + let scope = intrinsic.mount( root, parent_scope, parent.to_owned(), @@ -109,10 +109,10 @@ impl Reconcilable for VComp { next_sibling: NodeRef, bcomp: &mut Self::Bundle, ) -> NodeRef { - let VComp { mountable, key, .. } = self; + let VComp { intrinsic, key, .. } = self; bcomp.key = key; - mountable.reuse(&bcomp.scope, next_sibling); + intrinsic.reuse(&bcomp.scope, next_sibling); bcomp.internal_ref.clone() } } @@ -132,13 +132,13 @@ mod feat_hydration { ) -> (NodeRef, Self::Bundle) { let VComp { type_id, - mountable, + intrinsic, key, .. } = self; let internal_ref = NodeRef::default(); - let scoped = mountable.hydrate( + let scoped = intrinsic.hydrate( root.clone(), parent_scope, parent.clone(), diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index fcc48973085..24d60bb055c 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -19,7 +19,7 @@ pub(crate) struct ComponentState { pub slot: DomSlot, #[cfg(feature = "hydration")] - pending_mountable: Option>, + pending_intrinsic: Option>, suspension: Option, } @@ -42,7 +42,7 @@ impl ComponentState { slot, #[cfg(feature = "hydration")] - pending_mountable: None, + pending_intrinsic: None, } } @@ -79,11 +79,11 @@ impl ComponentState { pub fn run_update( scope: &Scope, - mountable: Option>, + intrinsic: Option>, next_sibling: Option, ) { if let Some(state) = scope.state_cell().borrow_mut().as_mut() { - state.changed(mountable, next_sibling); + state.changed(intrinsic, next_sibling); } } @@ -234,12 +234,12 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, - skip(self, mountable), + skip(self, intrinsic), fields(component.id = self.scope.id()) )] pub(super) fn changed( &mut self, - mountable: Option>, + intrinsic: Option>, next_sibling: Option, ) { if let Some(next_sibling) = next_sibling { @@ -253,30 +253,28 @@ impl ComponentState { let schedule_render = '_block: { #[cfg(feature = "hydration")] { - break '_block if let Some(mountable) = - mountable.or_else(|| self.pending_mountable.take()) - { + if let Some(intrinsic) = intrinsic.or_else(|| self.pending_intrinsic.take()) { match self.slot.content { Realized::Bundle { .. } => { - self.pending_mountable = None; - if !self.intrinsic.intrinsic_eq(mountable.as_ref()) { - self.intrinsic = mountable; + self.pending_intrinsic = None; + if !self.intrinsic.intrinsic_eq(intrinsic.as_ref()) { + self.intrinsic = intrinsic; } true } Realized::Fragement { .. } => { - self.pending_mountable = Some(mountable); + self.pending_intrinsic = Some(intrinsic); false } } } else { false - }; + } } #[cfg(not(feature = "hydration"))] { - mountable + intrinsic .and_then(|m| (!self.intrinsic.intrinsic_eq(m.as_ref())).then_some(m)) .map(|m| { self.intrinsic = m; @@ -305,7 +303,7 @@ impl ComponentState { #[cfg(feature = "hydration")] { - self.pending_mountable.is_some() + self.pending_intrinsic.is_some() } #[cfg(not(feature = "hydration"))] { diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 563477d087c..e1e68628111 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -102,7 +102,7 @@ mod feat_ssr { impl Scope { pub(crate) async fn render_into_stream<'a>( &'a self, - mountable: Rc, + intrinsic: Rc, w: &'a mut BufWriter, hydratable: bool, ) { @@ -118,14 +118,14 @@ mod feat_ssr { #[cfg(feature = "hydration")] None, ); - let collectable = mountable.create_collectable(); + let collectable = intrinsic.create_collectable(); if hydratable { collectable.write_open_tag(w); } let html = loop { - match mountable.render(&mut ctx) { + match intrinsic.render(&mut ctx) { Ok(m) => break m, Err(RenderError::Suspended(e)) => e.await, } @@ -157,10 +157,10 @@ mod feat_csr_ssr { impl Scope { /// Crate a scope with an optional parent scope - pub(crate) fn new(mountable: &dyn Intrinsical, parent: Option) -> Self { + pub(crate) fn new(intrinsic: &dyn Intrinsical, parent: Option) -> Self { Scope { inner: Rc::new(ScopeInner { - type_id: mountable.type_id(), + type_id: intrinsic.type_id(), #[cfg(feature = "csr")] state: RefCell::new(None), @@ -199,14 +199,14 @@ mod feat_csr { &self.inner.state } - pub(crate) fn reuse(&self, mountable: Rc, next_sibling: NodeRef) { - ComponentState::run_update(self, Some(mountable), Some(next_sibling)); + pub(crate) fn reuse(&self, intrinsic: Rc, next_sibling: NodeRef) { + ComponentState::run_update(self, Some(intrinsic), Some(next_sibling)); } /// Mounts a component with `props` to the specified `element` in the DOM. pub(crate) fn mount( &self, - mountable: Rc, + intrinsic: Rc, root: BSubtree, parent: Element, next_sibling: NodeRef, @@ -230,7 +230,7 @@ mod feat_csr { None, ); - ComponentState::run_create(ctx, self.clone(), mountable, slot); + ComponentState::run_create(ctx, self.clone(), intrinsic, slot); } /// Process an event to destroy a component @@ -265,13 +265,13 @@ mod feat_hydration { /// immediately. pub(crate) fn hydrate( &self, - mountable: Rc, + intrinsic: Rc, root: BSubtree, parent: Element, fragment: &mut Fragment, internal_ref: NodeRef, ) { - let collectable = mountable.create_collectable(); + let collectable = intrinsic.create_collectable(); let mut fragment = Fragment::collect_between(fragment, &collectable, &parent); let prepared_state = match fragment @@ -300,7 +300,7 @@ mod feat_hydration { RenderMode::Hydration, prepared_state.as_deref(), ); - ComponentState::run_create(ctx, self.clone(), mountable, slot); + ComponentState::run_create(ctx, self.clone(), intrinsic, slot); } } } diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 81d5d5a7720..46cb6e9feb1 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -100,16 +100,16 @@ where fields(hydratable = self.hydratable), )] pub fn render_stream(self) -> impl Stream { - let mountable = Rc::new(ComponentIntrinsic::::new(self.props)); + let intrinsic = Rc::new(ComponentIntrinsic::::new(self.props)); - let scope = Scope::new(mountable.as_ref(), None); + let scope = Scope::new(intrinsic.as_ref(), None); let outer_span = tracing::Span::current(); BufStream::new(move |mut w| async move { let render_span = tracing::debug_span!("render_stream_item"); render_span.follows_from(outer_span); scope - .render_into_stream(mountable, &mut w, self.hydratable) + .render_into_stream(intrinsic, &mut w, self.hydratable) .instrument(render_span) .await; }) diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 17988ffe2af..8a5db93c83a 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -10,7 +10,7 @@ use crate::html::{Component, ComponentIntrinsic, Intrinsical}; /// A virtual component. pub struct VComp { pub(crate) type_id: TypeId, - pub(crate) mountable: Rc, + pub(crate) intrinsic: Rc, pub(crate) key: Option, // for some reason, this reduces the bundle size by ~2-3 KBs _marker: u32, @@ -20,7 +20,7 @@ impl fmt::Debug for VComp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VComp") .field("type_id", &self.type_id) - .field("mountable", &"..") + .field("intrinsic", &"..") .field("key", &self.key) .finish() } @@ -30,7 +30,7 @@ impl Clone for VComp { fn clone(&self) -> Self { Self { type_id: self.type_id, - mountable: self.mountable.clone(), + intrinsic: self.intrinsic.clone(), key: self.key.clone(), _marker: 0, } @@ -93,7 +93,7 @@ impl VComp { { VComp { type_id: TypeId::of::(), - mountable: Rc::new(ComponentIntrinsic::::new(props)), + intrinsic: Rc::new(ComponentIntrinsic::::new(props)), key, _marker: 0, } @@ -107,7 +107,7 @@ impl VComp { { VComp { type_id: TypeId::of::(), - mountable: intrinsic.into(), + intrinsic: intrinsic.into(), key, _marker: 0, } @@ -140,7 +140,7 @@ mod feat_ssr { parent_scope: &Scope, hydratable: bool, ) { - self.mountable + self.intrinsic .clone() .render_into_stream(w, parent_scope, hydratable) .await; From ff9f118b7d233257af119464a8255022e8fedecb Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 21:51:40 +0900 Subject: [PATCH 42/47] Remove renderable as well. --- packages/yew-macro/src/function_component.rs | 6 ---- packages/yew/src/functional/mod.rs | 33 ++------------------ packages/yew/src/html/component/intrinsic.rs | 12 +++++-- 3 files changed, 12 insertions(+), 39 deletions(-) diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index fb0ad8208f2..a657bade82c 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -306,12 +306,6 @@ impl FunctionComponent { impl #impl_generics ::yew::html::Component for #component_name #ty_generics #where_clause { type Properties = #props_type; - fn create() -> Self { - Self { - _marker: ::std::marker::PhantomData, - } - } - fn run(#ctx_ident: &mut ::yew::functional::HookContext, #component_props: &Self::Properties) -> ::yew::html::HtmlResult { #func diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 10a2d26a60e..cd27fe7e26d 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -200,7 +200,7 @@ impl HookContext { } #[inline(always)] - fn prepare_run(&mut self) { + pub(crate) fn prepare_run(&mut self) { #[cfg(feature = "hydration")] { self.prepared_state_counter = 0; @@ -213,7 +213,7 @@ impl HookContext { /// /// This function asserts that the number of hooks matches for every render. #[cfg(debug_assertions)] - fn assert_hook_context(&mut self, render_ok: bool) { + pub(crate) fn assert_hook_context(&mut self, render_ok: bool) { // Procedural Macros can catch most conditionally called hooks at compile time, but it // cannot detect early return (as the return can be Err(_), Suspension). match (render_ok, self.total_hook_counter) { @@ -289,38 +289,9 @@ pub trait Component: Sized + 'static { /// Properties for the Function Component. type Properties: Properties + PartialEq; - /// Creates an instance of function provider. - fn create() -> Self; - /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the /// component. /// /// Equivalent of [`Component::view`](crate::html::Component::view). fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult; } - -pub(crate) trait Renderable { - /// Properties for the Function Component. - type TProps: Properties + PartialEq; - - fn render(ctx: &mut HookContext, props: &Self::TProps) -> HtmlResult; -} - -impl Renderable for T -where - T: Component + 'static, -{ - type TProps = T::Properties; - - fn render(ctx: &mut HookContext, props: &Self::TProps) -> HtmlResult { - ctx.prepare_run(); - - #[allow(clippy::let_and_return)] - let result = T::run(ctx, props); - - #[cfg(debug_assertions)] - ctx.assert_hook_context(result.is_ok()); - - result - } -} diff --git a/packages/yew/src/html/component/intrinsic.rs b/packages/yew/src/html/component/intrinsic.rs index 4d2ead0a338..bcf477f35ef 100644 --- a/packages/yew/src/html/component/intrinsic.rs +++ b/packages/yew/src/html/component/intrinsic.rs @@ -14,7 +14,7 @@ use super::Scope; use crate::dom_bundle::BSubtree; #[cfg(feature = "hydration")] use crate::dom_bundle::Fragment; -use crate::functional::{HookContext, Renderable}; +use crate::functional::HookContext; #[cfg(feature = "csr")] use crate::html::NodeRef; #[cfg(feature = "ssr")] @@ -96,7 +96,15 @@ impl Intrinsical for ComponentIntrinsic { } fn render(&self, ctx: &mut HookContext) -> HtmlResult { - COMP::render(ctx, self.props()) + ctx.prepare_run(); + + #[allow(clippy::let_and_return)] + let result = COMP::run(ctx, self.props()); + + #[cfg(debug_assertions)] + ctx.assert_hook_context(result.is_ok()); + + result } #[cfg(any(feature = "hydration", feature = "ssr"))] From 70574abcbfb7e46823d249a25e374d71b661a284 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 21:55:32 +0900 Subject: [PATCH 43/47] Remove reuse as well. --- packages/yew/src/app_handle.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index f8e2ffd8151..8e75883fde3 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -128,8 +128,6 @@ mod feat_hydration { &mut fragment, NodeRef::default(), ); - #[cfg(debug_assertions)] // Fix trapped next_sibling at the root - scope.reuse(intrinsic, NodeRef::default()); // We remove all remaining nodes, this mimics the clear_element behaviour in // mount_with_props. From 8aa970aa8f587d9b8784253ca180f6839a419e8a Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 22:17:15 +0900 Subject: [PATCH 44/47] Remove unused labels. --- packages/yew/src/html/component/lifecycle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 24d60bb055c..c33009c1989 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -250,7 +250,7 @@ impl ComponentState { } // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = '_block: { + let schedule_render = { #[cfg(feature = "hydration")] { if let Some(intrinsic) = intrinsic.or_else(|| self.pending_intrinsic.take()) { From 2938db158e7049f2de7e6e3765bfffb4c2c0b61f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Thu, 10 Nov 2022 22:18:30 +0900 Subject: [PATCH 45/47] Remove tests as they no longer works. --- packages/yew/src/html/component/lifecycle.rs | 264 ------------------- 1 file changed, 264 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index c33009c1989..45f74e58257 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -311,267 +311,3 @@ impl ComponentState { } } } - -#[cfg(target_arch = "wasm32")] -#[cfg(test)] -mod tests { - extern crate self as yew; - - use std::cell::RefCell; - use std::ops::Deref; - use std::rc::Rc; - - use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; - - use super::*; - use crate::dom_bundle::BSubtree; - use crate::html::*; - use crate::{html, Properties}; - - wasm_bindgen_test_configure!(run_in_browser); - - #[derive(Clone, Properties, Default, PartialEq)] - struct ChildProps { - lifecycle: Rc>>, - } - - struct Child {} - - impl Component for Child { - type Message = (); - type Properties = ChildProps; - - fn create(_ctx: &PreRenderContext) -> Self { - Child {} - } - - fn rendered(&mut self, ctx: &PreRenderContext, _first_render: bool) { - ctx.props() - .lifecycle - .borrow_mut() - .push("child rendered".into()); - } - - fn update(&mut self, _ctx: &PreRenderContext, _: Self::Message) -> bool { - false - } - - fn changed( - &mut self, - _ctx: &PreRenderContext, - _old_props: &Self::Properties, - ) -> bool { - false - } - - fn view(&self, _ctx: &PreRenderContext) -> Html { - html! {} - } - } - - #[derive(Clone, Properties, Default, PartialEq)] - struct Props { - lifecycle: Rc>>, - #[allow(dead_code)] - #[cfg(target_arch = "wasm32")] - create_message: Option, - update_message: RefCell>, - view_message: RefCell>, - rendered_message: RefCell>, - } - - struct Comp { - lifecycle: Rc>>, - } - - impl Component for Comp { - type Message = bool; - type Properties = Props; - - fn create(ctx: &PreRenderContext) -> Self { - ctx.props().lifecycle.borrow_mut().push("create".into()); - #[cfg(target_arch = "wasm32")] - if let Some(msg) = ctx.props().create_message { - ctx.link().send_message(msg); - } - Comp { - lifecycle: Rc::clone(&ctx.props().lifecycle), - } - } - - fn rendered(&mut self, ctx: &PreRenderContext, first_render: bool) { - if let Some(msg) = ctx.props().rendered_message.borrow_mut().take() { - ctx.link().send_message(msg); - } - ctx.props() - .lifecycle - .borrow_mut() - .push(format!("rendered({})", first_render)); - } - - fn update(&mut self, ctx: &PreRenderContext, msg: Self::Message) -> bool { - if let Some(msg) = ctx.props().update_message.borrow_mut().take() { - ctx.link().send_message(msg); - } - ctx.props() - .lifecycle - .borrow_mut() - .push(format!("update({})", msg)); - msg - } - - fn changed(&mut self, ctx: &PreRenderContext, _old_props: &Self::Properties) -> bool { - self.lifecycle = Rc::clone(&ctx.props().lifecycle); - self.lifecycle.borrow_mut().push("change".into()); - false - } - - fn view(&self, ctx: &PreRenderContext) -> Html { - if let Some(msg) = ctx.props().view_message.borrow_mut().take() { - ctx.link().send_message(msg); - } - self.lifecycle.borrow_mut().push("view".into()); - html! { } - } - } - - impl Drop for Comp { - fn drop(&mut self) { - self.lifecycle.borrow_mut().push("drop".into()); - } - } - - fn test_lifecycle(props: Props, expected: &[&str]) { - let document = gloo::utils::document(); - let scope = Scope::::new(None); - let parent = document.create_element("div").unwrap(); - let root = BSubtree::create_root(&parent); - - let lifecycle = props.lifecycle.clone(); - - lifecycle.borrow_mut().clear(); - scope.mount_in_place( - root, - parent, - NodeRef::default(), - NodeRef::default(), - Rc::new(props), - ); - - assert_eq!(&lifecycle.borrow_mut().deref()[..], expected); - } - - #[test] - fn lifecycle_tests() { - let lifecycle: Rc>> = Rc::default(); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - ..Props::default() - }, - &["create", "view", "child rendered", "rendered(true)"], - ); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - #[cfg(target_arch = "wasm32")] - create_message: Some(false), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(false)", - ], - ); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - view_message: RefCell::new(Some(true)), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(true)", - "view", - "rendered(false)", - ], - ); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - view_message: RefCell::new(Some(false)), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(false)", - ], - ); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - rendered_message: RefCell::new(Some(false)), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(false)", - ], - ); - - test_lifecycle( - Props { - lifecycle: lifecycle.clone(), - rendered_message: RefCell::new(Some(true)), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(true)", - "view", - "rendered(false)", - ], - ); - - // This also tests render deduplication after the first render - test_lifecycle( - Props { - lifecycle, - #[cfg(target_arch = "wasm32")] - create_message: Some(true), - update_message: RefCell::new(Some(true)), - ..Props::default() - }, - &[ - "create", - "view", - "child rendered", - "rendered(true)", - "update(true)", - "update(true)", - "view", - "rendered(false)", - ], - ); - } -} From e9b60b363275c44a9e651d0642f3d2d8c5f01361 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 11 Nov 2022 13:20:50 +0900 Subject: [PATCH 46/47] Break reference cycle. --- packages/yew/src/app_handle.rs | 21 +++++- packages/yew/src/html/component/lifecycle.rs | 78 ++++++++++---------- packages/yew/src/html/component/scope.rs | 4 +- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 8e75883fde3..a9f83b5a6ca 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -16,6 +16,7 @@ pub struct AppHandle { /// `Scope` holder pub(crate) scope: Scope, _marker: PhantomData, + destroyed: bool, } impl AppHandle @@ -38,6 +39,7 @@ where let app = Self { scope: Scope::new(intrinsic.as_ref(), None), _marker: PhantomData, + destroyed: false, }; let hosting_root = BSubtree::create_root(&host); @@ -78,8 +80,9 @@ where level = tracing::Level::DEBUG, skip_all, )] - pub fn destroy(self) { - let scope = self.scope; + pub fn destroy(mut self) { + self.destroyed = true; + let scope = self.scope.clone(); scheduler::push(move || { scope.destroy(false); }); @@ -113,6 +116,7 @@ mod feat_hydration { let app = Self { scope: Scope::new(intrinsic.as_ref(), None), _marker: PhantomData, + destroyed: false, }; let mut fragment = Fragment::collect_children(&host); @@ -140,3 +144,16 @@ mod feat_hydration { } } } + +impl Drop for AppHandle +where + COMP: Component, +{ + fn drop(&mut self) { + if !self.destroyed { + // We leak scope if hasn't been destroyed. + // So the application stays running for as long as the page is open. + Box::leak(Box::new(self.scope.clone())); + } + } +} diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 45f74e58257..889e2bbde87 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -12,15 +12,11 @@ use crate::{scheduler, Callback, ContextProvider, HookContext}; pub(crate) struct ComponentState { pub(super) ctx: HookContext, - intrinsic: Rc, - scope: Scope, - pub slot: DomSlot, #[cfg(feature = "hydration")] pending_intrinsic: Option>, - suspension: Option, } @@ -29,13 +25,17 @@ impl ComponentState { level = tracing::Level::DEBUG, name = "create", skip_all, - fields(component.id = scope.id()), + fields(component.id = _scope.id()), )] - fn new(ctx: HookContext, scope: Scope, intrinsic: Rc, slot: DomSlot) -> Self { + fn new( + _scope: &Scope, + ctx: HookContext, + intrinsic: Rc, + slot: DomSlot, + ) -> Self { Self { ctx, intrinsic, - scope, suspension: None, @@ -47,17 +47,16 @@ impl ComponentState { } pub fn run_create( + scope: &Scope, ctx: HookContext, - scope: Scope, intrinsic: Rc, slot: DomSlot, ) { - let scope_ = scope.clone(); - let mut current_state = scope_.state_cell().borrow_mut(); + let mut current_state = scope.state_cell().borrow_mut(); if current_state.is_none() { - let mut self_ = Self::new(ctx, scope, intrinsic, slot); - self_.render(); + let mut self_ = Self::new(scope, ctx, intrinsic, slot); + self_.render(scope); // We are safe to assign afterwards as we mutably borrow the state and don't release it // until this function returns. @@ -67,7 +66,7 @@ impl ComponentState { pub fn run_render(scope: &Scope) { if let Some(state) = scope.state_cell().borrow_mut().as_mut() { - state.render(); + state.render(scope); } } @@ -83,20 +82,19 @@ impl ComponentState { next_sibling: Option, ) { if let Some(state) = scope.state_cell().borrow_mut().as_mut() { - state.changed(intrinsic, next_sibling); + state.changed(scope, intrinsic, next_sibling); } } pub fn run_destroy(scope: &Scope, parent_to_detach: bool) { if let Some(state) = scope.state_cell().borrow_mut().take() { - state.destroy(parent_to_detach); + state.destroy(scope, parent_to_detach); } } - fn resume_existing_suspension(&mut self) { + fn resume_existing_suspension(&mut self, scope: &Scope) { if let Some(m) = self.suspension.take() { - let suspense_scope = self - .scope + let suspense_scope = scope .find_parent_scope::>() .unwrap(); resume_suspension(suspense_scope, m); @@ -121,11 +119,11 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.scope.id()) + fields(component.id = scope.id()) )] - fn destroy(mut self, parent_to_detach: bool) { + fn destroy(mut self, scope: &Scope, parent_to_detach: bool) { self.ctx.destroy(); - self.resume_existing_suspension(); + self.resume_existing_suspension(scope); match self.slot.content { Realized::Bundle(bundle) => { @@ -144,30 +142,29 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip_all, - fields(component.id = self.scope.id()) + fields(component.id = scope.id()) )] - fn render(&mut self) { + fn render(&mut self, scope: &Scope) { match self.intrinsic.render(&mut self.ctx) { - Ok(vnode) => self.commit_render(vnode), - Err(RenderError::Suspended(susp)) => self.suspend(susp), + Ok(vnode) => self.commit_render(scope, vnode), + Err(RenderError::Suspended(susp)) => self.suspend(scope, susp), }; } - fn suspend(&mut self, suspension: Suspension) { + fn suspend(&mut self, scope: &Scope, suspension: Suspension) { // Currently suspended, we re-use previous root node and send // suspension to parent element. if suspension.resumed() { - self.render(); + self.render(scope); } else { // We schedule a render after current suspension is resumed. - let suspense_scope = self - .scope + let suspense_scope = scope .find_parent_scope::>() .expect("To suspend rendering, a component is required."); { - let scope = self.scope.clone(); + let scope = scope.clone(); suspension.listen(Callback::from(move |_| { let scope = scope.clone(); scheduler::push(move || ComponentState::run_render(&scope)); @@ -186,25 +183,25 @@ impl ComponentState { } } - fn commit_render(&mut self, new_root: Html) { + fn commit_render(&mut self, scope: &Scope, new_root: Html) { // Currently not suspended, we remove any previous suspension and update // normally. - self.resume_existing_suspension(); + self.resume_existing_suspension(scope); match self.slot.content { Realized::Bundle(ref mut bundle) => { let new_node_ref = bundle.reconcile( &self.slot.root, - &self.scope, + scope, &self.slot.parent, self.slot.next_sibling.clone(), new_root, ); self.slot.internal_ref.link(new_node_ref); - let has_pending_props = self.rendered(); + let has_pending_props = self.rendered(scope); if has_pending_props { - self.changed(None, None); + self.changed(scope, None, None); } } @@ -214,7 +211,7 @@ impl ComponentState { let (node, bundle) = Bundle::hydrate( &self.slot.root, - &self.scope, + scope, &self.slot.parent, fragment, new_root, @@ -235,10 +232,11 @@ impl ComponentState { #[tracing::instrument( level = tracing::Level::DEBUG, skip(self, intrinsic), - fields(component.id = self.scope.id()) + fields(component.id = scope.id()) )] pub(super) fn changed( &mut self, + scope: &Scope, intrinsic: Option>, next_sibling: Option, ) { @@ -287,16 +285,16 @@ impl ComponentState { tracing::trace!("props_update(schedule_render={})", schedule_render); if schedule_render { - self.render() + self.render(scope) } } #[tracing::instrument( level = tracing::Level::DEBUG, skip(self), - fields(component.id = self.scope.id()) + fields(component.id = _scope.id()) )] - pub(super) fn rendered(&mut self) -> bool { + pub(super) fn rendered(&mut self, _scope: &Scope) -> bool { if self.suspension.is_none() { self.ctx.rendered(); } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index e1e68628111..e32d1ea2767 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -230,7 +230,7 @@ mod feat_csr { None, ); - ComponentState::run_create(ctx, self.clone(), intrinsic, slot); + ComponentState::run_create(self, ctx, intrinsic, slot); } /// Process an event to destroy a component @@ -300,7 +300,7 @@ mod feat_hydration { RenderMode::Hydration, prepared_state.as_deref(), ); - ComponentState::run_create(ctx, self.clone(), intrinsic, slot); + ComponentState::run_create(self, ctx, intrinsic, slot); } } } From c31c47b0fd8d4f60a31d5b8d8bde22eb70c98971 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 11 Nov 2022 13:48:24 +0900 Subject: [PATCH 47/47] Prevent unnecessary rendering. --- packages/yew/src/app_handle.rs | 24 +++++++++----------- packages/yew/src/dom_bundle/mod.rs | 1 - packages/yew/src/html/component/lifecycle.rs | 14 ++++++++---- packages/yew/src/html/component/scope.rs | 11 ++++++++- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index a9f83b5a6ca..36ff7cf01b8 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -14,7 +14,7 @@ use crate::scheduler; #[derive(Debug)] pub struct AppHandle { /// `Scope` holder - pub(crate) scope: Scope, + scope: Scope, _marker: PhantomData, destroyed: bool, } @@ -43,18 +43,16 @@ where }; let hosting_root = BSubtree::create_root(&host); - { - let scope = app.scope.clone(); - scheduler::push(move || { - scope.mount( - intrinsic, - hosting_root, - host, - NodeRef::default(), - NodeRef::default(), - ); - }); - } + let scope = app.scope.clone(); + scheduler::push(move || { + scope.mount( + intrinsic, + hosting_root, + host, + NodeRef::default(), + NodeRef::default(), + ); + }); app } diff --git a/packages/yew/src/dom_bundle/mod.rs b/packages/yew/src/dom_bundle/mod.rs index 3ef35988ff1..6be3c23408c 100644 --- a/packages/yew/src/dom_bundle/mod.rs +++ b/packages/yew/src/dom_bundle/mod.rs @@ -48,7 +48,6 @@ pub(crate) struct Bundle(BNode); impl Bundle { /// Creates a new bundle. - pub const fn new() -> Self { Self(BNode::List(BList::new())) } diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 889e2bbde87..dada3df9ce4 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -8,7 +8,7 @@ use super::scope::Scope; use crate::dom_bundle::{DomSlot, Realized}; use crate::html::{Html, Intrinsical, NodeRef, RenderError}; use crate::suspense::{resume_suspension, suspend_suspension, DispatchSuspension, Suspension}; -use crate::{scheduler, Callback, ContextProvider, HookContext}; +use crate::{Callback, ContextProvider, HookContext}; pub(crate) struct ComponentState { pub(super) ctx: HookContext, @@ -64,7 +64,14 @@ impl ComponentState { } } - pub fn run_render(scope: &Scope) { + pub fn run_render(scope: &Scope, step: usize) { + let current_step = scope.render_step_cell().get(); + // The desired change has been applied. + if current_step > step { + return; + } + + scope.render_step_cell().set(current_step + 1); if let Some(state) = scope.state_cell().borrow_mut().as_mut() { state.render(scope); } @@ -166,8 +173,7 @@ impl ComponentState { { let scope = scope.clone(); suspension.listen(Callback::from(move |_| { - let scope = scope.clone(); - scheduler::push(move || ComponentState::run_render(&scope)); + scope.schedule_render(); })); } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index e32d1ea2767..fe3d6fd7a22 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -1,6 +1,7 @@ //! Component scope module use std::any::TypeId; +use std::cell::Cell; #[cfg(feature = "csr")] use std::cell::RefCell; use std::rc::Rc; @@ -21,6 +22,7 @@ struct ScopeInner { #[cfg(feature = "csr")] pub(crate) state: RefCell>, + render_step: Cell, parent: Option, } @@ -41,6 +43,10 @@ impl Scope { self.inner.id } + pub(crate) fn render_step_cell(&self) -> &Cell { + &self.inner.render_step + } + /// Schedules a render. pub(crate) fn schedule_render(&self) { #[cfg(feature = "csr")] @@ -48,7 +54,8 @@ impl Scope { use crate::scheduler; let scope = self.clone(); - scheduler::push(move || ComponentState::run_render(&scope)); + let step = self.inner.render_step.get(); + scheduler::push(move || ComponentState::run_render(&scope, step)); } } @@ -166,6 +173,7 @@ mod feat_csr_ssr { state: RefCell::new(None), parent, + render_step: Cell::new(0), id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst), }), } @@ -191,6 +199,7 @@ mod feat_csr { type_id: TypeId::of::<()>(), state: RefCell::default(), parent: None, + render_step: Cell::new(0), }), } }