如何在纯函数式 React 组件中处理事件

Posted

技术标签:

【中文标题】如何在纯函数式 React 组件中处理事件【英文标题】:How to handle events in pure functional React components 【发布时间】:2017-04-22 05:10:02 【问题描述】:

根据Cam Jackson,我们应该使用Redux,并编写小的、无状态的、函数式的组件。这是一个例子:

const ListView = (items) => (
    <ul>
        items.map(item => <ItemView item=item/>)
    </ul>
)

const ItemView = (item) => (
    <li><a>item.name</a></li>
)

现在,假设您要处理鼠标点击,并触发一个动作来获取被点击的项目。我认为这是一个非常好的方法:

const ListView = (items, onItemClicked) => 
    const _onClick = item => event => 
        event.preventDefault()
        onItemClicked(item)
    
    return (
        <ul>
            items.map(item => <ItemView item=item onClick=_onClick(item)/>)
        </ul>
    )


const ItemView = (item, onClick) => (
    <li><a onClick=onClick>item.name</a></li>
)

但是,根据Esa-Matti Suuronen,这是一种反模式,会导致性能下降。

显然,可以在ItemView 组件内处理事件,并注入item。但是,如果我们想避免在渲染函数中创建函数,那么我们需要将其设为类组件。

这是一种非常常见的模式,我想找到一种简单的方法来处理这个问题,使用功能组件,而不会导致性能问题。你有什么建议?

如果没有办法做到这一点,我将根本无法使用功能组件。因为我经常认为,在某些时候我需要将每个功能组件转换为一个类。

【问题讨论】:

您可以将_onClick 移到函数之外 - 这样,它仍然在范围内,但不会在每次组件呈现时重新初始化。也就是说,到那时将它变成一个类可能会更干净。 没有。你没有抓住重点。首先,你不能这样做,因为它引用了 onItemClicked 属性。当然你可以传入它。但关键是它每次都会创建一个新的 bound 函数。请注意,_onClick 是一个返回函数的函数(我猜它被称为高阶函数)。由于ItemView 每次都会获得一个新的道具,这将破坏“纯渲染”优化。问题是是否有办法在不使用类的情况下完成这项工作。 抱歉,我不应该在早上喝咖啡之前发表评论!在这种情况下,我不确定解决方案。 【参考方案1】:

在做了更多思考之后,我想我已经找到了方法。我意识到我们需要更多的 Redux 容器。不仅适用于主要的***组件,也适用于可重用的子组件。

上面的例子可以这样解决:

const ListView = (items, onItemClicked) => (
    <ul>
        items.map(item => <ItemContainer item=item onItemClicked=onItemClicked/>)
    </ul>
)

const ItemView = (item, onClick) => (
    <li><a onClick=onClick>item.name</a></li>
)

const mapDispatch = (dispatch, item, onItemClicked) => (
    onClick: (event) => 
        event.preventDefault()
        onItemClicked(item)
    
)

const ItemContainer = connect(null, mapDispatch)(ItemView)

在许多情况下,我们不需要将回调从外部组件传递给内部组件,因为我们将能够直接从ItemContainer 调度操作。

【讨论】:

这不是有同样的问题吗?每次调用 mapDispatch 时,都会向 onClick 属性传入一个新函数。 mapDispatch 在每次组件接收到新的 props 时被调用(在这种情况下是 item,假设 onItemClicked 没有改变)。在这种情况下,我们希望组件重新渲染,因为它代表一个新项目。在原始示例中,all ItemView 将在每次更改任何单个项目时重新呈现。【参考方案2】:

onItemClicked 函数作为ItemView 上的道具传递,并将其用作onClick 函数。在onItemClicked的实现中让它接收一个事件,并让item关闭该事件。

<ItemView item=item onItemClicked=onItemClicked/>

const ItemView = (item, onItemClicked) => (
    <li><a item=item onClick=onItemClicked>item.name</a></li>
)

【讨论】:

好的,感谢您的建议,但它实际上并没有回答如何将项目从活动中移除的问题。 const item = event.target.item 这不起作用,您会收到此错误: 标记上的 Unknown prop item。从元素中移除这个道具 但是,可以将引用作为数据属性存储在 DOM 中,例如data-item=item.id。这实际上是我们一直在做的事情。它仅支持字符串,因此您只能存储 ID 之类的内容,或将数据序列化为 JSON。我不太喜欢这个解决方案。

以上是关于如何在纯函数式 React 组件中处理事件的主要内容,如果未能解决你的问题,请参考以下文章

React面向组件编程 - 基本理解和使用 - 组件三大核心属性state-props-refs - 事件处理 - 非受控组件 - 受控组件 - 高阶函数 - 函数柯里化

React 组件中的事件处理组件(受控非受控)函数柯里化

React之事件处理之受控组件和非受控组件以及函数柯里化

React4.事件处理与类组件状态

React 事件处理

React事件处理