停止在 Props 中使用匿名函数!

好吧,标题有点吸引人。有时你需要使用匿名函数作为 props,但这可能比你想象的要少得多。但首先,让我们描述一下这个问题。

匿名函数作为 Props

在 Svelte 和 React 等组件库中,用作组件 props 的匿名函数已成为一种偷懒的做法,会导致更大规模的膨胀。

我看到很多开发人员都这样做:

而不是这样:

这些代码块中的每一个都将实现完全相同的结果。唯一的区别是,第一个示例中插入了一个匿名函数,它所做的只是调用一个不带参数的命名函数。

您知道为什么使用匿名方法可能会有问题吗?每次呈现此按钮时,都会创建一个全新的匿名函数并将其保存在内存中。如果您在同一页面上呈现了十个这样的函数,则内存中会保存十个不同的匿名函数。

现在你可能会说:“好吧,但是我多久会在一页上放置超过 10 或 20 个交互元素?”答案是:“你可能认为这是一个极端情况,但它出现的次数比你想象的要多。你最好养成良好的习惯,为这种极端情况做好准备!”

一个典型的例子就是,在具有交互功能的大型表格或列表中,可能会出现问题,例如删除或编辑行或项目。例如,如果您的页面上有一个数据网格类型的组件,并且您的分页为每页 100 行,那么每行创建的任何匿名函数都会出现 100 次。如果您有一个对每一行都执行某些操作的复选框,以及编辑和删除按钮,那么您现在在内存中保存了 300 个匿名函数,以及由匿名函数调用的原始 3 个命名和声明的函数。

如果为了提高效率而使用预渲染之类的花招,情况会更糟。在这个例子中,现在内存中有 600 个匿名函数实例。

好的,那么人们为什么这样做呢?

我至少可以想到两个原因来解释为什么这种习惯如此普遍和根深蒂固。

反应性或其他期望行为

至少在 Svelte 中,有时你需要这样做以确保函数被响应式调用。可能还有其他类型的情况需要你这样做才能使你的逻辑按预期工作,虽然我已经有几年没有使用 React 了,但我敢打赌那个库中也有一些类似的例子。

但这些都是您可以在编码时根据需要解决的边缘情况。

简化函数参数的传递

这可能是最常见的罪魁祸首。它允许您将某种状态直接传递给函数,因此当您第一次学习 Svelte 或 React 等 UI 库时,它更容易掌握和使用。这也许就是为什么大多数教程将匿名函数显示为 props。

以下是一个示例(在 Svelte 5 中):

// EditButton.svelte



const onEditToggle = (productId) => {
  // Do some stuff with productId...
}

简洁明了,对吧?但如果此页面上有数百个此按钮实例,则存在潜在的性能问题。我们如何重构以提高性能?

更简单的解决方案

编程中经常出现的情况是,一个简单直接的解决方案是确保我们为 EditButton 之类的东西创建单独的组件,然后将 productId 限定在该实例中。因此,我们不必将任何东西传递给我们的处理程序函数,因为它位于一个已经知道该 productId 是什么的离散组件内。这就是为什么如果代码更模块化而不是单片化,通常会更好。

如果可以的话,您绝对应该首先尝试这个解决方案。请注意,由于我们的组件只处理一个产品 ID,所以我们不需要那个匿名函数。相反,我们的组件函数可以直接处理它。

// EditButton.svelte



// This is all you really have to do in a component isolated like this!
const onEditToggle = () => {
  // Do some stuff with productId, because we already have it
  // as a prop in memory and scoped here.
}

更复杂的解决方案

但有时,由于系统架构或其他原因,我们无法将更新逻辑限制在本地范围内。为了处理这种情况,我们会做一些事情,是的,有点复杂,我不想这么说,比发送匿名函数作为 prop 稍微缺乏声明性。但它仍然非常易读,并且符合我们的目的,即不将一堆匿名函数推送到内存中。

它涉及在获取您正在处理的事件(例如点击)的 html 元素上使用数据属性。它看起来像这样:

// EditButton.svelte






// DataGridRow.svelte




  // ... other cells in the row
  



// DataGrid.svelte


{#each products as product}
  
{/each}

您看到那里发生了什么吗?我们将产品 ID 放入 EditButton 内 html 按钮元素上的自定义数据属性中。当处理程序函数触发时,它可以直接从元素的数据属性中检索产品 ID。

现在我们只有一个函数,onEditToggle,在内存中声明,其他所有内容都只是通过引用指向它。

可读性和性能之间的平衡

我个人认为,始终从模块化程度高的代码开始,这样就可以直接通过 props 将关键数据传递给该组件,而不是让组件成为单片组件,并必须在内部确定所有这些。这就是我在上面“更简单的解决方案”中描述的内容。

如果您确实不能做到这一点,那么请选择具有数据属性的第二种解决方案。

现在,您可能会争辩说,因为使用匿名函数比处理数据属性更具可读性,所以这是一个更好的开始,因此如果遇到性能问题,您可以稍后再进行调整。

我大体上同意这种思路,但在这种情况下,这样做的复杂性有点常见,以至于总是以同样的方式去做。这样你就不必考虑是否/何时使用一种方法而不是另一种方法。它们都有效,而且如果你发现有这些性能问题,其中一种就不需要重构了。

最后,一个警告

Svelte 完全有可能在转译过程中以某种方式处理这个问题,以某种方式平滑这些匿名函数。我对 Svelte 的运行时或编译器还不够了解,所以不能肯定地说。但我个人认为这是一种更安全的模式,适用于您可能最终使用的任何 JS 库,所以这是一个更好的习惯,应该提前养成。

您怎么看?您有反驳意见吗?或者也许可以深入了解一下 Svelte 在运行时和编译级别发生的事情,这可能会改变我的看法?请在评论中告诉我!