【发布时间】:2020-04-22 23:19:21
【问题描述】:
尽管 setState() 的异步操作使任务复杂化,但在第一次单击后防止任何进一步的函数调用的最佳方法是什么?
该机制不一定必须基于 React 状态,但必须是易于可逆的(例如,在凭据错误的情况下通过 login())。
在下面的示例中,refs 仅用于模拟非常快速的点击。
在进入处理程序后立即检查状态的解决方案似乎不起作用:https://stackoverflow.com/a/49642037 (https://codesandbox.io/s/stupefied-moon-uouz2)
通过 ref 直接禁用按钮的解决方案对于可以通过回车从任何字段提交的表单来说根本不方便:https://stackoverflow.com/a/35316216
一个似乎适用于 Class 组件的解决方案,但看起来有点难看,并且奇怪地导致 codeandbox.io 下的双重 setState(为什么?)无论点击次数如何,但不是本地(https://codesandbox.io/s/ecstatic-river-wuhov):
import React, { Component } from "react";
export default class Login extends Component {
constructor() {
super();
this.state = { disabled: false };
this.ref = React.createRef();
}
componentDidMount() {
console.log("click");
this.ref.current.click();
console.log("click");
this.ref.current.click();
console.log("click");
this.ref.current.click();
}
onClick = () => {
this.setState(({ disabled }) => {
disabled || this.login();
return { disabled: true };
});
};
login = () => console.log("login");
render() {
return (
<button
ref={this.ref}
onClick={this.onClick}
disabled={this.state.disabled}
>
Login
</button>
);
}
}
一个稍重的变体,但没有那种奇怪的副作用 (https://codesandbox.io/s/compassionate-bouman-hjtv6):
import React, { Component } from "react";
export default class Login extends Component {
constructor() {
super();
this.state = { disabled: false };
this.ref = React.createRef();
}
componentDidMount() {
console.log("click");
this.ref.current.click();
console.log("click");
this.ref.current.click();
console.log("click");
this.ref.current.click();
}
componentDidUpdate(prevProps, prevState) {
const { disabled } = this.state;
if (prevState.disabled !== disabled && disabled) {
this.login();
}
}
onClick = () => this.setState({ disabled: true });
login = () => console.log("login");
render() {
return (
<button
ref={this.ref}
onClick={this.onClick}
disabled={this.state.disabled}
>
Login
</button>
);
}
}
对于功能组件,此解决方案有效,但在开发模式下会导致 eslint 警告(React Hook useEffect 缺少依赖项)(这很糟糕吗?)(https://codesandbox.io/s/frosty-allen-tgs42):
import React, { useRef, useEffect, useState } from "react";
export default () => {
const [disabled, setDisabled] = useState(false);
const ref = useRef();
useEffect(() => {
console.log("click");
ref.current.click();
console.log("click");
ref.current.click();
console.log("click");
ref.current.click();
}, []);
useEffect(() => {
disabled && login();
}, [disabled]);
const onClick = () => setDisabled(true);
const login = () => console.log("login");
return (
<button ref={ref} onClick={onClick} disabled={disabled}>
Login
</button>
);
};
在codeandbox.io下导致双重setSate的类组件的解决方案似乎对功能组件(我目前在生产中使用的模式)没有问题(https://codesandbox.io/s/kind-hoover-ls3t3):
import React, { useRef, useEffect, useState } from "react";
export default () => {
const [disabled, setDisabled] = useState(false);
const ref = useRef();
useEffect(() => {
console.log("click");
ref.current.click();
console.log("click");
ref.current.click();
console.log("click");
ref.current.click();
}, []);
const onClick = () =>
setDisabled(prevDisabled => {
prevDisabled || login();
return true;
});
const login = () => console.log("login");
return (
<button ref={ref} onClick={onClick} disabled={disabled}>
Login
</button>
);
};
由于这些解决方案似乎都不令人满意,有什么建议吗?
【问题讨论】:
-
那个 eslint 警告意味着你的依赖数组可能不应该是空的。看起来
ref.current可能需要在里面。 -
您是否考虑过使用去抖动器?
-
@ellitt:完全警告:
Line 23:8: React Hook useEffect has a missing dependency: 'login'. Either include it or remove the dependency array react-hooks/exhaustive-deps。似乎是表格中缺少的“登录”,但是当您将其添加到其中时,情况会更糟:The 'login' function makes the dependencies of useEffect Hook (at line 43) change on every render... -
哦,我明白了,你有两个 useEffects。我只是在看第一个。
-
@JayBee:确实,去抖动器肯定会允许解决 pb。另一方面,这个解决方案可能不是最干净的,因为我们必须估计去抖动器延迟与我们期望观察到的 setState 相比(停用按钮然后接管去抖动器)。因此它们不会以编程方式链接。
标签: reactjs