在React.js表单组件中,使用状态(state)还是引用(refs)?

137

我正在学习 React.js,想制作一个简单的表单,在文档中找到了两种做法。

第一种是使用 Refs(参考链接)

var CommentForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var author = React.findDOMNode(this.refs.author).value.trim();
    var text = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    // TODO: send request to the server
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value = '';
    return;
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

第二个是在React组件内部使用state

var TodoTextInput = React.createClass({
  getInitialState: function() {
    return {
      value: this.props.value || ''
    };
  },

  render: function() /*object*/ {
    return (
      <input className={this.props.className}
      id={this.props.id}
      placeholder={this.props.placeholder}
      onBlur={this._save}
      value={this.state.value}
      />
    );
  },

  _save: function() {
    this.props.onSave(this.state.value);
    this.setState({value: ''
  });
});

如果存在某些优缺点的话,我无法看清这两个选择的优缺点。谢谢。


我在这里漏掉了什么吗?为什么不使用事件对象来获取表单值呢?这似乎是首先使用表单的唯一原因。如果您没有使用默认提交行为并且在输入上具有引用,则无需将其包装在表单中。 - Russell Ormes
4个回答

151

简短版:避免使用 refs。


它们对可维护性不利,并且失去了所提供的所见即所得模型渲染的许多简单性。

你有一个表单。你需要添加一个按钮,用于重置该表单。

  • refs:
    • 操纵 DOM
    • 渲染描述了3分钟前表单的外观
  • state
    • setState
    • 渲染描述了表单的外观

你的应用程序中有一个CCV码输入字段以及其他一些数字字段。现在你需要强制用户只输入数字。

  • refs:
    • 添加一个onChange处理程序(我们不是使用refs来避免这个吗?)
    • 如果不是数字,在onChange中操作dom
  • state
    • 您已经有了一个onChange处理程序
    • 添加一个if语句,如果无效则不执行任何操作
    • 只有当其产生不同结果时,才调用render

额,算了吧,PM想让我们只要它无效就做一个红色的box-shadow。

  • refs:
    • 使 onChange 处理程序只调用 forceUpdate 或其他什么东西?
    • 使 render 基于...啥?
    • 在渲染中从哪里获取要验证的值?
    • 手动操作元素的 className dom 属性?
    • 我迷失了
    • 没有 refs 重写吗?
    • 如果已装载,则在渲染中读取dom,否则假定它有效?
  • state:
    • 删除 if 语句
    • 使 render 根据 this.state 进行验证

我们需要将控制权交还给父组件。数据现在在props中,我们需要对更改做出反应。

  • refs:
    • 实现 componentDidMount,componentWillUpdate 和 componentDidUpdate
    • 手动比较之前的 props
    • 用最小的变化操作DOM
    • 嘿!我们正在用React实现React...
    • 还有更多,但我的手指疼了
  • 状态:
    • sed -e 's/this.state/this.props/' 's/handleChange/onChange/' -i form.js

人们认为使用Refs比将它保存在状态中更容易。这可能在前20分钟内是正确的,在我的经验中,这并不正确。让自己处于可以说“是的,我会在5分钟内完成它”的位置,而不是“当然,我只需要重写几个组件”。


3
可以稍微解释一下sed -e 's/this.state/this.props/' 's/handleChange/onChange/' -i form.js吗? 这个命令是在form.js文件中,将所有的"this.state"替换为"this.props",将所有的"handleChange"替换为"onChange"。"-i"选项表示直接修改原始文件。 - gabrielgiussi
1
例如,如果您有<input onChange={this.handleChange} value={this.state.foo} />,请将其更改为<input onChange={this.props.handleChange} value={this.props.foo} />,或修改您的handleChange函数以调用props中的回调。无论哪种方式,都是一些小而明显的更改。 - Brigand
1
如果编写多字段表单的onChange函数很麻烦,可以使用React.addons.LinkedStateMixin高阶函数也是一种选择。 - kolobos
4
不确定是否只有我觉得你的回答有些令人困惑。你能否展示一些代码样例来更清楚地说明你的观点? - Rishabh
2
在屏幕上有50多个输入并在任何状态更改时呈现每个输入是不可取的。将每个“input”字段组件化,其中每个字段都维护其自己的状态是理想的。在某些时候,我们确实需要将这些各自独立的状态与一些较大的模型协调一致。也许我们有一个定时器上的自动保存,或者我们只是在componentWillUnmount上保存。这就是我发现refs理想的地方,在协调一致性期间,我们从每个ref中提取state值,而没有人会知道。我同意在大多数情况下,state是答案,但是对于大量的“inputs”,利用适当的refs模式可以提高性能。 - lux
显示剩余10条评论

123

我看到一些人引用上面的回答作为“永远不要使用ref”的理由,我想给出我的(以及我与几位React开发人员交谈后得出的)观点。

当谈论到使用ref来获取组件实例并调用它们的方法时,“不要使用refs”这种感觉是正确的。这是错误使用ref的方式,并且可能会导致ref迅速失效。

使用ref的正确(并且非常有用)的方式是,当您使用它们从DOM获取某些值时。例如,如果您有一个输入字段并将ref附加到该输入,则稍后通过ref获取该值就可以了。如果没有这种方式,您需要经过相当协调的流程来使您的输入字段与本地状态或flux存储保持同步,这似乎是不必要的。

2019编辑:你好未来的朋友。除了我几年前提到的内容之外,使用React Hooks,ref也是跟踪渲染之间数据的好方法,并且不仅限于抓取DOM节点。


3
你最后的段落非常清晰明了,但你能否澄清一下第二段?能否给出一个具体的例子,说明抓取组件实例并调用方法会被视为不正确的情况是什么?答:你的最后一段非常合理,但你能否澄清第二段?如果抓取组件实例并调用方法的具体示例不正确,可以说明是什么? - Daynil
2
我同意这个观点。除非需要验证或操作字段的值,否则我会使用 refs。如果我确实需要在更改时进行验证或编程更改值,则使用 state。 - Christopher Davies
1
我也同意这一点。在发现阶段,我故意以天真的态度接近一个有大量输入的屏幕。所有输入值都存储在一个映射中(状态中),由 id 键控。不用说,性能受到了影响,因为在像复选框点击这样的小型 UI 更改时设置状态并呈现 50 多个输入(其中一些是较重的材料-UI!)并不理想。每个可以维护自己的状态的输入组件化似乎是正确的方法。如果需要对比,只需查看 'refs' 并获取状态值。实际上,这似乎是一种非常好的模式。 - lux
2
我完全同意。在我看来,被接受的答案过于模糊。 - James Wright
我同意。在设计通用的表单组件时,这凸显了受控组件和管理焦点、错误处理等方面的痛点。实际上不可能有一个清晰的架构。如果需要,请与我联系。我正在将我的组件移动到 refs。 - HalfWebDev

16

这篇文章已经有些年头了。

我将分享一个关于这个问题的小经验。

我在处理一个大组件(414行)时,涉及很多“动态”的输入和缓存数据。 (我不是独自在页面上工作,我的感觉告诉我代码结构可能可以更好地拆分,但这不是重点(嗯,它可能是,但是我正在处理它)。

我首先使用state来处理输入的值:

  const [inputsValues, setInputsValues] = useState([])
  const setInputValue = (id, value) => {
    const arr = [...inputsValues]
    arr[id] = value
    setInputsValues(arr)
  }

当然还有在输入中:

value={inputsValues[id] || ''}
onChange={event => setInputValue(id, event.target.value)}

渲染过重导致输入变化不流畅(不要试图一直按住键,文本只会在暂停后出现)。

我确信可以使用 refs 避免这种情况。

最终效果如下:

  const inputsRef = useRef([])

并且在输入中:

ref={input => (inputsRef.current[id] = input)}

在我的情况下,输入是Material-UI的TextField组件,因此代码如下:

inputRef={input => (inputsRef.current[id] = input)}

感谢这个特性的存在,不需要重新渲染,输入非常顺畅,功能也能够正常工作。这将节约一定的计算时间和能源。为了地球,请使用它吧!我的结论是:对于输入值,甚至可能需要使用useRef。

我曾经遇到过同样的问题,但是我使用了 useReducer 钩子来解决它。https://blog.logrocket.com/react-usereducer-hook-ultimate-guide/ 虽然我尊重您的意见,但我仍然会避免使用 refs... 问题在于一旦引用被使用,人们就会随意使用它们,甚至不理解它们的真正目的,从而导致各种问题... - Denis
@Kaphar 我喜欢这个想法。我也正在开发一个带有大型表单和状态列表的同类型功能。目前我正在使用第一个值/onChange方法,并且遇到了相同的渲染问题。我只是想知道useRef是否能解决这个问题。如果考虑在某些输入更改时要计算一些总数并显示,那该怎么办呢?有什么建议吗?谢谢! - Utsav Patel

8
一般来说,refs与React的声明式哲学相抵触,因此应将其作为最后的选择。尽可能使用state / props
为了理解何时应该使用 refs 和何时使用 state / props,让我们看一下 React 遵循的一些设计原则。
根据 React 文档 关于 refs

避免使用 refs 来完成可以通过声明式实现的任何事情。

根据 React 的设计原则中关于 逃生舱口 的说明:

如果某种对于构建应用程序有用的模式难以以声明式方式表达,我们将为其提供命令式 API。(并在此处链接到 refs)

这意味着 React 团队建议避免使用 refs 并在可以以响应式/声明式方式完成的任何情况下使用 state / props
@Tyler McGinnis 提供了一个非常好的 答案,也指出:
正确(而且非常有用)使用 refs 的方法是在从 DOM 中获取某些值时使用它们...虽然你可以这样做,但这将违反 React 的哲学。如果输入框中有值,则它肯定来自于 state/props。为了保持代码一致和可预测性,你应该在那里坚持使用 state/props。我承认 refs 有时会给你更快的解决方案,因此如果你进行概念验证,"快速而肮脏"是可以接受的。
这使我们留下了几个 refs 的 具体用例
管理焦点、文本选择或媒体播放。 触发命令式动画。 与第三方 DOM 库集成。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接