【问题标题】:Performance implications of implementing 2-way data binding in React在 React 中实现 2-way 数据绑定的性能影响
【发布时间】:2018-08-02 13:46:22
【问题描述】:

React 新手的一个常见问题是为什么双向数据绑定不是内置功能,通常的回答包括对单向数据流的解释以及双向数据绑定并不总是可取的想法出于性能原因。这是我想更详细了解的第二点。

我目前正在为apollo-link-state(来自 Apollo 的新客户端状态管理工具)开发一个表单库。这个概念与 redux-form 非常相似,只是使用 apollo-link-state 而不是 redux 作为状态管理器。 (请注意,表单状态与域实体的状态分开存储,尽管可以选择使用实体来填充表单的初始状态。)

当用户对表单进行更改时,库会立即通过onChange 处理程序更新存储。我正在考虑允许单个字段选择退出该行为,以防程序员担心性能,但后来我开始想知道这何时会成为真正的性能问题。无论如何,浏览器都会触发oninput 事件,所以我能想到的唯一性能考虑是商店是否会随着用户输入而更新。当然,执行突变会产生额外的开销,而不仅仅是调用setState(),但这实际上只是几个额外的函数调用。假设我没有使用 apollo,而只是调用一个直接更新一些全局存储的函数 - 那么性能考虑是什么?

我的想法是,如果表单要支持在用户在一个字段中键入时立即更新表单状态,那么它也可能对所有字段都这样做。用户一次只能输入一个字段,我看不到使页面有时在某些字段上更快(可能可以忽略不计)而有时在其他字段上变慢的好处。此外,我的库允许消费者使用他们想要的任何输入组件,所以如果程序员只是想要更少的状态更新,他们可以编写一个组件来消除 React 的 onChange 事件或使用浏览器自己的 changeblur 事件而是。

我在这里遗漏了什么吗?在用户提交表单之前,我的图书馆的用户是否希望忽略特定字段的更改还有其他原因?或者也许更有用的选项是忽略对整个表单的更改(直到提交)?

这是我当前方法背后的基本概念的基本(非常简化)说明:

// defined in a globally-accessible module
const formState = {
    // This somehow causes any dependent form components to re-render
    // when state changes
    update(formName, updatedState) {
        ...
    }
}
export default formState

...
// UserForm.js:

export default class UserForm extends PureComponent {
    componentDidMount() {
        formState.userForm = {
            firstName: '',
            lastName: '',
        }
    }

    handleChange(e) {
        const { target } = e
        formState.update('userForm', { [target.name]: target.value })
    }

    //...

    render() {
        const { userForm } = formState
        return (
            <form onSubmit={this.handleSubmit}>
                <label for="name">Name</label>
                <input id="name" type="text" onChange={this.handleChange} value={userForm.name} />

                <label for="email">Email</label>
                <input id="email" type="email" onChange={this.handleChange} value={userForm.email} />
            </form>
        )
    }
}

最后,为了完整起见,我应该提一下,其中还涉及一些 API 设计注意事项。如果我提供选择退出自动 2 向绑定的选项,则单个输入组件的设计可能会稍微简单一些。如果有人感兴趣,我可以发布详细信息。

