mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: improved <For/>
algorithm (#1146)
Rewrites the algorithm behind the `<For/>` component to create a more robust keyed list implementation, with the potential for future additional optimizations related to grouping moved ranges. Closes #533.
This commit is contained in:
parent
bb10b32200
commit
b6d9060152
6 changed files with 966 additions and 363 deletions
|
@ -1,2 +1,2 @@
|
||||||
[build]
|
# [build]
|
||||||
# target = "wasm32-unknown-unknown"
|
# target = "wasm32-unknown-unknown"
|
|
@ -9,5 +9,6 @@ gloo = { version = "0.8", features = ["futures"] }
|
||||||
leptos = { path = "../../../leptos", features = ["tracing"] }
|
leptos = { path = "../../../leptos", features = ["tracing"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
tracing-subscriber-wasm = "0.1"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
|
@ -1,102 +1,142 @@
|
||||||
|
#![feature(iter_intersperse)]
|
||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use tracing::field::debug;
|
use tracing::field::debug;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_max_level(tracing::Level::TRACE)
|
.with_max_level(tracing::Level::TRACE)
|
||||||
.without_time()
|
.without_time()
|
||||||
.with_file(true)
|
.with_file(true)
|
||||||
.with_line_number(true)
|
.with_line_number(true)
|
||||||
.with_target(false)
|
.with_target(false)
|
||||||
.with_writer(utils::MakeConsoleWriter)
|
.with_writer(tracing_subscriber_wasm::MakeConsoleWriter::default())
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.pretty()
|
.pretty()
|
||||||
.finish()
|
.finish()
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
mount_to_body(view_fn);
|
mount_to_body(view_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn view_fn(cx: Scope) -> impl IntoView {
|
||||||
|
// view! { cx,
|
||||||
|
// <h2>"Passing Tests"</h2>
|
||||||
|
// <ul>
|
||||||
|
// /* These work! */
|
||||||
|
// <Test from=[1] to=[] />
|
||||||
|
// <Test from=[1, 2] to=[] />
|
||||||
|
// <Test from=[1, 2, 3] to=[] />
|
||||||
|
// <hr/>
|
||||||
|
// <Test from=[] to=[1] />
|
||||||
|
// <Test from=[1, 2] to=[1] />
|
||||||
|
// <Test from=[2, 1] to=[1] />
|
||||||
|
// <hr/>
|
||||||
|
// <Test from=[1, 2, 3] to=[1, 2] />
|
||||||
|
// <Test from=[2] to=[1, 2] />
|
||||||
|
// <Test from=[1] to=[1, 2] />
|
||||||
|
// <Test from=[] to=[1, 2, 3] />
|
||||||
|
// <Test from=[2] to=[1, 2, 3] />
|
||||||
|
// <Test from=[1] to=[1, 2, 3] />
|
||||||
|
// <Test from=[1, 3, 2] to=[1, 2, 3] />
|
||||||
|
// <Test from=[2, 1, 3] to=[1, 2, 3] />
|
||||||
|
// </ul>
|
||||||
|
// <h2>"Broken Tests"</h2>
|
||||||
|
// <ul>
|
||||||
|
// <Test from=[3] to=[1, 2, 3] />
|
||||||
|
// <Test from=[3, 1] to=[1, 2, 3] />
|
||||||
|
// <Test from=[3, 2, 1] to=[1, 2, 3] />
|
||||||
|
// <hr/>
|
||||||
|
// <Test from=[1, 4, 2, 3] to=[1, 2, 3, 4] />
|
||||||
|
// <hr/>
|
||||||
|
// <Test from=[1, 4, 3, 2, 5] to=[1, 2, 3, 4, 5] />
|
||||||
|
// <Test from=[4, 5, 3, 1, 2] to=[1, 2, 3, 4, 5] />
|
||||||
|
// </ul>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[component]
|
||||||
|
// fn Test<From, To>(cx: Scope, from: From, to: To) -> impl IntoView
|
||||||
|
// where
|
||||||
|
// From: IntoIterator<Item = usize>,
|
||||||
|
// To: IntoIterator<Item = usize>,
|
||||||
|
// {
|
||||||
|
// let from = from.into_iter().collect::<Vec<_>>();
|
||||||
|
// let to = to.into_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// let (list, set_list) = create_signal(cx, from.clone());
|
||||||
|
// request_animation_frame({
|
||||||
|
// let to = to.clone();
|
||||||
|
// move || {
|
||||||
|
// set_list(to);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// view! { cx,
|
||||||
|
// <li>
|
||||||
|
// "from: [" {move ||
|
||||||
|
// from
|
||||||
|
// .iter()
|
||||||
|
// .map(ToString::to_string)
|
||||||
|
// .intersperse(", ".to_string())
|
||||||
|
// .collect::<String>()
|
||||||
|
// } "]"
|
||||||
|
// <br />
|
||||||
|
// "to: [" {move ||
|
||||||
|
// to
|
||||||
|
// .iter()
|
||||||
|
// .map(ToString::to_string)
|
||||||
|
// .intersperse(", ".to_string())
|
||||||
|
// .collect::<String>()
|
||||||
|
// } "]"
|
||||||
|
// <br />
|
||||||
|
// "result: ["
|
||||||
|
// <For
|
||||||
|
// each=list
|
||||||
|
// key=|i| *i
|
||||||
|
// view=|cx, i| {
|
||||||
|
// view! { cx, <span>{i} ", "</span> }
|
||||||
|
// }
|
||||||
|
// /> "]"
|
||||||
|
// /* <p>
|
||||||
|
// "Pre | "
|
||||||
|
// <For
|
||||||
|
// each=list
|
||||||
|
// key=|i| *i
|
||||||
|
// view=|cx, i| {
|
||||||
|
// view! { cx, <span>{i}</span> }
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// " | Post"
|
||||||
|
// </p> */
|
||||||
|
// </li>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
fn view_fn(cx: Scope) -> impl IntoView {
|
fn view_fn(cx: Scope) -> impl IntoView {
|
||||||
let view = view! { cx,
|
let (should_show_a, sett_should_show_a) = create_signal(cx, true);
|
||||||
<For
|
|
||||||
each=|| vec![0, 1, 2, 3, 4, 5, 6, 7]
|
|
||||||
key=|i| *i
|
|
||||||
view=|cx, i| view! { cx, {i} }
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
.into_view(cx);
|
|
||||||
|
|
||||||
let (a, set_a) = create_signal(cx, view.clone());
|
let a = vec![1, 2, 3, 4];
|
||||||
let (b, set_b) = create_signal(cx, view);
|
let b = vec![1, 2, 3];
|
||||||
|
|
||||||
let (is_a, set_is_a) = create_signal(cx, true);
|
view! { cx,
|
||||||
|
<button on:click=move |_| sett_should_show_a.update(|show| *show = !*show)>"Toggle"</button>
|
||||||
|
|
||||||
let handle_toggle = move |_| {
|
<For
|
||||||
trace!("toggling");
|
each={move || if should_show_a.get() {
|
||||||
if is_a() {
|
a.clone()
|
||||||
set_b(a());
|
} else {
|
||||||
|
b.clone()
|
||||||
set_is_a(false);
|
}}
|
||||||
} else {
|
key=|i| *i
|
||||||
set_a(a());
|
view=|cx, i| view! { cx, <h1>{i}</h1> }
|
||||||
|
/>
|
||||||
set_is_a(true);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let a_tag = view! { cx, <svg::a/> };
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<button on:click=handle_toggle>"Toggle"</button>
|
|
||||||
</div>
|
|
||||||
<svg>{a_tag}</svg>
|
|
||||||
<Example/>
|
|
||||||
<A child=Signal::from(a) />
|
|
||||||
<A child=Signal::from(b) />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
fn A(cx: Scope, child: Signal<View>) -> impl IntoView {
|
|
||||||
move || child()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
fn Example(cx: Scope) -> impl IntoView {
|
|
||||||
trace!("rendering <Example/>");
|
|
||||||
|
|
||||||
let (value, set_value) = create_signal(cx, 10);
|
|
||||||
|
|
||||||
let memo = create_memo(cx, move |_| value() * 2);
|
|
||||||
let derived = Signal::derive(cx, move || value() * 3);
|
|
||||||
|
|
||||||
create_effect(cx, move |_| {
|
|
||||||
trace!("logging value of derived..., {}", derived.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
set_timeout(
|
|
||||||
move || set_value.update(|v| *v += 1),
|
|
||||||
std::time::Duration::from_millis(50),
|
|
||||||
);
|
|
||||||
|
|
||||||
view! { cx,
|
|
||||||
<h1>"Example"</h1>
|
|
||||||
<button on:click=move |_| set_value.update(|value| *value += 1)>
|
|
||||||
"Click me"
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
pub struct MakeConsoleWriter;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use tracing_subscriber::fmt::MakeWriter;
|
|
||||||
|
|
||||||
impl<'a> MakeWriter<'a> for MakeConsoleWriter {
|
|
||||||
type Writer = ConsoleWriter;
|
|
||||||
|
|
||||||
fn make_writer(&'a self) -> Self::Writer {
|
|
||||||
unimplemented!("use make_writer_for instead");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_writer_for(&'a self, meta: &tracing::Metadata<'_>) -> Self::Writer {
|
|
||||||
ConsoleWriter(*meta.level(), Vec::with_capacity(256))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ConsoleWriter(tracing::Level, Vec<u8>);
|
|
||||||
|
|
||||||
impl io::Write for ConsoleWriter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.1.write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
use gloo::console;
|
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
let data = String::from_utf8(self.1.to_owned())
|
|
||||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "data not UTF-8"))?;
|
|
||||||
|
|
||||||
match self.0 {
|
|
||||||
Level::TRACE => console::debug!(&data),
|
|
||||||
Level::DEBUG => console::debug!(&data),
|
|
||||||
Level::INFO => console::log!(&data),
|
|
||||||
Level::WARN => console::warn!(&data),
|
|
||||||
Level::ERROR => console::error!(&data),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ConsoleWriter {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let _ = self.flush();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -402,7 +402,7 @@ cfg_if! {
|
||||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||||
use web_sys::Node;
|
use web_sys::Node;
|
||||||
|
|
||||||
trait NonViewMarkerSibling {
|
pub(crate) trait NonViewMarkerSibling {
|
||||||
fn next_non_view_marker_sibling(&self) -> Option<Node>;
|
fn next_non_view_marker_sibling(&self) -> Option<Node>;
|
||||||
|
|
||||||
fn previous_non_view_marker_sibling(&self) -> Option<Node>;
|
fn previous_non_view_marker_sibling(&self) -> Option<Node>;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue