这里有三个答案,这取决于您(被迫使用)React的版本,以及是否要使用挂钩。
第一件事:
理解React的工作原理很重要,这样您就可以正确地做事(提示:在React网站上运行React教程练习是非常值得的。它写得很好,并涵盖了所有基础知识,实际上是在解释如何做。东西)。这里的“正确”表示您正在编写一个恰好在浏览器中呈现的应用程序接口;所有界面工作都在React中进行,而不是在“编写网页时的习惯”中进行(这就是为什么React应用是“应用”而不是“网页”的原因)。
React应用程序基于以下两个方面进行呈现:
- 由任何父级创建的组件实例的实例声明的组件属性,父级可以在其整个生命周期中对其进行修改,并且
- 组件自身的内部状态,可以在其整个生命周期中对其进行修改。
当您使用React时,您显然没有做的是生成HTML元素,然后使用它们:<input>
例如,当您告诉React使用一个时,您不是在创建HTML输入元素,而是在告诉React创建一个React输入对象它恰好呈现为HTML输入元素,并且其事件处理着眼于但不受 HTML元素的输入事件控制。
使用React时,您正在做的是生成应用程序UI元素,向用户展示(通常是可操作的)数据,并且用户交互会更改Component的状态,这可能导致应用程序界面的一部分重新呈现以反映新状态。在此模型中,状态始终是最终的权限,而不是“使用任何UI库呈现状态”,而状态权在网络上是浏览器的DOM。在这个编程模型中,DOM几乎是事后才想到的:它只是React恰好使用的特定UI框架。
因此,对于输入元素,逻辑为:
- 您输入输入元素,
- 您的输入元素没有任何反应,该事件被React拦截并立即终止,
- React将事件转发到您为事件处理设置的功能,
- 该功能可以安排状态更新,
- 如果确实如此,React将运行状态更新(异步!),并
render
在更新后触发调用,但前提是状态更新更改了状态。
- 只有在进行此渲染之后,UI才会显示您“键入了字母”。
所有这些操作大约需要几毫秒,甚至更短的时间,因此看起来就像您从习惯于“仅在页面上使用输入元素”那样输入input元素一样,但这绝对不是发生了
因此,说到如何从React中的元素获取值:
使用ES5的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
});
}
});
所以我们告诉反应使用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']}/>
});
}
});
当然,对此有改进的版本,因此请访问https://npmjs.com并搜索您最喜欢的React状态链接解决方案。开源主要是寻找他人已经做过的事情,而不是从头开始编写所有内容。
React 16(和15.5过渡版)和``现代''JS
从React 16开始(以及从15.5开始软启动),createClass
不再支持该调用,并且需要使用类语法。这将改变两件事:明显的类语法,this
以及createClass
可以“免费”执行的上下文绑定,因此要确保一切仍然正常,请确保使用“胖箭头”表示法this
在onWhatever
处理程序中保留上下文的匿名函数,例如在onChange
这里的代码中,我们使用:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
};
}
render() {
return (
//...
<input value={this.state.inputValue} onChange={evt => this.updateInputValue(evt)}/>
//...
);
},
updateInputValue(evt) {
this.setState({
inputValue: evt.target.value
});
}
});
您可能还已经看到人们bind
在其构造函数中使用了所有事件处理功能,如下所示:
constructor(props) {
super(props);
this.handler = this.handler.bind(this);
...
}
render() {
return (
...
<element onclick={this.handler}/>
...
);
}
不要那样做
几乎在您每次使用时bind
,都会有一个谚语:“您做错了”。您的类已经定义了对象原型,因此已经定义了实例上下文。不要放在首位bind
;请使用常规事件转发,而不是在构造函数中复制所有函数调用,因为这种复制会增加您的错误面,并且使跟踪错误变得更加困难,因为问题可能出在构造函数中而不是代码所在位置。以及给您(拥有或选择)与之共事的其他人增加维护负担。
是的,我知道react docs说很好。不是,不要这样做。
使用带钩子的功能组件进行React 16.8
从React 16.8开始,函数组件(即从字面上仅以某些props
参数作为参数的函数就可以使用,就好像它是组件类的实例,而无需编写类)也可以通过使用hooks来获得状态。
如果您不需要完整的类代码,并且只需一个实例函数,则可以使用该useState
钩子为自己获取一个状态变量及其更新函数,该函数的作用与上述示例大致相同,不同之处在于:该setState
函数调用:
import { useState } from 'react';
function myFunctionalComponentFunction() {
const [input, setInput] = useState(''); // '' is the initial state value
return (
<div>
<label>Please specify:</label>
<input value={input} onInput={e => setInput(e.target.value)}/>
</div>
);
}
以前,类和功能组件之间的非正式区别是“功能组件没有状态”,因此我们再也无法躲藏起来:功能组件和类组件之间的区别可以很好地分布在几页上编写的反应文档(没有捷径说明可以方便地为您带来误解!),您应该阅读该文档,以便您知道自己在做什么,从而可以知道自己是否选择了最佳的(无论对您而言意味着什么)解决方案来进行编程摆脱您遇到的问题。
this.onSubmit.bind(this);