如何使用ReactJS获取输入字段的值?

318

我有以下的React组件:

export default class MyComponent extends React.Component {

    onSubmit(e) {
        e.preventDefault();
        var title = this.title;
        console.log(title);
    }

    render(){
        return (
            ...
            <form className="form-horizontal">
                ...
                <input type="text" className="form-control" ref={(c) => this.title = c} name="title" />
                ...
            </form>
            ...
            <button type="button" onClick={this.onSubmit} className="btn">Save</button>
            ...
        );
    }

};

控制台显示undefined - 有什么想法这个代码有什么问题吗?


10
this.onSubmit.bind(this) 的意思是绑定当前的 thisonSubmit 方法上。 - zerkms
好的 - 您想将其添加为答案,我会勾选它吗? - JoeTidee
4
没有绑定时,e.target.value 怎么样? - omarjmh
1
e.target.value不是指向按钮而不是输入字段吗? - JoeTidee
当点击提交按钮(即 onClick={this.onSubmit.bind(this)})时,您需要将 onSubmit 方法绑定到提交按钮(DOM 元素)。如果您想访问表单中标题输入的值,可以使用 onSubmit(event) { const title = event.target.elements.title.value; } - tim-montague
显示剩余3条评论
18个回答

513

这里有三个答案,取决于你使用的React版本,以及你是否想要使用hooks。

首先:

重要的是要理解React的工作原理,这样你才能正确地做事情(专业提示:非常值得在React网站上运行React教程。它写得很好,并以实际解释如何做事情的方式涵盖了所有基础知识)。在这里,“正确”意味着你不是在编写网页,而是在编写应用程序的用户界面,该应用程序恰好在浏览器中呈现;所有实际的用户界面工作都发生在React中,而不是“从编写网页中习惯的内容”(这就是为什么React应用程序真正是“应用程序”,而不是“网页”的原因)。

React应用程序基于两个东西进行渲染:

  1. 由创建该组件实例的任何父级声明的组件属性,父级可以在其生命周期内修改这些属性,
  2. 该组件自己的内部状态,它可以在自己的生命周期内自行修改。

当你使用React时,你明确地不是在生成HTML元素并使用它们:例如,当你告诉React使用一个<input>时,你并没有创建一个HTML输入元素,而是告诉React创建一个React输入对象,在编译React应用程序为Web时呈现为HTML输入元素,并由React控制事件处理。

使用React时,你正在生成应用程序UI元素,向用户展示(通常是可操作的)数据,用户交互会以你定义的方式改变应用程序的状态 - 用户执行的操作可能会更新组件的props或state,React将其用作信号来为已更改的组件生成新的UI表示形式,这可能会导致应用程序界面的部分更新以反映新状态。

在这种编程模型中,应用程序的内部状态是最终的权威,而不是“用户查看和交互的UI”:如果用户尝试在输入字段中键入某些内容,而您没有编写任何处理该操作的内容,则不会发生任何事情:UI反映了应用程序状态,而不是相反。实际上,在这种编程模型中,浏览器DOM几乎是一个想法:它只是一个超级便捷的UI框架,整个地球都几乎可以访问(但React知道如何使用的不仅仅是它)。
一个具体的例子
那么,有了这个理解,让我们看看用户如何与React中的输入元素进行交互。首先,我们需要获得一个UI元素,供用户进行交互。
你编写了一个组件来管理用户的一些字符串数据,包括存储和展示,还有一个onChange函数用于处理用户数据。React使用你组件的渲染代码生成一个虚拟DOM,其中包含一个input组件(不是DOM的<input>元素),并将你的onChange处理程序绑定到该组件上,以便可以使用React事件数据调用它(请注意,这不是DOM的change事件监听器,并且不会获得与常规DOM事件监听器相同的事件数据)。然后,React库将该虚拟DOM转换为用户可以交互的UI,并在应用程序状态更改时更新它。由于它在浏览器中运行,因此构建了一个HTML输入元素。
然后,你的用户尝试实际与该输入元素进行交互:
1. 您的用户点击输入元素并开始输入。 2. 然而,此时输入元素什么也不会发生。相反,React拦截了输入事件并立即终止。 3. React将浏览器事件转换为React事件,并使用React事件数据调用虚拟DOM组件的onChange函数。 4. 根据您编写的方式,该函数可能会执行某些操作,在这种情况下,您几乎肯定编写了该函数以使用用户(尝试)输入更新组件的状态。 5. 如果安排了状态更新,则React将在不久的将来运行该状态更新,然后触发更新后的呈现过程。 6. 在呈现过程中,它会检查状态是否实际上有所不同,如果是,则生成临时第二个虚拟DOM,将其与应用程序的虚拟DOM的(一部分)进行比较,确定需要对应用程序的虚拟DOM执行哪个添加/更新/删除操作,以使其看起来与新的临时虚拟DOM相同,然后应用这些操作并再次丢弃临时虚拟DOM。 7. 然后更新UI以反映虚拟DOM的最新外观。 8. 经过所有这些步骤后,我们终于在用户实际查看的页面上获得了更新的DOM,并且他们可以看到他们在输入元素中输入的内容。
这与常规的浏览器模型完全不同:用户不是先通过在文本框中输入来更新UI数据,然后我们的代码读取“该文本框的当前值”以确定状态,而是React已经知道状态,并使用事件来首先更新状态,然后导致UI更新。
重要的是要记住,所有这些都发生在瞬间,所以对于用户来说,它看起来像他们在输入元素中键入文本,就像在任何随机网页上一样,但在底层,事情却完全不同,但仍然会导致相同的结果。
因此,在这方面得到覆盖后,让我们看一下如何从React元素中获取值:
组件类和ES6(React 16+和15.5过渡)。

