Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/virtual_dom/
vcomp.rs

1//! This module contains the implementation of a virtual component (`VComp`).
2
3use std::any::{Any, TypeId};
4use std::fmt;
5use std::rc::Rc;
6
7#[cfg(feature = "ssr")]
8use futures::future::{FutureExt, LocalBoxFuture};
9#[cfg(feature = "csr")]
10use web_sys::Element;
11
12use super::Key;
13#[cfg(feature = "hydration")]
14use crate::dom_bundle::Fragment;
15#[cfg(feature = "csr")]
16use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
17#[cfg(any(feature = "ssr", feature = "csr"))]
18use crate::html::AnyScope;
19#[cfg(feature = "csr")]
20use crate::html::Scoped;
21use crate::html::{BaseComponent, Scope};
22use crate::scheduler::Shared;
23#[cfg(feature = "ssr")]
24use crate::{feat_ssr::VTagKind, platform::fmt::BufWriter};
25
26/// A virtual component.
27pub struct VComp {
28    pub(crate) type_id: TypeId,
29    pub(crate) mountable: Box<dyn Mountable>,
30    pub(crate) key: Option<Key>,
31    // for some reason, this reduces the bundle size by ~2-3 KBs
32    _marker: u32,
33}
34
35impl fmt::Debug for VComp {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("VComp")
38            .field("type_id", &self.type_id)
39            .field("mountable", &"..")
40            .field("key", &self.key)
41            .finish()
42    }
43}
44
45impl Clone for VComp {
46    fn clone(&self) -> Self {
47        Self {
48            type_id: self.type_id,
49            mountable: self.mountable.copy(),
50            key: self.key.clone(),
51            _marker: 0,
52        }
53    }
54}
55
56pub(crate) trait Mountable {
57    fn copy(&self) -> Box<dyn Mountable>;
58
59    fn mountable_eq(&self, rhs: &dyn Mountable) -> bool;
60    fn as_any(&self) -> &dyn Any;
61
62    #[cfg(feature = "csr")]
63    fn mount(
64        self: Box<Self>,
65        root: &BSubtree,
66        parent_scope: &AnyScope,
67        parent: Element,
68        slot: DomSlot,
69    ) -> (Box<dyn Scoped>, DynamicDomSlot);
70
71    #[cfg(feature = "csr")]
72    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
73
74    #[cfg(feature = "ssr")]
75    fn render_into_stream<'a>(
76        &'a self,
77        w: &'a mut BufWriter,
78        parent_scope: &'a AnyScope,
79        hydratable: bool,
80        parent_vtag_kind: VTagKind,
81    ) -> LocalBoxFuture<'a, ()>;
82
83    #[cfg(feature = "hydration")]
84    fn hydrate(
85        self: Box<Self>,
86        root: BSubtree,
87        parent_scope: &AnyScope,
88        parent: Element,
89        fragment: &mut Fragment,
90        prev_next_sibling: &mut Option<DynamicDomSlot>,
91    ) -> (Box<dyn Scoped>, DynamicDomSlot);
92}
93
94pub(crate) struct PropsWrapper<COMP: BaseComponent> {
95    props: Rc<COMP::Properties>,
96    scope_ref: Option<Shared<Option<Scope<COMP>>>>,
97}
98
99impl<COMP: BaseComponent> PropsWrapper<COMP> {
100    pub fn new(props: Rc<COMP::Properties>) -> Self {
101        Self {
102            props,
103            scope_ref: None,
104        }
105    }
106
107    pub fn new_with_ref(
108        props: Rc<COMP::Properties>,
109        scope_ref: Shared<Option<Scope<COMP>>>,
110    ) -> Self {
111        Self {
112            props,
113            scope_ref: Some(scope_ref),
114        }
115    }
116}
117
118impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
119    fn copy(&self) -> Box<dyn Mountable> {
120        let wrapper: PropsWrapper<COMP> = PropsWrapper {
121            props: Rc::clone(&self.props),
122            scope_ref: self.scope_ref.clone(),
123        };
124        Box::new(wrapper)
125    }
126
127    fn as_any(&self) -> &dyn Any {
128        self
129    }
130
131    fn mountable_eq(&self, rhs: &dyn Mountable) -> bool {
132        rhs.as_any()
133            .downcast_ref::<Self>()
134            .map(|rhs| self.props == rhs.props)
135            .unwrap_or(false)
136    }
137
138    #[cfg(feature = "csr")]
139    fn mount(
140        self: Box<Self>,
141        root: &BSubtree,
142        parent_scope: &AnyScope,
143        parent: Element,
144        slot: DomSlot,
145    ) -> (Box<dyn Scoped>, DynamicDomSlot) {
146        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
147        if let Some(scope_ref) = self.scope_ref {
148            *scope_ref.borrow_mut() = Some(scope.clone());
149        }
150        let own_slot = scope.mount_in_place(root.clone(), parent, slot, self.props);
151
152        (Box::new(scope), own_slot)
153    }
154
155    #[cfg(feature = "csr")]
156    fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
157        let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
158        scope.reuse(self.props, slot);
159    }
160
161    #[cfg(feature = "ssr")]
162    fn render_into_stream<'a>(
163        &'a self,
164        w: &'a mut BufWriter,
165        parent_scope: &'a AnyScope,
166        hydratable: bool,
167        parent_vtag_kind: VTagKind,
168    ) -> LocalBoxFuture<'a, ()> {
169        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
170
171        async move {
172            scope
173                .render_into_stream(w, self.props.clone(), hydratable, parent_vtag_kind)
174                .await;
175        }
176        .boxed_local()
177    }
178
179    #[cfg(feature = "hydration")]
180    fn hydrate(
181        self: Box<Self>,
182        root: BSubtree,
183        parent_scope: &AnyScope,
184        parent: Element,
185        fragment: &mut Fragment,
186        prev_next_sibling: &mut Option<DynamicDomSlot>,
187    ) -> (Box<dyn Scoped>, DynamicDomSlot) {
188        let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
189        let own_slot =
190            scope.hydrate_in_place(root, parent, fragment, self.props, prev_next_sibling);
191
192        (Box::new(scope), own_slot)
193    }
194}
195
196/// A virtual child component.
197pub struct VChild<COMP: BaseComponent> {
198    /// The component properties
199    pub props: Rc<COMP::Properties>,
200    /// Reference to the mounted node
201    key: Option<Key>,
202}
203
204impl<COMP: BaseComponent> implicit_clone::ImplicitClone for VChild<COMP> {}
205
206impl<COMP: BaseComponent> Clone for VChild<COMP> {
207    fn clone(&self) -> Self {
208        VChild {
209            props: Rc::clone(&self.props),
210            key: self.key.clone(),
211        }
212    }
213}
214
215impl<COMP: BaseComponent> PartialEq for VChild<COMP>
216where
217    COMP::Properties: PartialEq,
218{
219    fn eq(&self, other: &VChild<COMP>) -> bool {
220        self.props == other.props
221    }
222}
223
224impl<COMP> VChild<COMP>
225where
226    COMP: BaseComponent,
227{
228    /// Creates a child component that can be accessed and modified by its parent.
229    pub fn new(props: COMP::Properties, key: Option<Key>) -> Self {
230        Self {
231            props: Rc::new(props),
232            key,
233        }
234    }
235}
236
237impl<COMP> VChild<COMP>
238where
239    COMP: BaseComponent,
240    COMP::Properties: Clone,
241{
242    /// Get a mutable reference to the underlying properties.
243    pub fn get_mut(&mut self) -> &mut COMP::Properties {
244        Rc::make_mut(&mut self.props)
245    }
246}
247
248impl<COMP> From<VChild<COMP>> for VComp
249where
250    COMP: BaseComponent,
251{
252    fn from(vchild: VChild<COMP>) -> Self {
253        VComp::new::<COMP>(vchild.props, vchild.key)
254    }
255}
256
257impl VComp {
258    /// Creates a new `VComp` instance.
259    pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
260    where
261        COMP: BaseComponent,
262    {
263        VComp {
264            type_id: TypeId::of::<COMP>(),
265            mountable: Box::new(PropsWrapper::<COMP>::new(props)),
266            key,
267            _marker: 0,
268        }
269    }
270
271    /// Attach a ref into which the scope of the child will be written when it is mounted
272    pub(crate) fn new_with_ref<COMP: BaseComponent>(
273        props: Rc<COMP::Properties>,
274        scope_ref: Shared<Option<Scope<COMP>>>,
275    ) -> Self {
276        VComp {
277            type_id: TypeId::of::<COMP>(),
278            mountable: Box::new(PropsWrapper::<COMP>::new_with_ref(props, scope_ref)),
279            key: None,
280            _marker: 0,
281        }
282    }
283}
284
285impl PartialEq for VComp {
286    fn eq(&self, other: &VComp) -> bool {
287        self.key == other.key
288            && self.type_id == other.type_id
289            && self.mountable.mountable_eq(other.mountable.as_ref())
290    }
291}
292
293impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        f.write_str("VChild<_>")
296    }
297}
298
299#[cfg(feature = "ssr")]
300mod feat_ssr {
301    use super::*;
302    use crate::html::AnyScope;
303
304    impl VComp {
305        #[inline]
306        pub(crate) async fn render_into_stream(
307            &self,
308            w: &mut BufWriter,
309            parent_scope: &AnyScope,
310            hydratable: bool,
311            parent_vtag_kind: VTagKind,
312        ) {
313            self.mountable
314                .as_ref()
315                .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
316                .await;
317        }
318    }
319}
320
321#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
322mod ssr_tests {
323    use tokio::test;
324
325    use crate::prelude::*;
326    use crate::ServerRenderer;
327
328    #[test]
329    async fn test_props() {
330        #[derive(PartialEq, Properties, Debug)]
331        struct ChildProps {
332            name: String,
333        }
334
335        #[component]
336        fn Child(props: &ChildProps) -> Html {
337            html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
338        }
339
340        #[component]
341        fn Comp() -> Html {
342            html! {
343                <div>
344                    <Child name="Jane" />
345                    <Child name="John" />
346                    <Child name="Josh" />
347                </div>
348            }
349        }
350
351        let s = ServerRenderer::<Comp>::new()
352            .hydratable(false)
353            .render()
354            .await;
355
356        assert_eq!(
357            s,
358            "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
359        );
360    }
361}