十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Yew 嵌套列表组件实战:用 `html_nested!` 约束子组件类型与 `WeakComponentLink` 实现跨层级通信

Yew 嵌套列表组件实战:用 `html_nested!` 约束子组件类型与 `WeakComponentLink` 实现跨层级通信 Yew 嵌套列表组件实战用html_nested!约束子组件类型与WeakComponentLink实现跨层级通信【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew导读本文基于 Yew 官方示例examples/nested_list展开系统讲解两个在真实项目中高频使用的组件工程技巧一是如何创建一个只接受特定子元素类型的容器组件通过html_nested!与VChild约束实现类型安全二是如何让非父子关系的组件之间建立通信通过WeakComponentLink间接获取目标组件的Scope。读完本文你将能够复刻一个带嵌套层级、hover 状态追踪与子项过滤的列表组件并理解其底层宏展开与虚拟 DOM 数据结构的运作方式。示例概述一个可嵌套、可追踪悬停状态的列表examples/nested_list/README.md将该示例定位为展示一个嵌套列表并实时显示最后一次被鼠标悬停的是哪个元素。运行时页面上会出现一个名为 Nested List Demo 的主列表包含标题栏Header与若干列表项Item其中名为 Rustifer 的列表项内部还嵌套了一层 Sub Rusties! 子列表子列表中包含由A..C生成的三个字母项。页面底部持续显示 Last hovered: 的当前状态文本。实现这一效果涉及 5 个源文件全部位于 examples/nested_list/src文件职责main.rs程序入口定义WeakComponentLink与Hovered枚举app.rs根组件App组装嵌套列表并维护 hovered 状态list.rs容器组件List持有VChildListHeader/VChildListItem子元素header.rs列表标题组件ListHeaderitem.rs列表项组件ListItem支持hide属性与嵌套 children运行方式与工程配置README 给出的启动方式非常简洁使用 Trunk 开发服务器trunk serve --open--open会让 Trunk 构建完成后自动在浏览器中打开页面。示例的工程配置与当前仓库的其余示例保持一致Cargo.toml 中yew以path ../../packages/yew方式引用本仓库内的包并启用csr客户端渲染feature同时依赖implicit-clone用于无 GC 环境下高效的隐式克隆见下文IArray、log与wasm-logger。index.html 声明link>fn main() { wasm_logger::init(wasm_logger::Config::default()); yew::Renderer::app::App::new().render(); }先初始化 wasm-logger 以便在浏览器控制台输出日志再通过Renderer::App::render()把根组件挂载到页面。核心概念一创建只接受特定子元素类型的组件问题背景Yew 的常规子元素写法是children: Children它接受任意Html。但在嵌套列表这类场景中我们希望List组件只接收ListHeader标题和ListItem列表项这两种子组件从而在编译期就排除把任意 HTML 塞进列表的错误。README 将其归纳为第一个概念Creating components which only accepts specific child elements。实现手法VChildThtml_nested!List的Props定义在 list.rs#[derive(Clone, PartialEq, Properties)] pub struct Props { #[prop_or_default] pub header: IArrayVChildListHeader, #[prop_or_default] pub children: IArrayVChildListItem, pub on_hover: CallbackHovered, pub weak_link: WeakComponentLinkList, }关键点在于字段类型使用了VChildComp。VChild是 Yew 虚拟 DOM 中的虚拟子组件结构定义于 packages/yew/src/virtual_dom/vcomp.rs#L178-L183pub struct VChildCOMP: BaseComponent { /// The component properties pub props: RcCOMP::Properties, /// Reference to the mounted node key: OptionKey, }它把某个具体组件类型COMP的Properties包装在Rc中并携带可选的key。因此VChildListHeader意味着该字段只能存放ListHeader组件实例类型在编译期即被固定。此外VChild还提供了get_mut()vcomp.rs#L224-L226通过Rc::make_mut返回属性可变引用这是后面动态改写子项名称功能的基础。IArray来自implicit-clone包是一种适合在 WASM 目标上使用的不可变数组类型配合#[prop_or_default]使header与children在缺省时自动为空数组。光有类型还不够——普通html!宏把子节点编译为通用 HTML/VNode无法产出带类型的VChild。因此示例在组装子元素时使用了html_nested!宏如 app.rs// note the use of html_nested! instead of html!. let letters (A..C) .map(|letter| html_nested! { ListItem key{format!(letter-{}, letter)} name{letter.to_string()} {on_hover} / });html_nested!的语法与html!几乎一致但专门用于生成VChildComp宏根据标签名解析出目标组件类型校验 props 是否匹配该组件的Properties从而在编译期保证放入header/children集合的每个元素类型正确。宏实现在 packages/yew-macro/src 中html_tree/html_block.rs 负责解析带花括号的嵌套块最终展开为VChild::new(props, key)调用。动态改写子项属性VChild::get_mutList::view在渲染 children 时除了过滤隐藏项还动态为每个可见项重写显示名list.rsfor (i, mut c) in ctx .props() .children .iter() .filter(|c| !c.props.hide) .cloned() .enumerate() { let props c.get_mut(); props.name format!(#{} - {}, i 1, props.name).into(); { c } }流程拆解iter()遍历IArrayVChildListItemfilter(|c| !c.props.hide)过滤掉hidetrue的项示例中 Rustaroo 与 Hidden Sub 因此不会渲染这正是 README Improvements 小节提到的静态隐藏逻辑cloned()复制VChildVChild的克隆只复制RcProperties句柄与 key开销极低见 vcomp.rs#L187-L194c.get_mut()拿到可变属性引用把name改写为#序号 - 原名称最后{ c }把处理过的VChild渲染为实际组件。这种先过滤、再按渲染位次改写 props的模式展示了VChild作为可编程子组件句柄的威力父组件可以在渲染前统一调整子组件的输入而不必为每种变化都声明回调。核心概念二与非父级组件通信 ——WeakComponentLink问题背景页面布局中标题栏ListHeader被放在List的header集合里而 Last hovered 文本显示在根组件App中。用户点击标题栏时需要让List切换inactive失活状态——但ListHeader与List并不是父子关系ListHeader只是List渲染树中的内容它的Scope并不嵌套在List之下。README 将此列为第二个概念Communicating with a component that isnt a parent. SeeWeakComponentLink。常规做法是把List提升为App的状态由App统一管理回调但本示例演示了一种更贴近引用式通信的方案——通过一个可共享的句柄WeakComponentLinkList让任意后代组件拿到List的Scope直接向它发送消息。WeakComponentLink的实现main.rs 完整实现了这个辅助类型pub struct WeakComponentLinkCOMP: Component(RcRefCellOptionScopeCOMP); implCOMP: Component Clone for WeakComponentLinkCOMP { fn clone(self) - Self { Self(Rc::clone(self.0)) } } implCOMP: Component ImplicitClone for WeakComponentLinkCOMP {} implCOMP: Component Default for WeakComponentLinkCOMP { fn default() - Self { Self(Rc::default()) } } implCOMP: Component Deref for WeakComponentLinkCOMP { type Target RcRefCellOptionScopeCOMP; fn deref(self) - Self::Target { self.0 } } implCOMP: Component PartialEq for WeakComponentLinkCOMP { fn eq(self, other: Self) - bool { Rc::ptr_eq(self.0, other.0) } }设计要点内部结构RcRefCellOptionScopeCOMP。Option表示目标组件可能尚未挂载RefCell允许在List::create阶段写入、在渲染阶段读取Rc让多个持有者共享同一份句柄。共享性Clone只克隆Rc指针复制成本为 O(1)并通过实现ImplicitClone让它可以作为普通 props 直接传递Yew 的Properties派生宏对实现ImplicitClone的类型有零拷贝优化。弱的含义这里弱不是指Weak指针的内存语义而是指链接在目标组件创建前可能为空None因此使用方必须处理目标尚不存在的分支——与强耦合的直接引用相比这种间接层让组件组合更灵活。PartialEq通过Rc::ptr_eq比较底层指针保证同一份链接判定为相等适合用作Properties的相等性比较。注册与消费链路注册写入发生在List::create即列表组件实例化时把自身的Scope填进链接list.rsfn create(ctx: ContextSelf) - Self { ctx.props() .weak_link .borrow_mut() .replace(ctx.link().clone()); Self { inactive: false } }weak_link由App在创建List时通过 prop 传入list_link/sub_list_link两个句柄在 app.rs 中各自WeakComponentLink::default()初始化。消费读取发生在ListHeader::viewheader.rslet list_link ctx.props().list_link.borrow().clone().unwrap(); let onmouseover ctx.props().on_hover.reform(|e: MouseEvent| { e.stop_propagation(); Hovered::Header }); html! { div classlist-header {onmouseover} onclick{list_link.callback(|_| ListMsg::HeaderClick)} { ctx.props().text } /div }标题栏点击时通过list_link.callback(|_| ListMsg::HeaderClick)把事件转成目标组件List的消息发送出去——这正是向非父级组件发送消息的实现Scope::callback会构造一个把事件转换为COMP::Message并投递到该Scope消息队列的回调。List收到Msg::HeaderClick后切换inactivelist.rs界面随之半透明化对应styles.scss中的.list.inactive。fn update(mut self, _ctx: ContextSelf, msg: Self::Message) - bool { match msg { Msg::HeaderClick { self.inactive !self.inactive; true } } }注意此处unwrap()因为List一定先于其 header 内容挂载List::create早于ListHeader::view所以读取时必然已有值实际项目中若挂载顺序不确定应改用if let Some(link)的防御式写法。与on_hover回调的分工除了WeakComponentLink这条命令式通道hover 状态的传递走的是传统回调上抛通道App构造on_hover回调ctx.link().callback(Msg::Hover)层层传给List、ListHeader、ListItem各子组件用Callback::reform把MouseEvent转换成Hovered枚举值如 item.rs 把事件映射为Hovered::Item(name)并调用e.stop_propagation()阻止事件继续冒泡到外层容器从而保证最后悬停的是精确的最内层元素。两种模式互补向上用Callback报告事件、由根组件统一收敛状态向下/跨层用WeakComponentLink直接驱动目标组件内部状态。这正是嵌套组件通信的两种典型姿势。状态模型Hovered枚举与显示Hovered定义于 main.rs用来精确描述最后悬停的目标#[derive(Debug, Clone, ImplicitClone, PartialEq, Eq, Hash)] pub enum Hovered { Header, Item(AttrValue), List, None, }Header悬停在标题栏Item(AttrValue)悬停在某个列表项携带该项名称List悬停在列表容器子列表内部区域None悬停在页面空白处。Hovered实现了fmt::Displaymain.rs#L51-L64把各变体映射为 Header、Nothing 等可读文本随后实现IntoPropValueHtml for Hoveredmain.rs#L66-L70使{ self.hovered }可以直接渲染进html!模板。App中对应的事件分发逻辑在 app.rs主容器div.main的onmouseover把一切未被stop_propagation拦截的悬停重置为Hovered::None子列表区域则专门映射为Hovered::List。Last hovered 显示区由App::view_last_hovered渲染app.rs#L103-L112文本样式为.last-hovered-text灰色小字见 styles.scss#L82-L89。组件树全景与改进空间综合以上源码示例的组件/事件流可概括为App持有两个WeakComponentLinkList主列表与子列表各一与Hovered状态List通过header: IArrayVChildListHeader与children: IArrayVChildListItem精确约束子元素类型ListItem支持hide: bool、name: AttrValue、on_hover与任意childrenListItem::view_details会把非空 children 包进.list-item-details容器子列表正是由此嵌入了 Rustifer 项内部子列表自身又是List形成任意深度的递归嵌套结构悬停事件自内向外冒泡每层reform后stop_propagation保证最内层语义胜出。README 的 Improvements 小节指出一个明确的后续方向ListItem的hide属性目前是静态写死的Rustaroo、Hidden Sub 永远不可见理想情况下应提供按钮交互让用户动态切换隐藏项的可见性——这需要把hide从静态 prop 提升为可变状态如提升到List内部由消息驱动感兴趣可以将其作为练习实现。小结通过examples/nested_list这个不到 200 行的完整示例我们掌握了三件可直接迁移到业务代码中的能力用VChildComphtml_nested!构造类型安全的子元素集合让容器组件在编译期锁定允许的子组件类型并用get_mut()在渲染前动态改写子组件 props用WeakComponentLinkComp基于RcRefCellOptionScope让非父子组件通过Scope::callback直接向目标组件发送消息实现跨层级的命令式通信用Callback::reformstop_propagation构建精确到最内层的 hover 事件追踪模型。这三项技术分别对应 Yew 的宏展开yew-macro 的 html_tree、虚拟组件句柄vcomp.rs与作用域消息投递Scope机制是阅读 Yew 源码、编写复杂组件树时的理想切入点。【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表