在 React.js 表单组件中使用 state 或 refs?

Posted

技术标签:

【中文标题】在 React.js 表单组件中使用 state 或 refs?【英文标题】:Use state or refs in React.js form components? 【发布时间】:2015-06-12 17:39:54 【问题描述】:

我从 React.js 开始,我想做一个简单的表单,但在文档中我发现了两种方法。

first one 正在使用 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>
    );
  
);

second one 在 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: ''
  );
);

如果存在的话,我看不出这两种选择的优缺点。 谢谢。

【问题讨论】:

我在这里遗漏了什么吗?为什么不使用事件对象来获取表单值?这似乎是首先在这里使用表格的唯一原因。如果您没有使用默认提交行为并且在输入上有引用,则不需要将它们包装在表单中。 【参考方案1】:

这篇文章很旧。

我将在这件事上分享我对一个案例的小经验。

我正在研究一个包含大量“动态”输入和大量缓存数据的大型组件(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)

]

多亏了这一点,没有重新渲染,输入流畅,功能以同样的方式工作。它将节省周期和计算,也可以节省能源。为地球做这件事x)

我的结论:甚至可能需要用于输入值的 useRef。

【讨论】:

【参考方案2】:

我看到一些人引用上述答案作为“从不使用 refs”的理由,我想给出我(以及我与之交谈过的其他一些 React 开发人员)的意见。

在谈论将它们用于组件实例时,“不要使用 refs”的观点是正确的。这意味着,您不应该使用 refs 作为获取组件实例并在其上调用方法的一种方式。这是使用 refs 的不正确方式,并且当 refs 快速向南时。

使用 refs 的正确(并且非常有用)的方法是当您使用它们从 DOM 中获取一些值时。例如,如果您有一个输入字段将 ref 附加到该输入,那么稍后通过 ref 获取值就可以了。如果没有这种方式,您需要经过一个相当协调的过程,以使您的输入字段与您当地的州或助焊剂商店保持同步——这似乎没有必要。

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

【讨论】:

你的最后一段很有道理,但你能澄清一下你的第二段吗?获取组件实例并调用被认为不正确的方法的具体示例是什么? 我同意这一点。我使用 refs 除非/直到我需要对字段的值进行验证或操作。如果我确实需要验证更改或以编程方式更改值,那么我使用状态。 我也同意这一点。在发现阶段,我故意天真地接近了一个包含大量输入的屏幕。存储在由 id 键入的映射(处于状态)中的所有输入值。不用说,由于设置状态和渲染 50 多个输入(一些 material-ui,它们很重!)在这样的微小 UI 更改上(如复选框单击并不理想),因此性能受到了影响。将每个可以保持其自身状态的输入组件化似乎是正确的方法。如果需要对账,只需查看refs 并获取状态值。实际上,这似乎是一个非常好的模式。 我完全同意。在我看来,接受的答案太模糊了。 我同意。在设计通用表单组件时,这暴露了受控组件和管理焦点、错误处理等的痛点。实际上不可能有一个干净的架构。如果需要,请与我交谈。我正在将我的组件移动到 refs。【参考方案3】:

TL;DR 一般来说,refs 与 React 的 declarative philosophy 相悖,所以你应该将它们作为最后的手段。尽可能使用state / props


要了解refsstate / props 在哪里使用,让我们看一下 React 遵循的一些设计原则。

每个反应 documentation 关于 refs

避免将 refs 用于任何可以以声明方式完成的事情。

Per React 关于Escape Hatches的设计原则

如果某些对构建应用程序有用的模式难以以声明性方式表达,我们将为它提供命令式 API。 (他们在这里链接到参考)

这意味着 React 的团队建议避免使用 refs 并使用 state / props 来处理可以以反应/声明方式完成的任何事情。

@Tyler McGinnis 提供了一个非常好的answer,并指出

使用 refs 的正确(并且非常有用)的方式是当您使用它们从 DOM 中获取一些值时......