从React 16开始(并在15.5中进行软启动),不再支持createClass调用,需要使用类语法。这会改变两件事情:明显的类语法,还有createClass可以“免费”执行的this上下文绑定,因此请确保您在onWhatever处理程序中使用“fat arrow”符号来保留匿名函数中的this上下文,例如我们在此处使用的onChange

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.reset();
  }

  reset() {
    // Always set the initial state in its own function, so that
    // you can trivially reset your components at any point.
    this.state = {
      inputValue: ''
    };
  }

  render() {
    return (
      // ...
      <input value={this.state.inputValue} onChange={evt => this.updateInputValue(evt)}/>
      // ...
    );
  },

  updateInputValue(evt) {
    const val = evt.target.value;
    // ...       
    this.setState({
      inputValue: val
    });
  }
});

您可能也看到过有人在构造函数中使用bind来处理所有的事件处理函数,像这样:

constructor(props) {
  super(props);
  this.handler = this.handler.bind(this);
  ...
}

render() {
  return (
    ...
    <element onclick={this.handler}/>
    ...
  );
}

不要这样做。

几乎任何时候使用bind,都适用谚语“你做错了”。您的类已经定义了对象原型,因此已经定义了实例上下文。不要在其上方放置bind;使用普通事件转发代替在构造函数中复制所有函数调用,因为该复制增加了错误面,使得跟踪错误变得更加困难,因为问题可能出现在构造函数中而不是在调用代码的地方。

“但它会在重新渲染时不断创建和丢弃函数!”这可能是真的,但您不会注意到。您的用户也不会。如果事件处理程序垃圾收集是性能瓶颈,则已经出现了很多问题,您需要停下来重新思考设计:React之所以运行得如此出色,是因为它不会更新整个UI,而只会更新更改的部分,在设计良好的UI中,大部分UI的时间都不会发生剧烈变化,只有小部分UI的时间会花费在更新上。

带有钩子的函数组件(React 16.8+)

自React 16.8起,函数组件(即只是一个以props为参数的函数,可以像使用组件类的实例一样使用,而无需编写类)也可以通过使用hooks来获得状态。
如果您不需要完整的类代码,而单个实例函数就足够了,那么现在可以使用useState hook来获取单个状态变量及其更新函数,其工作方式与上面的示例大致相同,但没有"通用"的setState函数调用,并且对于您正在处理的每个值,使用一个专用的状态设置器。
import { useId, useState } from 'react';

function myFunctionalComponentFunction(props) {
  const id = useId();
  const [input, setInput] = useState(props?.value ?? '');
  return (
    <div>
    <label htmlFor={id}>Please specify:</label>
    <input id={id} value={input} onInput={e => setInput(e.target.value)}/>
    </div>
  );
}

之前,非官方的类组件和函数组件之间的区别是“函数组件没有状态”,所以我们不能再依靠这一点了:函数组件和类组件之间的区别可以在非常好写的React文档中找到(没有捷径的一行解释可以方便地曲解!),您应该阅读它以便知道自己在做什么,从而知道是否选择了最佳(无论对您来说意味着什么)解决方案,以摆脱您正在遇到的问题。

使用传统ES5和createClass的React 15及以下版本

为了正确地执行操作,您的组件必须具有状态值,该状态值通过输入字段显示,并且我们可以通过使UI元素将更改事件发送回组件来更新它:

var Component = React.createClass({
  getInitialState: function() {
    return {
      inputValue: ''
    };
  },

  render: function() {
    return (
      //...
      <input value={this.state.inputValue} onChange={this.updateInputValue}/>
      //...
    );
  },

  updateInputValue: function(evt) {
    this.setState({
      inputValue: evt.target.value
    });
  }
});

