Javascript 反应事件AJAX调用竞争条件
基本场景 我有一个React textbox控制的组件,其Javascript 反应事件AJAX调用竞争条件,javascript,jquery,ajax,reactjs,Javascript,Jquery,Ajax,Reactjs,基本场景 我有一个React textbox控制的组件,其onChange事件最终触发对服务器端API的AJAX调用。此调用的结果可能会更改文本框的值。因此,在AJAX调用的回调中有一个对setState的调用 基本问题 在AJAX调用完成之前对输入进行更改时,我很难找到一种平滑、一致地更新此值的方法。到目前为止,我已经尝试了两种方法。主要区别在于AJAX调用最终是如何发生的 方法1 我的第一次尝试使用立即输入的数据调用setState,最终触发重新渲染和componentdiddupdate。
onChange
事件最终触发对服务器端API的AJAX调用。此调用的结果可能会更改文本框的值。因此,在AJAX调用的回调中有一个对setState
的调用
基本问题
在AJAX调用完成之前对输入进行更改时,我很难找到一种平滑、一致地更新此值的方法。到目前为止,我已经尝试了两种方法。主要区别在于AJAX调用最终是如何发生的
方法1
我的第一次尝试使用立即输入的数据调用setState
,最终触发重新渲染和componentdiddupdate
。后者然后进行AJAX调用,条件是所讨论的状态数据不同
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
});
}
}
这种方法的优点是可以快速更新状态以反映用户的即时输入。但是,如果快速进行两次输入,则会出现如下顺序:
'1'
- 处理程序1使用值
'1'
- 组件从状态更改中重新渲染
从重新渲染触发componentdiddupdate
- 值
与上一个值不同,因此'1'
- 使用值
'1'
'12'
- 处理程序2使用值
'12'
从重新渲染触发componentdiddupdate
- 值
不同于'12'
,因此'1'
- 使用值
'12'
'1'
- AJAX回调1使用值
'1'
从重新渲染触发componentdiddupdate
- 值
不同于'1'
,因此'12'
- 使用值
'1'
'12'
componentdiddupdate
中进行了最后一次状态检查,但仍会发生无限循环,因为两个重叠的AJAX调用为setState
提供交替值
方法2
为了解决这个问题,我的第二种方法简化了系统,并直接从事件处理程序进行AJAX调用:
handleChange(event) {
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue });
});
}
但是,如果我这样做,受控组件值的即时更新将暂停,直到AJAX调用完成并调用setState
。简单稳定,只需设置状态和渲染一次;但在等待AJAX调用时暂停输入是不好的。第一种方法至少有(过度)立即更新的外表
方法3?
在等待答案的同时,我将实施以下方法3,它基本上是方法1的增强版本:
- 将请求ID添加到AJAX调用数据中,该数据在每次调用时递增
- 在响应中回显请求ID
- 在回调中,如果当前请求ID大于响应ID,则响应数据已过期
- 如果响应数据未过期,请调用
setState
我还没有做出反应。我想其他人也遇到过这个用例,但我很难找到解决方案。我想要一种方法来设置状态并立即更新组件的值,即la方法1,并且仍然保持方法2的数据稳定性。方法3似乎很有希望,但有点太复杂了。是否有一种优雅的模式可以实现这一点?我最终返回到方法1,但对输入进行去抖动以消除重叠 特别是,我使用了一个从
componentdiddupdate
中的代码重构的方法,该方法实际上进行了AJAX调用:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.validateInput = this.validateInput.bind(this);
this.validateInputDebounced = _.debounce(this.validateInput, 100);
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
validateInputDebounced(inputTextValue);
}
}
validateInput(newInputTextValue) {
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: newInputTextValue })
// set other AJAX options
}).done((response) => {
setState({ inputText: response.validatedInputTextValue }); // will also trigger componentDidUpdate
});
}
这部分是基于此处所做的工作:
编辑
经进一步检查,这种方法也有不足之处。如果AJAX调用的时间足够长,那么请求可能会再次解决无序问题。我想我会保持去Bounce逻辑以节省网络流量;但是,被接受的解决方案,即取消先前的进行中请求,足以解决这个问题。建议的解决方案(#1)有一个很大的警告:
您不能保证第一个请求会在第二个请求之前返回
为了避免这种情况,您可以采用以下方法之一:
锁定选择输入:
您选择的组件:
const Select = props => {
const {disabled, options} = props;
return (<select disabled={disabled}>
{ options.map(item => <option value={item}> {item} </option> }
</select>)
}
class LogicalComponent extends React.Component {
constructor(props) {
this.state = {
selectDisabled: false;
options: ['item1', 'item2', 'item3'],
inputText: ''
}
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
// disabling the select until the request finishes
this.setState({ selectDisabled: true });
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
//re-enabling it when done
setState({ inputText: response.validatedInputTextValue, selectDisabled: false }); // will also trigger componentDidUpdate
// don't forget to enable it when the request is failed
}).fail(res => this.setState({selectDisabled: false}));
}
}
render() {
const { selectDisabled, options, inputText } = this.state;
return <>
<Select disabled={selectDisabled} options={options} />
<input type="text" value={inputText}/>
<>
}
}
const Select=props=>{
const{disabled,options}=props;
返回(
{options.map(项=>{item}}
)
}
您的逻辑组件:
const Select = props => {
const {disabled, options} = props;
return (<select disabled={disabled}>
{ options.map(item => <option value={item}> {item} </option> }
</select>)
}
class LogicalComponent extends React.Component {
constructor(props) {
this.state = {
selectDisabled: false;
options: ['item1', 'item2', 'item3'],
inputText: ''
}
}
handleChange(event) {
const inputTextValue = event.target.value;
setState({ inputText: inputTextValue }); // will trigger componentDidUpdate
}
componentDidUpdate(lastProps, lastState) {
const inputTextValue = this.state.inputText;
if (lastState.inputText !== inputTextValue) { // string comparison to prevent infinite loop
// disabling the select until the request finishes
this.setState({ selectDisabled: true });
$.ajax({
url: serviceUrl,
data: JSON.stringify({ inputText: inputTextValue })
// set other AJAX options
}).done((response) => {
//re-enabling it when done
setState({ inputText: response.validatedInputTextValue, selectDisabled: false }); // will also trigger componentDidUpdate
// don't forget to enable it when the request is failed
}).fail(res => this.setState({selectDisabled: false}));
}
}
render() {
const { selectDisabled, options, inputText } = this.state;
return <>
<Select disabled={selectDisabled} options={options} />
<input type="text" value={inputText}/>
<>
}
}
类LogicalComponent扩展了React.Component{
建造师(道具){
此.state={
selectDisabled:false;
选项:['item1','item2','item3'],
输入文本:“”
}
}
手变(活动){
const inputExtValue=event.target.value;
setState({inputText:inputTextValue});//将触发componentDidUpdate
}
componentDidUpdate(lastProps,lastState){
const inputExtValue=this.state.inputExt;
if(lastState.inputText!==inputTextValue){//字符串比较以防止无限循环
//禁用select直到请求完成
this.setState({selectDisabled:true});
$.ajax({
url:serviceUrl,
数据:JSON.stringify({inputText:inputTextValue})
//设置其他AJAX选项
}).完成((响应)=>{
//我们正在启用它