11use super :: { use_hook, Hook } ;
22use crate :: get_current_scope;
3- use std:: any:: TypeId ;
4- use std:: cell:: RefCell ;
5- use std:: rc:: { Rc , Weak } ;
6- use std:: { iter, mem} ;
7- use yew:: html;
8- use yew:: html:: { AnyScope , Scope } ;
9- use yew:: { Children , Component , ComponentLink , Html , Properties } ;
10-
11- type ConsumerCallback < T > = Box < dyn Fn ( Rc < T > ) > ;
12-
13- /// Props for [`ContextProvider`]
14- #[ derive( Clone , PartialEq , Properties ) ]
15- pub struct ContextProviderProps < T : Clone + PartialEq > {
16- pub context : T ,
17- pub children : Children ,
18- }
19-
20- /// The context provider component.
21- ///
22- /// Every child (direct or indirect) of this component may access the context value.
23- /// Currently the only way to consume the context is using the [`use_context`] hook.
24- pub struct ContextProvider < T : Clone + PartialEq + ' static > {
25- context : Rc < T > ,
26- children : Children ,
27- consumers : RefCell < Vec < Weak < ConsumerCallback < T > > > > ,
28- }
29-
30- impl < T : Clone + PartialEq > ContextProvider < T > {
31- /// Add the callback to the subscriber list to be called whenever the context changes.
32- /// The consumer is unsubscribed as soon as the callback is dropped.
33- fn subscribe_consumer ( & self , mut callback : Weak < ConsumerCallback < T > > ) {
34- let mut consumers = self . consumers . borrow_mut ( ) ;
35- // consumers re-subscribe on every render. Try to keep the subscriber list small by reusing dead slots.
36- for cb in consumers. iter_mut ( ) {
37- if cb. strong_count ( ) == 0 {
38- mem:: swap ( cb, & mut callback) ;
39- return ;
40- }
41- }
42-
43- // no slot to reuse, this is a new consumer
44- consumers. push ( callback) ;
45- }
46-
47- /// Notify all subscribed consumers and remove dropped consumers from the list.
48- fn notify_consumers ( & mut self ) {
49- let context = & self . context ;
50- self . consumers . borrow_mut ( ) . retain ( |cb| {
51- if let Some ( cb) = cb. upgrade ( ) {
52- cb ( Rc :: clone ( context) ) ;
53- true
54- } else {
55- false
56- }
57- } ) ;
58- }
59- }
60-
61- impl < T : Clone + PartialEq + ' static > Component for ContextProvider < T > {
62- type Message = ( ) ;
63- type Properties = ContextProviderProps < T > ;
64-
65- fn create ( props : Self :: Properties , _link : ComponentLink < Self > ) -> Self {
66- Self {
67- children : props. children ,
68- context : Rc :: new ( props. context ) ,
69- consumers : RefCell :: new ( Vec :: new ( ) ) ,
70- }
71- }
72-
73- fn update ( & mut self , _msg : Self :: Message ) -> bool {
74- true
75- }
76-
77- fn change ( & mut self , props : Self :: Properties ) -> bool {
78- let should_render = if self . children == props. children {
79- false
80- } else {
81- self . children = props. children ;
82- true
83- } ;
84-
85- let new_context = Rc :: new ( props. context ) ;
86- if self . context != new_context {
87- self . context = new_context;
88- self . notify_consumers ( ) ;
89- }
90-
91- should_render
92- }
93-
94- fn view ( & self ) -> Html {
95- html ! { <>{ self . children. clone( ) } </> }
96- }
97- }
98-
99- fn find_context_provider_scope < T : Clone + PartialEq + ' static > (
100- scope : & AnyScope ,
101- ) -> Option < Scope < ContextProvider < T > > > {
102- let expected_type_id = TypeId :: of :: < ContextProvider < T > > ( ) ;
103- iter:: successors ( Some ( scope) , |scope| scope. get_parent ( ) )
104- . filter ( |scope| scope. get_type_id ( ) == & expected_type_id)
105- . cloned ( )
106- . map ( AnyScope :: downcast :: < ContextProvider < T > > )
107- . next ( )
108- }
109-
110- fn with_provider_component < T , F , R > (
111- provider_scope : & Option < Scope < ContextProvider < T > > > ,
112- f : F ,
113- ) -> Option < R >
114- where
115- T : Clone + PartialEq ,
116- F : FnOnce ( & ContextProvider < T > ) -> R ,
117- {
118- provider_scope
119- . as_ref ( )
120- . and_then ( |scope| scope. get_component ( ) . map ( |comp| f ( & * comp) ) )
121- }
3+ use yew:: context:: ContextHandle ;
1224
1235/// Hook for consuming context values in function components.
1246/// The context of the type passed as `T` is returned. If there is no such context in scope, `None` is returned.
@@ -148,17 +30,14 @@ where
14830/// }
14931/// }
15032/// ```
151- pub fn use_context < T : Clone + PartialEq + ' static > ( ) -> Option < Rc < T > > {
33+ pub fn use_context < T : Clone + PartialEq + ' static > ( ) -> Option < T > {
15234 struct UseContextState < T2 : Clone + PartialEq + ' static > {
153- provider_scope : Option < Scope < ContextProvider < T2 > > > ,
154- current_context : Option < Rc < T2 > > ,
155- callback : Option < Rc < ConsumerCallback < T2 > > > ,
35+ initialized : bool ,
36+ context : Option < ( T2 , ContextHandle < T2 > ) > ,
15637 }
15738 impl < T : Clone + PartialEq + ' static > Hook for UseContextState < T > {
15839 fn tear_down ( & mut self ) {
159- if let Some ( cb) = self . callback . take ( ) {
160- drop ( cb) ;
161- }
40+ self . context = None ;
16241 }
16342 }
16443
@@ -167,31 +46,27 @@ pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<Rc<T>> {
16746
16847 use_hook (
16948 |state : & mut UseContextState < T > , hook_callback| {
170- state. callback = Some ( Rc :: new ( Box :: new ( move |ctx : Rc < T > | {
171- hook_callback (
172- |state : & mut UseContextState < T > | {
173- state. current_context = Some ( ctx) ;
174- true
175- } ,
176- false , // run pre render
177- ) ;
178- } ) ) ) ;
179- let weak_cb = Rc :: downgrade ( state. callback . as_ref ( ) . unwrap ( ) ) ;
180- with_provider_component ( & state. provider_scope , |comp| {
181- comp. subscribe_consumer ( weak_cb)
182- } ) ;
49+ if !state. initialized {
50+ state. initialized = true ;
51+ let callback = move |ctx : T | {
52+ hook_callback (
53+ |state : & mut UseContextState < T > | {
54+ if let Some ( context) = & mut state. context {
55+ context. 0 = ctx;
56+ }
57+ true
58+ } ,
59+ false , // run pre render
60+ ) ;
61+ } ;
62+ state. context = scope. context :: < T > ( callback. into ( ) ) ;
63+ }
18364
184- state. current_context . clone ( )
65+ Some ( state. context . as_ref ( ) ? . 0 . clone ( ) )
18566 } ,
186- move || {
187- let provider_scope = find_context_provider_scope :: < T > ( & scope) ;
188- let current_context =
189- with_provider_component ( & provider_scope, |comp| Rc :: clone ( & comp. context ) ) ;
190- UseContextState {
191- provider_scope,
192- current_context,
193- callback : None ,
194- }
67+ move || UseContextState {
68+ initialized : false ,
69+ context : None ,
19570 } ,
19671 )
19772}
0 commit comments