虽然您可以这样做,但您将违背 React 的理念。如果你的输入有价值,它肯定来自state / props。为了保持代码的一致性和可预测性,您也应该坚持使用state / props。我承认refs 有时会为您提供更快的解决方案这一事实,因此如果您进行概念验证,又快又脏 是可以接受的。

这给我们留下了几个concrete use cases 用于refs

管理焦点、文本选择或媒体播放。 触发命令式动画。 与第三方 DOM 库集成。

【讨论】:

【参考方案4】:

简短版本:避免引用。


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

你有一个表格。您需要添加一个重置表单的按钮。

参考: 操作 DOM render 描述了表单在 3 分钟前的样子 状态 设置状态 render 描述了表单的外观

您的输入中有一个 CCV 编号字段,而您的应用程序中有一些其他字段是数字。现在你需要强制用户只输入数字。

参考: 添加一个 onChange 处理程序(我们不是使用 refs 来避免这种情况吗?) 如果不是数字,则在 onChange 中操作 dom 状态 您已经有一个 onChange 处理程序 添加 if 语句,如果无效则什么都不做 只有在要产生不同结果时才会调用渲染

呃,没关系,如果它无效,PM 想要我们只做一个红色框阴影。

参考: 让 onChange 处理程序只调用 forceUpdate 什么的? 根据...制作渲染输出是吧? 我们从哪里获得要在渲染中验证的值? 手动操作元素的 className dom 属性? 我迷路了 没有引用重写? 如果我们已安装,则从渲染中的 dom 读取,否则假定有效? 状态: 删除 if 语句 根据 this.state 进行渲染验证

我们需要将控制权交还给父级。数据现在在 props 中,我们需要对变化做出反应。

参考: 实现 componentDidMount、componentWillUpdate 和 componentDidUpdate 手动区分之前的props 用最少的更改来操作 dom 嘿!我们正在 react 中实现 react... 还有更多,但我的手指受伤了 状态: sed -e 's/this.state/this.props/' 's/handleChange/onChange/' -i form.js

人们认为 refs 比保持状态“更容易”。前 20 分钟可能是这样,但在我之后的经验中,情况并非如此。让你自己说“是的,我会在 5 分钟内完成”,而不是“当然,我会重写一些组件”。

【讨论】:

你能解释一下 sed -e 's/this.state/this.props/' 's/handleChange/onChange/' -i form.js 吗? 不,我的意思是对 dom 的实际更改。 React.findDOMNode(this.refs.foo)。如果你例如更改this.refs.foo.props.bar 不会发生任何事情。 例如如果您有 &lt;input onChange=this.handleChange value=this.state.foo /&gt; 将其更改为 &lt;input onChange=this.props.handleChange value=this.props.foo /&gt;,或修改您的 handleChange 函数以调用 props 中的回调。无论哪种方式,这都是一些明显的小变化。 不确定我是不是唯一一个觉得你的答案有点混乱的人。您能否展示一些代码示例,让您的观点更清楚? 在屏幕上有 50 多个输入,并且在任何状态更改时都进行渲染是不可取的。组件化每个维护自己状态的input 字段是理想的。在某些时候,我们确实需要用一些更大的模型来协调这些不同的独立状态。也许我们在计时器上有一个自动保存,或者我们只是保存在componentWillUnmount 这是我发现refs 理想的地方,在协调过程中,我们从每个ref 中提取state 值,没有一个是更明智的。我同意在大多数情况下state 是答案,但是对于大量的inputs,使用适当的refs 模式是一个性能提升

以上是关于在 React.js 表单组件中使用 state 或 refs?的主要内容,如果未能解决你的问题,请参考以下文章

React Js之组件

this.state.todos.map 不是函数,React.js

在 React.js 中更改父母的状态

我可以在 React.js 中更新组件的 props 吗?

React JS - 用动态子组件组成通用表单

react.js 时钟组件