这里有三个答案,这取决于你(被迫)使用的React版本,以及你是否想使用钩子。
首先要做的事情:
了解 React 是如何工作的很重要,这样你才能正确地做事(专业提示:在 React 网站上运行 React 教程是非常值得的。它写得很好,并以一种实际解释如何做事的方式涵盖了所有基础知识)。这里的“正确”意味着你不是在写网页,而是在为碰巧在浏览器中呈现的应用程序编写用户界面;所有实际的用户界面工作都发生在 React 中,而不是“你习惯于编写网页”(这就是为什么 React 应用程序真的是“应用程序”,而不是“网页”)。
React 应用程序基于两件事进行渲染:
- 由创建该组件实例的父级声明的组件属性,父级可以在其整个生命周期中修改该实例,以及
- 组件自己的内部状态,它可以在自己的生命周期中修改自身。
当你使用 React 时,你明确没有做的是生成 HTML 元素,然后使用这些元素:例如,当你告诉 React 使用一个 ,例如,你不是在创建一个 HTML 输入元素,而是告诉 React 创建一个 React 输入对象,当你编译 Web 的 React 应用程序时,该对象恰好呈现为 HTML 输入元素, 使用由 React 控制的事件处理。<input>
使用 React 时,您正在执行的操作是生成应用程序 UI 元素,这些元素向用户呈现(通常是可操作的)数据,用户交互以您定义的方式更改应用程序的状态 - 用户执行的操作可能会更新组件的 props 或状态,React 将其用作为已更改组件生成新 UI 表示的信号, 这可能会导致部分应用程序接口的更新以反映新状态。
在此编程模型中,应用的内部状态是最终的权威,而不是“用户查看并与之交互的 UI”:如果用户尝试在输入字段中键入某些内容,而您没有编写任何内容来处理该操作,则不会发生任何事情:UI 是应用程序状态的反映,而不是相反。实际上,浏览器DOM在这个编程模型中几乎是事后的想法:它恰好是一个超级方便的UI框架,整个星球几乎可以保证可以访问(但它不是React唯一知道如何使用的框架)。
一个具体的例子
因此,在介绍完这些内容后,让我们来看看用户如何在 React 中与输入元素交互。首先,我们需要有一个UI元素供用户与之交互:
- 您编写了一个组件来管理(即存储和呈现)用户的某些字符串数据,并具有用于处理用户数据的函数。
onChange
- React 使用组件的呈现代码来生成包含组件(不是 DOM 元素)的虚拟 DOM,并将处理程序绑定到该组件,以便可以使用 React 事件数据调用它(请注意,这不是 DOM 事件侦听器,并且不会获取与常规 DOM 事件侦听器相同的事件数据)。
input
<input>
onChange
change
- 然后,React 库将虚拟 DOM 转换为用户可以与之交互的 UI,并且它将随着应用程序状态的变化而更新。由于它在浏览器中运行,因此它会构建一个 HTML 输入元素。
然后,您的用户尝试与该输入元素实际交互:
- 您的用户单击输入元素并开始键入。
-
输入元素尚未发生任何情况。相反,输入事件被 React 拦截并立即被杀死。
- React 将浏览器事件转换为 React 事件,并使用 React 事件数据调用虚拟 DOM 组件的函数。
onChange
- 该函数可能会根据您编写它的方式执行某些操作,在这种情况下,您几乎肯定会编写它以使用用户(尝试)键入的内容更新组件的状态。
- 如果计划了状态更新,React 将在不久的将来运行该状态更新,这将在更新后触发渲染传递。
- 在渲染过程中,它会检查状态是否实际不同,如果是这样,它会生成一个临时的第二个虚拟DOM,它将它与应用程序的虚拟DOM(一部分)进行比较,确定它需要对应用程序的虚拟DOM执行哪组添加/更新/删除操作,以便它看起来与新的临时DOM相同, 然后应用这些操作并再次丢弃临时虚拟 DOM。
- 然后,它会更新 UI,以便它反映虚拟 DOM 现在的外观。
- 在所有这些之后,我们终于在用户实际查看的页面上有一个更新的DOM,他们可以看到他们在输入元素中键入的内容。
因此,这与常规浏览器模型完全不同:用户不是通过首先在文本框中键入来更新UI数据,然后我们的代码读取“该文本框的当前值”以找出状态,而是React已经知道状态是什么,并使用事件首先更新状态, 这会导致第二次UI更新。
重要的是要记住,所有这些都是立即有效地发生的,所以对于你的用户来说,看起来他们以与任何随机网页相同的方式将文本键入输入元素,但在引擎盖下,事情不会有太大的不同,同时仍然导致相同的结果。
因此,在介绍完这些内容后,让我们来看看如何从 React 中的元素中获取值:
组件类和 ES6(React 16+ 和 15.5 过渡)
从 React 16 开始(以及从 15.5 开始的软启动),不再支持调用,需要使用类语法。这改变了两件事:明显的类语法,以及可以“免费”执行的上下文绑定,因此为了确保事情仍然有效,请确保您使用“胖箭头”表示法来保存处理程序中的匿名函数,例如我们在代码中使用的代码:createClass
this
createClass
this
onWhatever
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}/>
...
);
}
别这样。
几乎任何时候你都在使用,众所周知的“你做错了”就适用了。您的类已经定义了对象原型,因此已经定义了实例上下文。不要把那放在上面;使用正常的事件转发,而不是复制构造函数中的所有函数调用,因为这种重复会增加 Bug 表面,并使跟踪错误变得更加困难,因为问题可能出在构造函数中,而不是在调用代码的位置。bind
bind
“但是,它不断地在重新渲染上制作和丢弃功能!”这可能是真的,但你不会注意到。您的用户也不是。如果事件处理程序垃圾回收是你的性能瓶颈,那么已经出了很多错误,你需要停下来重新思考你的设计:React工作得如此之好的原因是因为它不会更新整个UI,它只会更新更改的部分,并且在设计良好的UI中,大多数UI花费不更改的时间远远超过UI的一小部分用于更新的时间。
带钩子的函数组件 (React 16.8+)
从 React 16.8 开始,函数组件(即字面上只是一个将一些作为参数的函数,可以像组件类的实例一样使用,而无需编写类)也可以通过使用钩子来赋予状态。props
如果你不需要完整的类代码,并且单个实例函数就可以了,那么你现在可以使用钩子为自己获取一个状态变量及其更新函数,其工作原理与上面的示例大致相同,除了没有“通用”函数调用,并且对你正在使用的每个值使用一个专用的状态 setter:useState
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 15 及更低版本,使用旧版 ES5 和createClass
为了正确地执行操作,您的组件具有一个状态值,该值通过输入字段显示,我们可以通过使该 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 使用该函数来处理用户交互,用于计划状态更新,而 tap into 的事实意味着,当它在更新状态后重新呈现时,用户将根据他们键入的内容看到更新文本。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']}/>
});
}
});