You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Rust Yew的表格Demo鼠标滚轮滚动时出现内存泄漏求助

Yew表格组件滚轮滚动内存泄漏排查与修复

问题背景

提供的Rust代码基于Yew框架实现了分页表格组件:App生成测试数据传递给TestTable,TestTable通过Table组件渲染表头与分页表体,滚轮滚动时触发页码切换,渲染当前页数据。但滚动过程中出现内存泄漏问题。

泄漏原因分析

  1. Callback重复创建与引用滞留

    • Table组件的view方法中,每次渲染都会通过reform生成新的Callback闭包,原Callback的引用无法被GC回收。
    • TestTable每次更新页码时,都会重新创建传递给Table的onwheel Callback,多重闭包捕获形成长期引用链,导致旧组件实例无法释放。
  2. 不必要的数据克隆

    • 渲染表格行时,对data[index][n]调用to_owned()进行字符串克隆,每次渲染都会生成大量重复字符串,增加内存占用。
  3. 高频事件触发过度渲染

    • 滚轮滚动会触发大量事件,导致TestTable频繁更新状态、重新渲染,虚拟DOM的diff无法及时回收旧元素关联的资源。

修复方案

1. 简化Callback传递逻辑

修改Table组件,直接使用props传入的Callback,避免重复生成闭包:

#[derive(PartialEq, Properties)]
pub struct TableProps {
    onwheel: Callback<web_sys::WheelEvent>,
    children: Children,
}

pub struct Table;

impl Component for Table {
    type Message = ();
    type Properties = TableProps;

    fn create(ctx: &Context<Self>) -> Self {
        Self {}
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <table onwheel={ctx.props().onwheel.clone()}>
            <thead>
                <tr>
                    <th>{""}</th>
                    <th>{"HeadCell1"}</th>
                    <th>{"HeadCell2"}</th>
                    <th>{"HeadCell3"}</th>
                    <th>{"HeadCell4"}</th>
                    <th>{"HeadCell5"}</th>
                </tr>
            </thead>
            <tbody>
                {for ctx.props().children.iter()}
            </tbody>
            </table>
        }
    }
}

2. 复用Callback,避免重复创建

在TestTable的初始化阶段创建Callback,复用同一个实例:

pub struct TestTable {
    page: usize,
    per_page: usize,
    total: usize,
    onwheel_callback: Callback<web_sys::WheelEvent>,
}

impl Component for TestTable {
    type Message = ();
    type Properties = TestTableProps;

    fn create(ctx: &Context<Self>) -> Self {
        let onwheel_callback = ctx.link().callback(|_e: web_sys::WheelEvent| ());
        Self {
            page: 0,
            per_page: 16,
            total: ctx.props().data.len(),
            onwheel_callback,
        }
    }

    fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
        self.page += 1;
        if self.page > self.total / self.per_page {
            self.page = 0;
        }
        true
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        let mut rows = vec![];
        for i in 0..self.per_page {
            let index = i + self.page * self.per_page;
            if index >= self.total {
                break;
            }
            // 直接引用数据,避免不必要克隆
            let row_data = &self.data[index];
            rows.push(html! {
                <tr>
                    <td>{index}</td>
                    <td>{format!("{}--{}", row_data[0], index)}</td>
                    <td>{format!("{}--{}", row_data[1], index)}</td>
                    <td>{format!("{}--{}", row_data[2], index)}</td>
                    <td>{format!("{}--{}", row_data[3], index)}</td>
                    <td>{format!("{}--{}", row_data[4], index)}</td>
                </tr>
            });
        }

        html! {
            <Table onwheel={self.onwheel_callback.clone()}>
            {for rows}
            </Table>
        }
    }
}

3. 添加防抖处理,限制更新频率

通过防抖减少高频滚轮事件触发的组件更新:

use std::time::Duration;
use yew::callback::Callback;
use yew::utils::debounce::Debounce;

pub struct TestTable {
    page: usize,
    per_page: usize,
    total: usize,
    debounced_update: Debounce<Callback<()>>,
}

impl Component for TestTable {
    type Message = ();
    type Properties = TestTableProps;

    fn create(ctx: &Context<Self>) -> Self {
        let update_callback = ctx.link().callback(|_| ());
        let debounced_update = Debounce::new(Duration::from_millis(100), update_callback);
        Self {
            page: 0,
            per_page: 16,
            total: ctx.props().data.len(),
            debounced_update,
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let onwheel = ctx.link().callback(|_e: web_sys::WheelEvent| {
            self.debounced_update.emit(());
        });

        // 表格行渲染逻辑不变...

        html! {
            <Table onwheel={onwheel}>
            {for rows}
            </Table>
        }
    }
}

修复效果验证

  • 重复创建Callback的问题被解决,事件监听器资源能被正确回收
  • 数据引用优化减少了不必要的内存占用
  • 防抖处理降低了组件更新频率,缓解了内存压力

内容的提问来源于stack exchange,提问作者Chaos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 14:16:47