Feat: more updates to hooks

This commit is contained in:
Jonathan Kelley committed 2021-02-07 14:59:34 -05:00
1 parent 4d01436c3f
commit 2c2882c9a2
9 files changed
+286 -84

No files matched your search

+14 -12
View File
@@ -55,27 +55,29 @@ impl<'src> Properties for Props<'src> {
}
}
fn component<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
fn component<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
// Write asynchronous rendering code that immediately returns a "suspended" VNode
// The concurrent API will then progress this component when the future finishes
// You can suspend the entire component, or just parts of it
let product_list = ctx.suspend(async {
// Suspend the rendering that completes when the future is done
match fetch_data().await {
Ok(data) => html! { <div> </div>},
Err(_) => html! { <div> </div>},
Ok(data) => html! { <div> "success!" </div>},
Err(_) => html! { <div> "failure :(" </div>},
}
});
todo!()
// ctx.view(html! {
// <div>
// // <h1> "Products" </h1>
// // // Subnodes can even be suspended
// // // When completely rendered, they won't cause the component itself to re-render, just their slot
// // <p> { product_list } </p>
// </div>
// })
// todo!()
ctx.view(html! {
<div>
<h1> "Products" </h1>
<button> "hello!" </button>
// Subnodes can even be suspended
// When completely rendered, they won't cause the component itself to re-render, just their slot
// <p> { product_list } </p>
</div>
})
}
fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
+136 -19
View File
@@ -1,43 +1,160 @@
use bumpalo::Bump;
use dioxus_core::prelude::{Context, VNode};
use std::{any::Any, cell::RefCell, rc::Rc};
use dioxus_core::{
prelude::{Context, VElement, VNode, FC},
virtual_dom::{Properties, Scope},
};
use std::{
any::Any,
borrow::BorrowMut,
cell::RefCell,
mem::swap,
ops::{Deref, DerefMut},
rc::Rc,
};
use std::{borrow::Borrow, sync::atomic::AtomicUsize};
use typed_arena::Arena;
fn main() {
let ar = Arena::new();
let mut scope = Scope::new(component);
(0..5).for_each(|f| {
// Create the temporary context obect
let c = Context {
_p: std::marker::PhantomData {},
props: (),
idx: 0.into(),
arena: &ar,
hooks: RefCell::new(Vec::new()),
};
component(c);
let ctx = scope.create_context();
component(ctx);
});
}
// we need to do something about props and context being borrowed from different sources....
// kinda anooying
/// use_ref creates a new value when the component is created and then borrows that value on every render
fn component(ctx: Context<()>) {
fn component(ctx: Context<()>) -> VNode {
(0..10).for_each(|f| {
let r = use_ref(&ctx, move || f);
assert_eq!(*r, f);
});
todo!()
}
pub struct UseRef<T: 'static> {
current: RefCell<T>,
}
impl<T: 'static> UseRef<T> {
fn new(val: T) -> Self {
Self {
current: RefCell::new(val),
}
}
fn modify(&self, modifier: impl FnOnce(&mut T)) {
let mut val = self.current.borrow_mut();
let val_as_ref = val.deref_mut();
modifier(val_as_ref);
}
}
/// Store a mutable value between renders!
/// To read the value, borrow the ref.
/// To change it, use modify.
/// Modifications to this value do not cause updates to the component
pub fn use_ref<'a, P, T: 'static>(
ctx: &'a Context<'a, P>,
initial_state_fn: impl FnOnce() -> T + 'static,
) -> &'a T {
) -> &'a UseRef<T> {
ctx.use_hook(
|| initial_state_fn(), // initializer
|state| state, // runner, borrows the internal value
|b| {}, // tear down
|| UseRef::new(initial_state_fn()),
|state, _| &*state,
|_| {},
)
}
struct UseState<T> {
new_val: Rc<RefCell<Option<T>>>,
current_val: T,
caller: Box<dyn Fn(T) + 'static>,
}
/// Store state between component renders!
/// When called, this hook retrives a stored value and provides a setter to update that value.
/// When the setter is called, the component is re-ran with the new value.
///
/// This is behaves almost exactly the same way as React's "use_state".
///
/// Usage:
/// ```ignore
/// static Example: FC<()> = |ctx| {
/// let (counter, set_counter) = use_state(ctx, || 0);
/// let increment = || set_couter(counter + 1);
/// let decrement = || set_couter(counter + 1);
///
/// html! {
/// <div>
/// <h1>"Counter: {counter}" </h1>
/// <button onclick={increment}> "Increment" </button>
/// <button onclick={decrement}> "Decrement" </button>
/// </div>
/// }
/// }
/// ```
pub fn use_state<'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
ctx: &'a Context<'a, P>,
initial_state_fn: F,
) -> (&T, &impl Fn(T)) {
ctx.use_hook(
move || UseState {
new_val: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(),
caller: Box::new(|_| println!("setter called!")),
},
move |hook, updater| {
let inner = hook.new_val.clone();
// todo: swap out the caller with a subscription call and an internal update
hook.caller = Box::new(move |new_val| {
// update the setter with the new value
let mut new_inner = inner.as_ref().borrow_mut();
*new_inner = Some(new_val);
});
// box gets derefed into a ref which is then taken as ref with the hook
(&hook.current_val, &hook.caller)
},
|_| {},
)
}
fn test_use_state(ctx: Context<()>) -> VNode {
let (val, set_val) = use_state(&ctx, || 10);
// gloriousness!
// closures are taken with ref to ctx :)
// Can freely use hooks
let handler_0: _ = || set_val(val + 1);
let handler_1: _ = || set_val(val + 10);
let handler_2: _ = || set_val(val + 100);
// these fns are captured, boxed into the bump arena, and then attached to the listeners
// the vnodes share the lifetime of these closures (and the hook data)
// whenever a listener wakes up, we take the reference directly from the bump arena and, with a small bit
// of unsafe code, execute the associated closure / listener function
// Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
// are removed and UB is prevented from affecting the program
{
VNode::Element(VElement::new("button"))
}
}
fn test_use_state_2(ctx: Context<()>) -> VNode {
let (val, set_val) = use_state(&ctx, || 0);
let incr = || set_val(val + 1);
let decr = || set_val(val - 1);
todo!()
// html! {
// <div>
// <nav class="menu">
// <button onclick=incr> { "Increment" } </button>
// <button onclick=decr> { "Decrement" } </button>
// </nav>
// <p> <b>{ "Current value: {val}" }</b> </p>
// </div>
// }
}