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/html/component/
scope.rs

1//! Component scope module
2
3use std::any::{Any, TypeId};
4use std::future::Future;
5use std::marker::PhantomData;
6use std::ops::Deref;
7use std::rc::Rc;
8use std::{fmt, iter};
9
10use futures::{Stream, StreamExt};
11
12#[cfg(any(feature = "csr", feature = "ssr"))]
13use super::lifecycle::ComponentState;
14use super::BaseComponent;
15use crate::callback::Callback;
16use crate::context::{ContextHandle, ContextProvider};
17use crate::platform::spawn_local;
18#[cfg(any(feature = "csr", feature = "ssr"))]
19use crate::scheduler::Shared;
20
21/// Untyped scope used for accessing parent scope
22#[derive(Clone)]
23pub struct AnyScope {
24    type_id: TypeId,
25    parent: Option<Rc<AnyScope>>,
26    typed_scope: Rc<dyn Any>,
27}
28
29impl fmt::Debug for AnyScope {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str("AnyScope<_>")
32    }
33}
34
35impl<COMP: BaseComponent> From<Scope<COMP>> for AnyScope {
36    fn from(scope: Scope<COMP>) -> Self {
37        AnyScope {
38            type_id: TypeId::of::<COMP>(),
39            parent: scope.parent.clone(),
40            typed_scope: Rc::new(scope),
41        }
42    }
43}
44
45impl AnyScope {
46    /// Returns the parent scope
47    pub fn get_parent(&self) -> Option<&AnyScope> {
48        self.parent.as_deref()
49    }
50
51    /// Returns the type of the linked component
52    pub fn get_type_id(&self) -> &TypeId {
53        &self.type_id
54    }
55
56    /// Attempts to downcast into a typed scope
57    ///
58    /// # Panics
59    ///
60    /// If the self value can't be cast into the target type.
61    pub fn downcast<COMP: BaseComponent>(&self) -> Scope<COMP> {
62        self.try_downcast::<COMP>().unwrap()
63    }
64
65    /// Attempts to downcast into a typed scope
66    ///
67    /// Returns [`None`] if the self value can't be cast into the target type.
68    pub fn try_downcast<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
69        self.typed_scope.downcast_ref::<Scope<COMP>>().cloned()
70    }
71
72    /// Attempts to find a parent scope of a certain type
73    ///
74    /// Returns [`None`] if no parent scope with the specified type was found.
75    pub fn find_parent_scope<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
76        iter::successors(Some(self), |scope| scope.get_parent())
77            .find_map(AnyScope::try_downcast::<COMP>)
78    }
79
80    /// Accesses a value provided by a parent `ContextProvider` component of the
81    /// same type.
82    pub fn context<T: Clone + PartialEq + 'static>(
83        &self,
84        callback: Callback<T>,
85    ) -> Option<(T, ContextHandle<T>)> {
86        let scope = self.find_parent_scope::<ContextProvider<T>>()?;
87        let scope_clone = scope.clone();
88        let component = scope.get_component()?;
89        Some(component.subscribe_consumer(callback, scope_clone))
90    }
91}
92
93/// A context which allows sending messages to a component.
94pub struct Scope<COMP: BaseComponent> {
95    _marker: PhantomData<COMP>,
96    parent: Option<Rc<AnyScope>>,
97
98    #[cfg(any(feature = "csr", feature = "ssr"))]
99    pub(crate) pending_messages: MsgQueue<COMP::Message>,
100
101    #[cfg(any(feature = "csr", feature = "ssr"))]
102    pub(crate) state: Shared<Option<ComponentState>>,
103
104    pub(crate) id: usize,
105}
106
107impl<COMP: BaseComponent> fmt::Debug for Scope<COMP> {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.write_str("Scope<_>")
110    }
111}
112
113impl<COMP: BaseComponent> Clone for Scope<COMP> {
114    fn clone(&self) -> Self {
115        Scope {
116            _marker: PhantomData,
117
118            #[cfg(any(feature = "csr", feature = "ssr"))]
119            pending_messages: self.pending_messages.clone(),
120            parent: self.parent.clone(),
121
122            #[cfg(any(feature = "csr", feature = "ssr"))]
123            state: self.state.clone(),
124
125            id: self.id,
126        }
127    }
128}
129
130impl<COMP: BaseComponent> Scope<COMP> {
131    /// Returns the parent scope
132    pub fn get_parent(&self) -> Option<&AnyScope> {
133        self.parent.as_deref()
134    }
135
136    /// Creates a `Callback` which will send a message to the linked
137    /// component's update method when invoked.
138    ///
139    /// If your callback function returns a [Future],
140    /// use [`callback_future`](Scope::callback_future) instead.
141    pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
142    where
143        M: Into<COMP::Message>,
144        F: Fn(IN) -> M + 'static,
145    {
146        let scope = self.clone();
147        let closure = move |input| {
148            let output = function(input);
149            scope.send_message(output);
150        };
151        Callback::from(closure)
152    }
153
154    /// Creates a `Callback` which will send a batch of messages back
155    /// to the linked component's update method when invoked.
156    ///
157    /// The callback function's return type is generic to allow for dealing with both
158    /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that
159    /// might not need to send an update.
160    ///
161    /// ```ignore
162    /// link.batch_callback(|_| vec![Msg::A, Msg::B]);
163    /// link.batch_callback(|_| Some(Msg::A));
164    /// ```
165    pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
166    where
167        F: Fn(IN) -> OUT + 'static,
168        OUT: SendAsMessage<COMP>,
169    {
170        let scope = self.clone();
171        let closure = move |input| {
172            let messages = function(input);
173            messages.send(&scope);
174        };
175        closure.into()
176    }
177
178    /// Accesses a value provided by a parent `ContextProvider` component of the
179    /// same type.
180    pub fn context<T: Clone + PartialEq + 'static>(
181        &self,
182        callback: Callback<T>,
183    ) -> Option<(T, ContextHandle<T>)> {
184        AnyScope::from(self.clone()).context(callback)
185    }
186
187    /// This method asynchronously awaits a [Future] that returns a message and sends it
188    /// to the linked component.
189    ///
190    /// # Panics
191    /// If the future panics, then the promise will not resolve, and will leak.
192    pub fn send_future<Fut, Msg>(&self, future: Fut)
193    where
194        Msg: Into<COMP::Message>,
195        Fut: Future<Output = Msg> + 'static,
196    {
197        let link = self.clone();
198        spawn_local(async move {
199            let message: COMP::Message = future.await.into();
200            link.send_message(message);
201        });
202    }
203
204    /// This method creates a [`Callback`] which, when emitted, asynchronously awaits the
205    /// message returned from the passed function before sending it to the linked component.
206    ///
207    /// # Panics
208    /// If the future panics, then the promise will not resolve, and will leak.
209    pub fn callback_future<F, Fut, IN, Msg>(&self, function: F) -> Callback<IN>
210    where
211        Msg: Into<COMP::Message>,
212        Fut: Future<Output = Msg> + 'static,
213        F: Fn(IN) -> Fut + 'static,
214    {
215        let link = self.clone();
216
217        let closure = move |input: IN| {
218            link.send_future(function(input));
219        };
220
221        closure.into()
222    }
223
224    /// Asynchronously send a batch of messages to a component. This asynchronously awaits the
225    /// passed [Future], before sending the message batch to the linked component.
226    ///
227    /// # Panics
228    /// If the future panics, then the promise will not resolve, and will leak.
229    pub fn send_future_batch<Fut>(&self, future: Fut)
230    where
231        Fut: Future + 'static,
232        Fut::Output: SendAsMessage<COMP>,
233    {
234        let link = self.clone();
235        let js_future = async move {
236            future.await.send(&link);
237        };
238        spawn_local(js_future);
239    }
240
241    /// This method asynchronously awaits a [`Stream`] that returns a series of messages and sends
242    /// them to the linked component.
243    ///
244    /// # Panics
245    /// If the stream panics, then the promise will not resolve, and will leak.
246    ///
247    /// # Note
248    ///
249    /// This method will not notify the component when the stream has been fully exhausted. If
250    /// you want this feature, you can add an EOF message variant for your component and use
251    /// [`StreamExt::chain`] and [`stream::once`](futures::stream::once) to chain an EOF message to
252    /// the original stream. If your stream is produced by another crate, you can use
253    /// [`StreamExt::map`] to transform the stream's item type to the component message type.
254    pub fn send_stream<S, M>(&self, stream: S)
255    where
256        M: Into<COMP::Message>,
257        S: Stream<Item = M> + 'static,
258    {
259        let link = self.clone();
260        let js_future = async move {
261            futures::pin_mut!(stream);
262            while let Some(msg) = stream.next().await {
263                let message: COMP::Message = msg.into();
264                link.send_message(message);
265            }
266        };
267        spawn_local(js_future);
268    }
269
270    /// Returns the linked component if available
271    pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
272        self.arch_get_component()
273    }
274
275    /// Send a message to the component.
276    pub fn send_message<T>(&self, msg: T)
277    where
278        T: Into<COMP::Message>,
279    {
280        self.arch_send_message(msg)
281    }
282
283    /// Send a batch of messages to the component.
284    ///
285    /// This is slightly more efficient than calling [`send_message`](Self::send_message)
286    /// in a loop.
287    pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
288        self.arch_send_message_batch(messages)
289    }
290}
291
292#[cfg(feature = "ssr")]
293mod feat_ssr {
294    use std::fmt::Write;
295
296    use super::*;
297    use crate::feat_ssr::VTagKind;
298    use crate::html::component::lifecycle::{
299        ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
300    };
301    use crate::platform::fmt::BufWriter;
302    use crate::platform::pinned::oneshot;
303    use crate::scheduler;
304    use crate::virtual_dom::Collectable;
305
306    impl<COMP: BaseComponent> Scope<COMP> {
307        pub(crate) async fn render_into_stream(
308            &self,
309            w: &mut BufWriter,
310            props: Rc<COMP::Properties>,
311            hydratable: bool,
312            parent_vtag_kind: VTagKind,
313        ) {
314            // Rust's Future implementation is stack-allocated and incurs zero runtime-cost.
315            //
316            // If the content of this channel is ready before it is awaited, it is
317            // similar to taking the value from a mutex lock.
318            let (tx, rx) = oneshot::channel();
319            let state = ComponentRenderState::Ssr { sender: Some(tx) };
320
321            scheduler::push_component_create(
322                self.id,
323                Box::new(CreateRunner {
324                    initial_render_state: state,
325                    props,
326                    scope: self.clone(),
327                    #[cfg(feature = "hydration")]
328                    prepared_state: None,
329                }),
330                Box::new(RenderRunner {
331                    state: self.state.clone(),
332                }),
333            );
334            scheduler::start();
335
336            let collectable = Collectable::for_component::<COMP>();
337
338            if hydratable {
339                collectable.write_open_tag(w);
340            }
341
342            let html = rx.await.unwrap();
343
344            let self_any_scope = AnyScope::from(self.clone());
345            html.render_into_stream(w, &self_any_scope, hydratable, parent_vtag_kind)
346                .await;
347
348            if let Some(prepared_state) = self.get_component().unwrap().prepare_state() {
349                let _ = w.write_str(r#"<script type="application/x-yew-comp-state">"#);
350                let _ = w.write_str(&prepared_state);
351                let _ = w.write_str(r#"</script>"#);
352            }
353
354            if hydratable {
355                collectable.write_close_tag(w);
356            }
357
358            scheduler::push_component_destroy(Box::new(DestroyRunner {
359                state: self.state.clone(),
360                parent_to_detach: false,
361            }));
362            scheduler::start();
363        }
364    }
365}
366
367#[cfg(not(any(feature = "ssr", feature = "csr")))]
368mod feat_no_csr_ssr {
369    use super::*;
370
371    // Skeleton code to provide public methods when no renderer are enabled.
372    impl<COMP: BaseComponent> Scope<COMP> {
373        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
374            Option::<&COMP>::None
375        }
376
377        pub(super) fn arch_send_message<T>(&self, _msg: T)
378        where
379            T: Into<COMP::Message>,
380        {
381        }
382
383        pub(super) fn arch_send_message_batch(&self, _messages: Vec<COMP::Message>) {}
384    }
385}
386
387#[cfg(any(feature = "ssr", feature = "csr"))]
388mod feat_csr_ssr {
389    use std::cell::{Ref, RefCell};
390    use std::sync::atomic::{AtomicUsize, Ordering};
391
392    use super::*;
393    use crate::html::component::lifecycle::{RenderRunner, UpdateRunner};
394    use crate::scheduler::{self, Shared};
395
396    #[derive(Debug)]
397    pub(crate) struct MsgQueue<Msg>(Shared<Vec<Msg>>);
398
399    impl<Msg> MsgQueue<Msg> {
400        pub fn new() -> Self {
401            MsgQueue(Rc::default())
402        }
403
404        pub fn push(&self, msg: Msg) -> usize {
405            let mut inner = self.0.borrow_mut();
406            inner.push(msg);
407
408            inner.len()
409        }
410
411        pub fn append(&self, other: &mut Vec<Msg>) -> usize {
412            let mut inner = self.0.borrow_mut();
413            inner.append(other);
414
415            inner.len()
416        }
417
418        pub fn drain(&self) -> Vec<Msg> {
419            let mut other_queue = Vec::new();
420            let mut inner = self.0.borrow_mut();
421
422            std::mem::swap(&mut *inner, &mut other_queue);
423
424            other_queue
425        }
426    }
427
428    impl<Msg> Clone for MsgQueue<Msg> {
429        fn clone(&self) -> Self {
430            MsgQueue(self.0.clone())
431        }
432    }
433
434    static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
435
436    impl<COMP: BaseComponent> Scope<COMP> {
437        /// Crate a scope with an optional parent scope
438        pub(crate) fn new(parent: Option<AnyScope>) -> Self {
439            let parent = parent.map(Rc::new);
440
441            let state = Rc::new(RefCell::new(None));
442
443            let pending_messages = MsgQueue::new();
444
445            Scope {
446                _marker: PhantomData,
447
448                pending_messages,
449
450                state,
451                parent,
452
453                id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst),
454            }
455        }
456
457        #[inline]
458        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
459            self.state.try_borrow().ok().and_then(|state_ref| {
460                Ref::filter_map(state_ref, |state| {
461                    state.as_ref().and_then(|m| m.downcast_comp_ref::<COMP>())
462                })
463                .ok()
464            })
465        }
466
467        #[inline]
468        fn schedule_update(&self) {
469            scheduler::push_component_update(Box::new(UpdateRunner {
470                state: self.state.clone(),
471            }));
472            // Not guaranteed to already have the scheduler started
473            scheduler::start();
474        }
475
476        #[inline]
477        pub(crate) fn schedule_render(&self) {
478            scheduler::push_component_render(
479                self.id,
480                Box::new(RenderRunner {
481                    state: self.state.clone(),
482                }),
483            );
484        }
485
486        #[inline]
487        pub(super) fn arch_send_message<T>(&self, msg: T)
488        where
489            T: Into<COMP::Message>,
490        {
491            // We are the first message in queue, so we queue the update.
492            if self.pending_messages.push(msg.into()) == 1 {
493                self.schedule_update();
494            }
495        }
496
497        #[inline]
498        pub(super) fn arch_send_message_batch(&self, mut messages: Vec<COMP::Message>) {
499            let msg_len = messages.len();
500
501            // The queue was empty, so we queue the update
502            if self.pending_messages.append(&mut messages) == msg_len {
503                self.schedule_update();
504            }
505        }
506    }
507}
508
509#[cfg(any(feature = "ssr", feature = "csr"))]
510pub(crate) use feat_csr_ssr::*;
511
512#[cfg(feature = "csr")]
513mod feat_csr {
514    use std::cell::Ref;
515
516    use web_sys::Element;
517
518    use super::*;
519    use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
520    use crate::html::component::lifecycle::{
521        ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
522    };
523    use crate::scheduler;
524
525    impl AnyScope {
526        #[cfg(any(test, feature = "test"))]
527        pub(crate) fn test() -> Self {
528            Self {
529                type_id: TypeId::of::<()>(),
530                parent: None,
531                typed_scope: Rc::new(()),
532            }
533        }
534    }
535
536    fn schedule_props_update(
537        state: Shared<Option<ComponentState>>,
538        props: Rc<dyn Any>,
539        next_sibling_slot: DomSlot,
540    ) {
541        scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
542            state,
543            next_sibling_slot: Some(next_sibling_slot),
544            props: Some(props),
545        }));
546        // Not guaranteed to already have the scheduler started
547        scheduler::start();
548    }
549
550    impl<COMP> Scope<COMP>
551    where
552        COMP: BaseComponent,
553    {
554        /// Mounts a component with `props` to the specified `element` in the DOM.
555        pub(crate) fn mount_in_place(
556            &self,
557            root: BSubtree,
558            parent: Element,
559            slot: DomSlot,
560            props: Rc<COMP::Properties>,
561        ) -> DynamicDomSlot {
562            let bundle = Bundle::new();
563            let sibling_slot = DynamicDomSlot::new(slot);
564            let own_slot = DynamicDomSlot::new(sibling_slot.to_position());
565            let shared_slot = own_slot.clone();
566
567            let state = ComponentRenderState::Render {
568                bundle,
569                root,
570                own_slot,
571                parent,
572                sibling_slot,
573            };
574
575            scheduler::push_component_create(
576                self.id,
577                Box::new(CreateRunner {
578                    initial_render_state: state,
579                    props,
580                    scope: self.clone(),
581                    #[cfg(feature = "hydration")]
582                    prepared_state: None,
583                }),
584                Box::new(RenderRunner {
585                    state: self.state.clone(),
586                }),
587            );
588            // Not guaranteed to already have the scheduler started
589            scheduler::start();
590            shared_slot
591        }
592
593        pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
594            schedule_props_update(self.state.clone(), props, slot)
595        }
596    }
597
598    pub(crate) trait Scoped {
599        fn to_any(&self) -> AnyScope;
600        /// Get the render state if it hasn't already been destroyed
601        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
602        /// Shift the node associated with this scope to a new place
603        fn shift_node(&self, parent: Element, slot: DomSlot);
604        /// Process an event to destroy a component
605        fn destroy(self, parent_to_detach: bool);
606        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
607    }
608
609    impl<COMP: BaseComponent> Scoped for Scope<COMP> {
610        fn to_any(&self) -> AnyScope {
611            self.clone().into()
612        }
613
614        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>> {
615            let state_ref = self.state.borrow();
616
617            // check that component hasn't been destroyed
618            state_ref.as_ref()?;
619
620            Some(Ref::map(state_ref, |state_ref| {
621                &state_ref.as_ref().unwrap().render_state
622            }))
623        }
624
625        /// Process an event to destroy a component
626        fn destroy(self, parent_to_detach: bool) {
627            scheduler::push_component_destroy(Box::new(DestroyRunner {
628                state: self.state,
629                parent_to_detach,
630            }));
631            // Not guaranteed to already have the scheduler started
632            scheduler::start();
633        }
634
635        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool) {
636            self.destroy(parent_to_detach)
637        }
638
639        fn shift_node(&self, parent: Element, slot: DomSlot) {
640            let mut state_ref = self.state.borrow_mut();
641            if let Some(render_state) = state_ref.as_mut() {
642                render_state.render_state.shift(parent, slot)
643            }
644        }
645    }
646}
647#[cfg(feature = "csr")]
648pub(crate) use feat_csr::*;
649
650#[cfg(feature = "hydration")]
651mod feat_hydration {
652    use wasm_bindgen::JsCast;
653    use web_sys::{Element, HtmlScriptElement};
654
655    use super::*;
656    use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
657    use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
658    use crate::scheduler;
659    use crate::virtual_dom::Collectable;
660
661    impl<COMP> Scope<COMP>
662    where
663        COMP: BaseComponent,
664    {
665        /// Hydrates the component.
666        ///
667        /// Returns the position of the hydrated node in DOM.
668        ///
669        /// # Note
670        ///
671        /// This method is expected to collect all the elements belongs to the current component
672        /// immediately.
673        pub(crate) fn hydrate_in_place(
674            &self,
675            root: BSubtree,
676            parent: Element,
677            fragment: &mut Fragment,
678            props: Rc<COMP::Properties>,
679            prev_next_sibling: &mut Option<DynamicDomSlot>,
680        ) -> DynamicDomSlot {
681            // This is very helpful to see which component is failing during hydration
682            // which means this component may not having a stable layout / differs between
683            // client-side and server-side.
684            tracing::trace!(
685                component.id = self.id,
686                "hydration(type = {})",
687                std::any::type_name::<COMP>()
688            );
689
690            let collectable = Collectable::for_component::<COMP>();
691
692            let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
693
694            let prepared_state = match fragment
695                .back()
696                .cloned()
697                .and_then(|m| m.dyn_into::<HtmlScriptElement>().ok())
698            {
699                Some(m) if m.type_() == "application/x-yew-comp-state" => {
700                    fragment.pop_back();
701                    parent.remove_child(&m).unwrap();
702                    Some(m.text().unwrap())
703                }
704                _ => None,
705            };
706
707            let own_slot = match fragment.front().cloned() {
708                Some(first_node) => DynamicDomSlot::new(DomSlot::at(first_node)),
709                None => DynamicDomSlot::new(DomSlot::at_end()),
710            };
711            let shared_slot = own_slot.clone();
712            let sibling_slot = DynamicDomSlot::new_debug_trapped();
713            if let Some(prev_next_sibling) = prev_next_sibling {
714                prev_next_sibling.reassign(shared_slot.to_position());
715            }
716            *prev_next_sibling = Some(sibling_slot.clone());
717            let state = ComponentRenderState::Hydration {
718                parent,
719                root,
720                own_slot,
721                sibling_slot,
722                fragment,
723            };
724
725            scheduler::push_component_create(
726                self.id,
727                Box::new(CreateRunner {
728                    initial_render_state: state,
729                    props,
730                    scope: self.clone(),
731                    prepared_state,
732                }),
733                Box::new(RenderRunner {
734                    state: self.state.clone(),
735                }),
736            );
737
738            // Not guaranteed to already have the scheduler started
739            scheduler::start();
740            shared_slot
741        }
742    }
743}
744
745/// Defines a message type that can be sent to a component.
746/// Used for the return value of closure given to
747/// [Scope::batch_callback](struct.Scope.html#method.batch_callback).
748pub trait SendAsMessage<COMP: BaseComponent> {
749    /// Sends the message to the given component's scope.
750    /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback).
751    fn send(self, scope: &Scope<COMP>);
752}
753
754impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
755where
756    COMP: BaseComponent,
757{
758    fn send(self, scope: &Scope<COMP>) {
759        if let Some(msg) = self {
760            scope.send_message(msg);
761        }
762    }
763}
764
765impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
766where
767    COMP: BaseComponent,
768{
769    fn send(self, scope: &Scope<COMP>) {
770        scope.send_message_batch(self);
771    }
772}