这里有三个答案,取决于你使用的React版本,以及你是否想要使用hooks。
首先:
重要的是要理解React的工作原理,这样你才能正确地做事情(专业提示:非常值得在React网站上运行React教程。它写得很好,并以实际解释如何做事情的方式涵盖了所有基础知识)。在这里,“正确”意味着你不是在编写网页,而是在编写应用程序的用户界面,该应用程序恰好在浏览器中呈现;所有实际的用户界面工作都发生在React中,而不是“从编写网页中习惯的内容”(这就是为什么React应用程序真正是“应用程序”,而不是“网页”的原因)。
React应用程序基于两个东西进行渲染:
- 由创建该组件实例的任何父级声明的组件属性,父级可以在其生命周期内修改这些属性,
- 该组件自己的内部状态,它可以在自己的生命周期内自行修改。
当你使用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() {
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']}/>
});
}
});
this.onSubmit.bind(this)
的意思是绑定当前的this
到onSubmit
方法上。 - zerkmse.target.value
怎么样? - omarjmhonClick={this.onSubmit.bind(this)}
)时,您需要将onSubmit
方法绑定到提交按钮(DOM 元素)。如果您想访问表单中标题输入的值,可以使用onSubmit(event) { const title = event.target.elements.title.value; }
。 - tim-montague