wip: remove runner on hook and then update docs
This commit is contained in:
1 parent
7d41a16208
commit
d1560450ba
95 files changed
+1339
-675
No files matched your search
@@ -25,16 +25,16 @@ criterion_main!(mbenches);
|
||||
fn create_rows(c: &mut Criterion) {
|
||||
static App: Component = |cx| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!(Row {
|
||||
row_id: f,
|
||||
label: label
|
||||
})
|
||||
});
|
||||
|
||||
rsx!(cx, table {
|
||||
tbody {
|
||||
{rows}
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!(Row {
|
||||
row_id: f,
|
||||
label: label
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -58,12 +58,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{cx.props.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
|
||||
a { class: "lbl", "{adj}" "{col}" "{noun}" }
|
||||
}
|
||||
td { class: "col-md-1"
|
||||
a { class: "remove", onclick: move |_| {/* remove */}
|
||||
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
|
||||
td { class: "col-md-1",
|
||||
a { class: "remove", onclick: move |_| {/* remove */},
|
||||
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
}
|
||||
|
||||
fn parent(cx: Scope) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| f);
|
||||
let value = cx.use_hook(|_| String::new());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
@@ -33,8 +33,8 @@ struct ChildProps<'a> {
|
||||
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"it's nested {cx.props.name}"
|
||||
{&cx.props.children}
|
||||
"it's nested {cx.props.name}",
|
||||
&cx.props.children
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -10,7 +10,7 @@ fn main() {
|
||||
}
|
||||
|
||||
fn parent(cx: Scope) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| f);
|
||||
let value = cx.use_hook(|_| String::new());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
+12
-19
@@ -473,9 +473,16 @@ impl<'bump> DiffState<'bump> {
|
||||
// Check the most common cases first
|
||||
// these are *actual* elements, not wrappers around lists
|
||||
(Text(old), Text(new)) => {
|
||||
self.diff_text_nodes(old, new, old_node, new_node);
|
||||
if let Some(root) = old.id.get() {
|
||||
if old.text != new.text {
|
||||
self.mutations.set_text(new.text, root.as_u64());
|
||||
}
|
||||
self.scopes.update_node(new_node, root);
|
||||
|
||||
new.id.set(Some(root));
|
||||
}
|
||||
}
|
||||
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
|
||||
|
||||
(Placeholder(old), Placeholder(new)) => {
|
||||
if let Some(root) = old.id.get() {
|
||||
self.scopes.update_node(new_node, root);
|
||||
@@ -483,10 +490,13 @@ impl<'bump> DiffState<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
|
||||
|
||||
// These two sets are pointers to nodes but are not actually nodes themselves
|
||||
(Component(old), Component(new)) => {
|
||||
self.diff_component_nodes(old_node, new_node, *old, *new)
|
||||
}
|
||||
|
||||
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
||||
|
||||
// The normal pathway still works, but generates slightly weird instructions
|
||||
@@ -506,23 +516,6 @@ impl<'bump> DiffState<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_text_nodes(
|
||||
&mut self,
|
||||
old: &'bump VText<'bump>,
|
||||
new: &'bump VText<'bump>,
|
||||
_old_node: &'bump VNode<'bump>,
|
||||
new_node: &'bump VNode<'bump>,
|
||||
) {
|
||||
if let Some(root) = old.id.get() {
|
||||
if old.text != new.text {
|
||||
self.mutations.set_text(new.text, root.as_u64());
|
||||
}
|
||||
self.scopes.update_node(new_node, root);
|
||||
|
||||
new.id.set(Some(root));
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_element_nodes(
|
||||
&mut self,
|
||||
old: &'bump VElement<'bump>,
|
||||
|
||||
@@ -65,10 +65,10 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
|
||||
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
|
||||
ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
|
||||
VText, VirtualDom,
|
||||
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, Event,
|
||||
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
|
||||
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement,
|
||||
VFragment, VNode, VPlaceholder, VText, VirtualDom,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
+18
-12
@@ -320,6 +320,18 @@ impl ScopeArena {
|
||||
pub fn root_node(&self, id: ScopeId) -> &VNode {
|
||||
self.fin_head(id)
|
||||
}
|
||||
|
||||
// this is totally okay since all our nodes are always in a valid state
|
||||
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
|
||||
let ptr = self.nodes.borrow().get(id.0).cloned();
|
||||
match ptr {
|
||||
Some(ptr) => {
|
||||
let node = unsafe { &*ptr };
|
||||
Some(unsafe { extend_vnode(node) })
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||
@@ -620,7 +632,7 @@ impl ScopeState {
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static App: Component = |cx| {
|
||||
/// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {});
|
||||
/// cx.use_hook(|_| cx.provide_context(SharedState("world")));
|
||||
/// rsx!(cx, Child {})
|
||||
/// }
|
||||
///
|
||||
@@ -711,17 +723,13 @@ impl ScopeState {
|
||||
/// ```ignore
|
||||
/// // use_ref is the simplest way of storing a value between renders
|
||||
/// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
|
||||
/// use_hook(
|
||||
/// || Rc::new(RefCell::new(initial_value())),
|
||||
/// |state| state,
|
||||
/// )
|
||||
/// use_hook(|| Rc::new(RefCell::new(initial_value())))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_hook<'src, State: 'static, Output: 'src>(
|
||||
pub fn use_hook<'src, State: 'static>(
|
||||
&'src self,
|
||||
initializer: impl FnOnce(usize) -> State,
|
||||
runner: impl FnOnce(&'src mut State) -> Output,
|
||||
) -> Output {
|
||||
) -> &'src mut State {
|
||||
let mut vals = self.hook_vals.borrow_mut();
|
||||
|
||||
let hook_len = vals.len();
|
||||
@@ -731,7 +739,7 @@ impl ScopeState {
|
||||
vals.push(self.hook_arena.alloc(initializer(hook_len)));
|
||||
}
|
||||
|
||||
let state = vals
|
||||
vals
|
||||
.get(cur_idx)
|
||||
.and_then(|inn| {
|
||||
self.hook_idx.set(cur_idx + 1);
|
||||
@@ -746,9 +754,7 @@ impl ScopeState {
|
||||
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
||||
Functions prefixed with "use" should never be called conditionally.
|
||||
"###,
|
||||
);
|
||||
|
||||
runner(state)
|
||||
)
|
||||
}
|
||||
|
||||
/// The "work in progress frame" represents the frame that is currently being worked on.
|
||||
|
||||
@@ -260,6 +260,10 @@ impl VirtualDom {
|
||||
self.channel.0.clone()
|
||||
}
|
||||
|
||||
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
|
||||
self.scopes.get_element(id)
|
||||
}
|
||||
|
||||
/// Add a new message to the scheduler queue directly.
|
||||
///
|
||||
///
|
||||
@@ -1003,3 +1007,103 @@ impl EmptyBuilder {
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
|
||||
pub struct Event<T> {
|
||||
data: Arc<T>,
|
||||
_cancel: std::rc::Rc<std::cell::Cell<bool>>,
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Event<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.data.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
pub fn cancel(&self) {}
|
||||
pub fn timestamp(&self) {}
|
||||
pub fn triggered_element(&self) -> Option<Element> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElementIdIterator<'a> {
|
||||
vdom: &'a VirtualDom,
|
||||
|
||||
// Heuristcally we should never bleed into 5 completely nested fragments/components
|
||||
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
||||
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
|
||||
}
|
||||
|
||||
impl<'a> ElementIdIterator<'a> {
|
||||
pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
|
||||
Self {
|
||||
vdom,
|
||||
stack: smallvec::smallvec![(0, node)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ElementIdIterator<'a> {
|
||||
type Item = &'a VNode<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<&'a VNode<'a>> {
|
||||
let mut should_pop = false;
|
||||
let mut returned_node = None;
|
||||
let mut should_push = None;
|
||||
|
||||
while returned_node.is_none() {
|
||||
if let Some((count, node)) = self.stack.last_mut() {
|
||||
match node {
|
||||
// We can only exit our looping when we get "real" nodes
|
||||
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
|
||||
// We've recursed INTO an element/text
|
||||
// We need to recurse *out* of it and move forward to the next
|
||||
// println!("Found element! Returning it!");
|
||||
should_pop = true;
|
||||
returned_node = Some(&**node);
|
||||
}
|
||||
|
||||
// If we get a fragment we push the next child
|
||||
VNode::Fragment(frag) => {
|
||||
let _count = *count as usize;
|
||||
if _count >= frag.children.len() {
|
||||
should_pop = true;
|
||||
} else {
|
||||
should_push = Some(&frag.children[_count]);
|
||||
}
|
||||
}
|
||||
|
||||
// For components, we load their root and push them onto the stack
|
||||
VNode::Component(sc) => {
|
||||
// todo!();
|
||||
let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
|
||||
// Simply swap the current node on the stack with the root of the component
|
||||
*node = scope.root_node();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there's no more items on the stack, we're done!
|
||||
return None;
|
||||
}
|
||||
|
||||
if should_pop {
|
||||
self.stack.pop();
|
||||
if let Some((id, _)) = self.stack.last_mut() {
|
||||
*id += 1;
|
||||
}
|
||||
should_pop = false;
|
||||
}
|
||||
|
||||
if let Some(push) = should_push {
|
||||
self.stack.push((0, push));
|
||||
should_push = None;
|
||||
}
|
||||
}
|
||||
|
||||
returned_node
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ fn test_borrowed_state() {
|
||||
}
|
||||
|
||||
fn Parent(cx: Scope) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f);
|
||||
let value = cx.use_hook(|_| String::new());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
@@ -25,7 +25,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
|
||||
#[test]
|
||||
fn test_early_abort() {
|
||||
const app: Component = |cx| {
|
||||
let val = cx.use_hook(|_| 0, |f| f);
|
||||
let val = cx.use_hook(|_| 0);
|
||||
|
||||
*val += 1;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ fn manual_diffing() {
|
||||
#[test]
|
||||
fn events_generate() {
|
||||
static App: Component = |cx| {
|
||||
let count = cx.use_hook(|_| 0, |f| f);
|
||||
let count = cx.use_hook(|_| 0);
|
||||
|
||||
let inner = match *count {
|
||||
0 => {
|
||||
@@ -105,7 +105,7 @@ fn events_generate() {
|
||||
#[test]
|
||||
fn components_generate() {
|
||||
static App: Component = |cx| {
|
||||
let render_phase = cx.use_hook(|_| 0, |f| f);
|
||||
let render_phase = cx.use_hook(|_| 0);
|
||||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
@@ -221,7 +221,7 @@ fn components_generate() {
|
||||
#[test]
|
||||
fn component_swap() {
|
||||
static App: Component = |cx| {
|
||||
let render_phase = cx.use_hook(|_| 0, |f| f);
|
||||
let render_phase = cx.use_hook(|_| 0);
|
||||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
|
||||
@@ -29,7 +29,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
|
||||
#[test]
|
||||
fn test_memory_leak() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = cx.use_hook(|_| 0, |f| f);
|
||||
let val = cx.use_hook(|_| 0);
|
||||
|
||||
*val += 1;
|
||||
|
||||
@@ -37,7 +37,7 @@ fn test_memory_leak() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|_| String::from("asd"), |f| f);
|
||||
let name = cx.use_hook(|_| String::from("asd"));
|
||||
|
||||
cx.render(rsx!(
|
||||
div { "Hello, world!" }
|
||||
@@ -86,7 +86,7 @@ fn test_memory_leak() {
|
||||
#[test]
|
||||
fn memo_works_properly() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = cx.use_hook(|_| 0, |f| f);
|
||||
let val = cx.use_hook(|_| 0);
|
||||
|
||||
*val += 1;
|
||||
|
||||
@@ -94,7 +94,7 @@ fn memo_works_properly() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|_| String::from("asd"), |f| f);
|
||||
let name = cx.use_hook(|_| String::from("asd"));
|
||||
|
||||
cx.render(rsx!(
|
||||
div { "Hello, world!" }
|
||||
@@ -204,7 +204,7 @@ fn free_works_on_root_hooks() {
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
|
||||
let name = cx.use_hook(|_| Droppable(String::from("asd")));
|
||||
rsx!(cx, div { "{name.0}" })
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ fn free_works_on_root_hooks() {
|
||||
fn old_props_arent_stale() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
dbg!("rendering parent");
|
||||
let cnt = cx.use_hook(|_| 0, |f| f);
|
||||
let cnt = cx.use_hook(|_| 0);
|
||||
*cnt += 1;
|
||||
|
||||
if *cnt == 1 {
|
||||
|
||||
Reference in new issue
Block a user