【问题讨论】:

    标签: javascript performance reactjs flux


    【解决方案1】:

    2 路数据绑定含义

    从您问题的第一部分开始,不使用双向数据绑定的反应有两个主要原因:

    1. React 应用中数据更改的单一事实来源,因此出现错误的机会更少,调试更容易
    2. 性能优势

    在 React 中,我们可以通过lifting the state up 将不同子组件之间的状态共享给一个公共父组件。当一个共享的状态被更新时,所有的子组件都可以更新自己。 Here 是与表单相关的文档中的一个很好的例子。

    谈到性能优势,在其他一些上下文(比如 AngularJS)中,两种方式的数据绑定通过观察者观察不同的元素来工作。对于少量元素,这听起来更容易(并且比 React 的单向数据流的代码更少),但是随着 UI 组件/元素数量的增加,观察者的数量也会增加。在这种情况下,单个更改会导致许多观察者启动以保持同步。这使得性能有点迟钝。在 React 的情况下,由于只有一种数据流动方式,因此更容易确定哪些组件需要更新。

    处理状态更新

    来到您问题的第二部分,您的状态库将数据提供给您的表单组件,导致任何依赖组件在状态更改时更新,甜蜜。以下是我的想法:

    我正在考虑允许个别字段选择退出 程序员关心性能的情况下的行为,但是 然后我开始想知道这什么时候会是一场真正的表演 问题。

    商店更新本身会很快发生。 JavaScript 运行速度非常快,这是 DOM 更新经常导致瓶颈。因此,除非同一页面上有数百个相关的表单元素并且所有这些元素都在更新,否则您会没事的。

    假设我没有使用 apollo,而只是调用了 直接更新一些全局存储的功能 - 什么是 那么性能考虑呢?

    我认为这不会有显着差异。

    我的想法是,如果表单要支持立即更新 用户在一个字段中键入时的表单状态,它也可以这样做 对于所有领域。用户一次只能输入一个字段,并且 我看不到使页面有时更快的好处(可能 可以忽略不计)在某些字段中,有时在其他字段中较慢。

    同意这个。

    我的库允许消费者使用他们使用的任何输入组件 想要,所以如果程序员只想要更少的状态更新,他们可以 只需编写一个组件来消除 React 的 onChange 事件或使用 取而代之的是浏览器自己的 change 或 blur 事件。

    我认为大多数用例都可以通过简单的input 解决。同样,我在这里没有看到更少的状态更新带来的性能优势。例如,如果我在输入上运行 API 调用(并且希望在用户停止输入之前等待),去抖动可能会很有用。

    我的图书馆的用户是否还有其他原因想要 在用户提交表单之前忽略特定字段的更改? 或者也许更有用的选择是忽略 整个表单(直到提交)?

    我认为忽略特定字段的更改或等待提交没有任何好处。另一方面,在使用表单时,我遇到的一个常见用例是数据验证。例如,

    • 在用户创建密码时向他提供反馈
    • 检查电子邮件是否有效
    • 执行 API 调用以查看用户名是否有效等。

    这些情况需要在用户输入时更新状态。

    tl;博士

    您应该可以在用户键入时更新状态。如果您仍然担心性能,我建议profile your components 隔离瓶颈(如果有的话):)

    【讨论】:

    • 非常感谢您的详尽回答。一个问题是:“在这种情况下,一个单一的变化会导致很多观察者为了保持同步而兴奋起来。”这只是因为 Angular 是如何实现的,对吧?我的库在技术上也有很多“观察者”——每个输入组件都有自己的onChange 处理程序,以便在更新时通知表单(通过从this.props 获得的回调)——但它们非常简单,只有处理所有状态更新的表单上的一个handleChange 方法。
    • 所以这些onChange 处理程序基本上用作传递给父组件的事件,然后父组件对事件做出反应,这正是 React 的预期工作方式(我认为)。所以这应该会比 Angular 的实现带来更好的性能,对吧?
    • 无论如何,是的——如果它看起来很慢,我绝对应该在生产中使用它之前做一些分析,就像你建议的那样。
    • 不客气! AngularJS 通过脏检查实现了两种方式的绑定,这会导致触发一连串事件,以确定 UI 的哪些部分发生了变化。作为反应,您只更新了更新依赖子级的父级状态。也许反应文档可以更详细地解释这一点reactjs.org/docs/reconciliation.html :)
    • 感谢您的链接。听起来我的方法是合理的,应该是有效的,但总是很好理解细节:)
    猜你喜欢
    • 2011-03-22
    • 2015-07-28
    • 2014-01-06
    • 2019-02-20
    • 1970-01-01
    • 2019-04-03
    • 2018-11-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多