因此,我们告诉 React 使用 updateInputValue 函数来处理用户交互,使用 setState 来安排状态更新。当 render 在更新状态后重新渲染时,由于它利用了 this.state.inputValue,所以用户将看到基于他们键入的更新文本。 根据评论添加的说明 鉴于 UI 输入代表状态值(考虑如果用户在中途关闭选项卡并恢复选项卡会发生什么。是否应该恢复他们填写的所有值?如果是这样,那就是状态),可能会让您感到一个大型表单需要有成十甚至数百个输入字段,但React关注于以可维护的方式建模您的UI:您没有100个独立的输入字段,而是一组相关的输入,因此您将每个组捕获在组件中,然后将您的“主”表单构建为组合组。
MyForm:
  render:
    <PersonalData/>
    <AppPreferences/>
    <ThirdParty/>
     ...

这种方式比一个庞大的单一表单组件更易于维护。将组分拆为具有状态维护的组件,其中每个组件仅负责跟踪少量输入字段。
你可能觉得编写所有这些代码“麻烦”,但这是假的节约:对于不是你自己的开发人员,包括未来的你,实际上从明确看到所有这些输入被连接起来中受益匪浅,因为它使代码路径更容易追踪。但是,你总是可以进行优化。例如,你可以编写一个状态链接器。
MyComponent = React.createClass({
  getInitialState() {
    return {
      firstName: this.props.firstName || "",
      lastName: this.props.lastName || "" 
      ...: ...
      ...
    }
  },
  componentWillMount() {
    Object.keys(this.state).forEach(n => {
      let fn = n + 'Changed';
      this[fn] = evt => {
        let update = {};
        update[n] = evt.target.value;
        this.setState(update);
      });
    });
  },
  render: function() {
    return Object.keys(this.state).map(n => {
      <input
        key={n} 
        type="text"
        value={this.state[n]}
        onChange={this[n + 'Changed']}/>
    });
  }
});

5
我看了几篇在线文章,说使用太多状态是个不好的主意。在我的应用程序的一个特定表单中,我有大约100个字段。定义一个函数来保存状态感觉像是一种不必要的艰难方式。如果我可以使用 onClick={this.onSubmit.bind(this)},这似乎是获取值的好方法(然后如果需要,设置组件状态) - 我希望能对此发表一些评论。 - JoeTidee
6
编写更聪明的代码。表单输入肯定是有状态的(考虑如果用户在中途关闭了他们的标签页并进行了恢复,那么填写的所有值应该恢复吗?是吗?这就是状态),因此编写一些状态维护代码。Facebook 也有成百上千的表单值,他们的疯狂解决方案是 React。它的效果非常好。使用双向状态链接是使你的代码变得更加简单的一种方法,同时仍然使用状态,同样在 React 网站上有解释。值得阅读!=) - Mike 'Pomax' Kamermans
2
请注意,“100个字段”大多数情况下是无关紧要的:拆分您的表单,因为它不是100个元素,而是几个部分,每个部分都有若干个输入,因此应用良好的设计,并使每个部分成为自己的组件,使用组合组件来进行表单分组。这通常使一个组件负责少于10个输入,突然间您的信息架构就变得更加清晰明了。当然,作为浏览器操作,表单提交只会看到“您的表单”,并一次性提交所有内容。干净、有针对性的用户界面设计。 - Mike 'Pomax' Kamermans
2
感谢您的评论。我注意到自React v15以来,状态链接已被弃用。 - JoeTidee
4
如果你这样做,就会在你的代码中引入潜在的bug。React并不是网页的“全能解决方案”,它是一个用于构建接口的框架,负责状态管理和实际UI渲染(用户与React进行交互,而不是DOM。DOM更新只是一个异步(但非常快速)的最后一步,以便UI直观地反映状态)。如果你想绕过这个过程,更重要的问题是:为什么要使用React?因为你错误地使用React的最明显迹象是将它与jQuery结合使用。 - Mike 'Pomax' Kamermans
显示剩余14条评论

29

通过类似以下这样的方式,成功获取了输入字段的值:

import React, { Component } from 'react';

class App extends Component {

constructor(props){
super(props);

this.state = {
  username : ''
}

this.updateInput = this.updateInput.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}


updateInput(event){
this.setState({username : event.target.value})
}


handleSubmit(){
console.log('Your input value is: ' + this.state.username)
//Send state to the server code
}



render(){
return (
    <div>
    <input type="text" onChange={this.updateInput}></input>
    <input type="submit" onClick={this.handleSubmit} ></input>
    </div>
  );
}
} 

