mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add the ability to use non-literal string as attribute names (#2593)
* Use AttrValue instead of &'static str in Attributes::IndexMap * avoid monomorphization to reduce binary size * fmt & `#[inline(always)]`s * more attempts to reduce the code size * make clippy happy
This commit is contained in:
parent
73771562c2
commit
e9b64e0a45
@ -7,6 +7,7 @@ use std::collections::HashMap;
|
||||
use std::iter;
|
||||
use std::ops::Deref;
|
||||
use web_sys::{Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
|
||||
use yew::AttrValue;
|
||||
|
||||
impl<T: AccessValue> Apply for Value<T> {
|
||||
type Element = T;
|
||||
@ -83,24 +84,20 @@ impl Apply for InputFields {
|
||||
|
||||
impl Attributes {
|
||||
#[cold]
|
||||
fn apply_diff_index_maps<'a, A, B>(
|
||||
fn apply_diff_index_maps(
|
||||
el: &Element,
|
||||
// this makes it possible to diff `&'a IndexMap<_, A>` and `IndexMap<_, &'a A>`.
|
||||
mut new_iter: impl Iterator<Item = (&'static str, &'a str)>,
|
||||
new: &IndexMap<&'static str, A>,
|
||||
old: &IndexMap<&'static str, B>,
|
||||
) where
|
||||
A: AsRef<str>,
|
||||
B: AsRef<str>,
|
||||
{
|
||||
new: &IndexMap<AttrValue, AttrValue>,
|
||||
old: &IndexMap<AttrValue, AttrValue>,
|
||||
) {
|
||||
let mut old_iter = old.iter();
|
||||
let mut new_iter = new.iter();
|
||||
loop {
|
||||
match (new_iter.next(), old_iter.next()) {
|
||||
(Some((new_key, new_value)), Some((old_key, old_value))) => {
|
||||
if new_key != *old_key {
|
||||
if new_key != old_key {
|
||||
break;
|
||||
}
|
||||
if new_value != old_value.as_ref() {
|
||||
if new_value != old_value {
|
||||
Self::set_attribute(el, new_key, new_value);
|
||||
}
|
||||
}
|
||||
@ -109,7 +106,7 @@ impl Attributes {
|
||||
for (key, value) in iter::once(attr).chain(new_iter) {
|
||||
match old.get(key) {
|
||||
Some(old_value) => {
|
||||
if value != old_value.as_ref() {
|
||||
if value != old_value {
|
||||
Self::set_attribute(el, key, value);
|
||||
}
|
||||
}
|
||||
@ -123,6 +120,7 @@ impl Attributes {
|
||||
// removed attributes
|
||||
(None, Some(attr)) => {
|
||||
for (key, _) in iter::once(attr).chain(old_iter) {
|
||||
let key = key;
|
||||
if !new.contains_key(key) {
|
||||
Self::remove_attribute(el, key);
|
||||
}
|
||||
@ -138,7 +136,7 @@ impl Attributes {
|
||||
/// Works with any [Attributes] variants.
|
||||
#[cold]
|
||||
fn apply_diff_as_maps<'a>(el: &Element, new: &'a Self, old: &'a Self) {
|
||||
fn collect<'a>(src: &'a Attributes) -> HashMap<&'static str, &'a str> {
|
||||
fn collect(src: &Attributes) -> HashMap<&str, &str> {
|
||||
use Attributes::*;
|
||||
|
||||
match src {
|
||||
@ -148,7 +146,7 @@ impl Attributes {
|
||||
.zip(values.iter())
|
||||
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref())))
|
||||
.collect(),
|
||||
IndexMap(m) => m.iter().map(|(k, v)| (*k, v.as_ref())).collect(),
|
||||
IndexMap(m) => m.iter().map(|(k, v)| (k.as_ref(), v.as_ref())).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +231,8 @@ impl Apply for Attributes {
|
||||
},
|
||||
) if ptr_eq(new_k, old_k) => {
|
||||
// Double zipping does not optimize well, so use asserts and unsafe instead
|
||||
assert!(new_k.len() == new_v.len());
|
||||
assert!(new_k.len() == old_v.len());
|
||||
assert_eq!(new_k.len(), new_v.len());
|
||||
assert_eq!(new_k.len(), old_v.len());
|
||||
for i in 0..new_k.len() {
|
||||
macro_rules! key {
|
||||
() => {
|
||||
@ -263,8 +261,7 @@ impl Apply for Attributes {
|
||||
}
|
||||
// For VTag's constructed outside the html! macro
|
||||
(Self::IndexMap(new), Self::IndexMap(ref old)) => {
|
||||
let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
|
||||
Self::apply_diff_index_maps(el, new_iter, new, old);
|
||||
Self::apply_diff_index_maps(el, &*new, &*old);
|
||||
}
|
||||
// Cold path. Happens only with conditional swapping and reordering of `VTag`s with the
|
||||
// same tag and no keys.
|
||||
|
||||
@ -39,8 +39,9 @@ pub use self::vtag::VTag;
|
||||
pub use self::vtext::VText;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use std::borrow::Cow;
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt::Formatter;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, hint::unreachable_unchecked};
|
||||
@ -57,6 +58,7 @@ pub enum AttrValue {
|
||||
impl Deref for AttrValue {
|
||||
type Target = str;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
AttrValue::Static(s) => *s,
|
||||
@ -65,25 +67,36 @@ impl Deref for AttrValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for AttrValue {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_ref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: &'static str) -> Self {
|
||||
AttrValue::Static(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: String) -> Self {
|
||||
AttrValue::Rc(Rc::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<str>> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: Rc<str>) -> Self {
|
||||
AttrValue::Rc(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn from(s: Cow<'static, str>) -> Self {
|
||||
match s {
|
||||
Cow::Borrowed(s) => s.into(),
|
||||
@ -102,11 +115,18 @@ impl Clone for AttrValue {
|
||||
}
|
||||
|
||||
impl AsRef<str> for AttrValue {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for AttrValue {
|
||||
fn borrow(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AttrValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@ -117,6 +137,7 @@ impl fmt::Display for AttrValue {
|
||||
}
|
||||
|
||||
impl PartialEq for AttrValue {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_ref() == other.as_ref()
|
||||
}
|
||||
@ -292,7 +313,7 @@ pub enum Attributes {
|
||||
|
||||
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
|
||||
/// was not used to guarantee it.
|
||||
IndexMap(IndexMap<&'static str, AttrValue>),
|
||||
IndexMap(IndexMap<AttrValue, AttrValue>),
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
@ -303,7 +324,7 @@ impl Attributes {
|
||||
|
||||
/// Return iterator over attribute key-value pairs.
|
||||
/// This function is suboptimal and does not inline well. Avoid on hot paths.
|
||||
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'static str, &'a str)> + 'a> {
|
||||
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a> {
|
||||
match self {
|
||||
Self::Static(arr) => Box::new(arr.iter().map(|kv| (kv[0], kv[1] as &'a str))),
|
||||
Self::Dynamic { keys, values } => Box::new(
|
||||
@ -311,13 +332,13 @@ impl Attributes {
|
||||
.zip(values.iter())
|
||||
.filter_map(|(k, v)| v.as_ref().map(|v| (*k, v.as_ref()))),
|
||||
),
|
||||
Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (*k, v.as_ref()))),
|
||||
Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying `IndexMap`.
|
||||
/// If the attributes are stored in the `Vec` variant, it will be converted.
|
||||
pub fn get_mut_index_map(&mut self) -> &mut IndexMap<&'static str, AttrValue> {
|
||||
pub fn get_mut_index_map(&mut self) -> &mut IndexMap<AttrValue, AttrValue> {
|
||||
macro_rules! unpack {
|
||||
() => {
|
||||
match self {
|
||||
@ -331,7 +352,7 @@ impl Attributes {
|
||||
match self {
|
||||
Self::IndexMap(m) => m,
|
||||
Self::Static(arr) => {
|
||||
*self = Self::IndexMap(arr.iter().map(|kv| (kv[0], kv[1].into())).collect());
|
||||
*self = Self::IndexMap(arr.iter().map(|kv| (kv[0].into(), kv[1].into())).collect());
|
||||
unpack!()
|
||||
}
|
||||
Self::Dynamic { keys, values } => {
|
||||
@ -339,7 +360,7 @@ impl Attributes {
|
||||
std::mem::take(values)
|
||||
.iter_mut()
|
||||
.zip(keys.iter())
|
||||
.filter_map(|(v, k)| v.take().map(|v| (*k, v)))
|
||||
.filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
|
||||
.collect(),
|
||||
);
|
||||
unpack!()
|
||||
@ -348,8 +369,18 @@ impl Attributes {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
|
||||
fn from(v: IndexMap<AttrValue, AttrValue>) -> Self {
|
||||
Self::IndexMap(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexMap<&'static str, AttrValue>> for Attributes {
|
||||
fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
|
||||
let v = v
|
||||
.into_iter()
|
||||
.map(|(k, v)| (AttrValue::Static(k), v))
|
||||
.collect();
|
||||
Self::IndexMap(v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,7 +351,7 @@ impl VTag {
|
||||
pub fn add_attribute(&mut self, key: &'static str, value: impl Into<AttrValue>) {
|
||||
self.attributes
|
||||
.get_mut_index_map()
|
||||
.insert(key, value.into());
|
||||
.insert(AttrValue::Static(key), value.into());
|
||||
}
|
||||
|
||||
/// Sets attributes to a virtual node.
|
||||
@ -366,7 +366,7 @@ impl VTag {
|
||||
pub fn __macro_push_attr(&mut self, key: &'static str, value: impl IntoPropValue<AttrValue>) {
|
||||
self.attributes
|
||||
.get_mut_index_map()
|
||||
.insert(key, value.into_prop_value());
|
||||
.insert(AttrValue::from(key), value.into_prop_value());
|
||||
}
|
||||
|
||||
/// Add event listener on the [VTag]'s [Element](web_sys::Element).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user