在 React 中实现双向数据绑定的性能影响

Performance implications of implementing 2-way data binding in React

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 设计注意事项。如果我 not 提供选择退出自动双向绑定的选项,则各个输入组件的设计可能会稍微简单一些。如果有人感兴趣,我可以 post 详细信息。

2 种方式的数据绑定影响

从你问题的第一部分开始,react 不支持双向数据绑定的主要原因有两个:

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

在 React 中,我们可以通过 lifting the state up to a common parent component. When a shared piece of state is updated, all the child components can update themselves. Here 在不同的子组件之间共享状态,这是与表单相关的文档中的一个很好的例子。

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

处理状态更新

来到你问题的第二部分,你的状态库向你的表单组件提供数据,导致任何依赖组件在状态变化时更新,亲爱的。以下是我的想法:

I was thinking about allowing individual fields to opt-out of that behavior in case the programmer was concerned about performance, but then I started wondering when this would ever be a real performance issue.

商店更新本身会非常快。 JavaScript 运行速度非常快,DOM 更新经常导致瓶颈。所以,除非同一页面上有数百个相关的表单元素并且所有元素都得到更新,否则你会没事的。

And let's suppose that I weren't using apollo but just calling a function that updates some global store directly - what would be the performance consideration then?

我认为不会有显着差异。

My thinking is that if a form is going to support immediately updating the form state as the user types in one field, it might as well do so for all the fields. The user can only type in one field at a time, and I don't see the benefit of making the page sometimes faster (probably negligibly) with some fields and sometimes slower with others.

同意这一点。

My library allows consumers to use whatever input components they want, so if the programmer just wants fewer state updates, they could just write a component that debounces React's onChange event or uses the browser's own change or blur event instead.

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

Is there some other reason why a user of my library would want to ignore changes for particular fields until the user submits the form? Or maybe a more useful option would be to ignore changes for the entire form (until submit)?

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

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

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

tl;博士

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