//output
//Your input value is: x

4
为什么使用“setState”?这将涉及重新渲染..是吗? 为什么要使用“setState”?这会导致重新渲染...对吗? - Luca Davanzo

24

在 React 16 中,我使用

<Input id="number" 
       type="time" 
       onChange={(evt) => { console.log(evt.target.value); }} />

3
如果该字段在加载年龄时自动填充,那么如果出现这种情况,它将无效。 - SeanMC

23

你应该在类MyComponent继承React.Component下使用构造函数

constructor(props){
    super(props);
    this.onSubmit = this.onSubmit.bind(this);
  }

那么你将会得到标题的结果


此外,如果您正在使用Hooks,这里是如何使用React Hooks获取输入值的方法。链接 - InfiniteStack

14

在函数组件中

useState

useState 返回一个有状态的值及用于更新其值的函数。 在初始渲染时,返回的 state (state) 与作为第一个参数传递的初始状态值 (initialState) 相同。 setState 函数用于更新状态。它接受一个新的状态值并排队重新渲染组件。
src ---> https://reactjs.org/docs/hooks-reference.html#usestate

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数 (initialValue)。返回的对象将在组件的整个生命周期中持续存在。
src ---> https://reactjs.org/docs/hooks-reference.html#useref

import { useRef, useState } from "react";

export default function App() {
  const [val, setVal] = useState('');
  const inputRef = useRef();

  const submitHandler = (e) => {
    e.preventDefault();

    setVal(inputRef.current.value);
  }

  return (
    <div className="App">
      <form onSubmit={submitHandler}>
        <input ref={inputRef} />
        <button type="submit">Submit</button>
      </form>

      <p>Submit Value: <b>{val}</b></p>
    </div>
  );
}

12

<input>元素分配一个唯一的id属性

<input id='title' ...>

接着使用标准Web API来在DOM中引用它。

const title = document.getElementById('title').value

不需要在每次按键时持续更新React状态,只需在需要时获取值即可。


8
在React中这样做是非常不好的实践。 - DivinesLight
3
@DivinesLight,你能解释一下为什么吗? - Buddhi

7
在功能组件中:-
export default function App(){

const [state, setState] = useState({
        value:'',
        show:''
    });

const handleChange = (e) => {
    setState({value: e.target.value})
}

const submit = () => {
    setState({show: state.value})
}

return(
        <>
            <form onSubmit={()=>submit()}>
                <input type="text" value={state.value} onChange={(e)=>handleChange(e)} />
                <input type="submit" />
            </form>
            <h2>{state.show}</h2>
        </>
)}

7
export default class App extends React.Component{
     state={
         value:'',
         show:''
      }

handleChange=(e)=>{
  this.setState({value:e.target.value})
}

submit=()=>{
   this.setState({show:this.state.value})
}

render(){
    return(
        <>
          <form onSubmit={this.submit}>
             <input type="text" value={this.state.value} onChange={this.handleChange} />
             <input type="submit" />
          </form>
          <h2>{this.state.show}</h2>
        </>
        )
    }
}

5

我通过将this绑定到函数updateInputValue(evt) 来成功完成这个操作:

this.updateInputValue = this.updateInputValue.bind(this);

然而,将输入value={this.state.inputValue} ... 证明并不是一个好主意。

以下是Babel ES6的完整代码:

class InputField extends React.Component{

    
  constructor(props){
   super(props);
   //this.state={inputfield: "no value"};   
   this.handleClick = this.handleClick.bind(this);
   this.updateInputValue = this.updateInputValue.bind(this);
  }
  
  handleClick(){
   console.log("trying to add picture url");
   console.log("value of input field : "+this.state.inputfield);
   
  }
 
  updateInputValue(evt){
    //console.log("input field updated with "+evt.target.value);
    this.state={inputfield: evt.target.value};   
    
  }

  render(){
    var r; 
    r=<div><input type="text" id="addpixinputfield" 
            onChange={this.updateInputValue} />
      <input type="button" value="add" id="addpix" onClick={this.handleClick}/>
      </div>;    
    return r;
   }
}

3
  • React版本:17.0.1

    a)使用函数组件

    b)使用hook来管理状态:useState()。

按照上述方式编写并运行代码:

import React, {useState} from 'react';

const InputElement = () => {
  const [inputText, setInputText] = useState('');

  return (
   <div> 
        <input
              onChange={(e) => {
                  setInputText(e.target.value);
                   }                   
             }
             placeholder='Enter Text'
       />
       {inputText}
   </div>
 );
}

解决方案算法类似于双向数据绑定:

输入 <=> 数据模型 <=> 标签文本


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