我正在尝试创建一个其值被取消反弹的输入字段(以避免不必要的服务器跳闸(。 第一次渲染我的组件时,我从服务器获取其值(有一个加载状态和所有(。
这是我所拥有的(出于示例的目的,我省略了不相关的代码(。
这是我的去抖钩:
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
(我从:https://usehooks.com/useDebounce/得到这个(
是的,这是我的组件以及我如何使用useDebounce
钩子:
function ExampleTitleInput(props) {
const [title, setTitle] = useState(props.title || "");
const [lastCommittedTitle, setLastCommittedTitle] = useState(title);
const [commitsCount, setCommitsCount] = useState(0);
const debouncedTitle = useDebounce(title, 1000);
useEffect(() => {
setTitle(props.title || "");
}, [props.title]);
useEffect(() => {
if (debouncedTitle !== lastCommittedTitle) {
setLastCommittedTitle(debouncedTitle);
setCommitsCount(commitsCount + 1);
}
}, [debouncedTitle, lastCommittedTitle, commitsCount]);
return (
<div className="example-input-container">
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
/>
<div>Last Committed Value: {lastCommittedTitle}</div>
<div>Commits: {commitsCount}</div>
</div>
);
}
下面是父组件:
function App() {
const [title, setTitle] = useState("");
useEffect(() => {
setTimeout(() => setTitle("This came async from the server"), 2000);
}, []);
return (
<div className="App">
<h1>Example</h1>
<ExampleTitleInput title={title} />
</div>
);
}
当我运行此代码时,我希望它在第一次(仅(忽略去抖值更改,因此它应该显示提交次数为 0,因为该值是从 props 传递的。应跟踪任何其他更改。对不起,我度过了漫长的一天,在这一点上我有点困惑(我认为我已经盯着这个"问题"太久了(。
我创建了一个示例:
https://codesandbox.io/s/zen-dust-mih5d
它应该显示提交次数为 0 并且正确设置的值,而无需更改去抖动。
我希望我说得有道理,如果我能提供更多信息,请告诉我。
编辑
这完全符合我的预期,但是它给了我"警告"(注意 deps 数组中缺少依赖项(:
function ExampleTitleInput(props) {
const [title, setTitle] = useState(props.title || "");
const [lastCommittedTitle, setLastCommittedTitle] = useState(title);
const [commitsCount, setCommitsCount] = useState(0);
const debouncedTitle = useDebounce(title, 1000);
useEffect(() => {
setTitle(props.title || "");
// I added this line here
setLastCommittedTitle(props.title || "");
}, [props]);
useEffect(() => {
if (debouncedTitle !== lastCommittedTitle) {
setLastCommittedTitle(debouncedTitle);
setCommitsCount(commitsCount + 1);
}
}, [debouncedTitle]); // removed the rest of the dependencies here, but now eslint is complaining and giving me a warning that I use dependencies that are not listed in the deps array
return (
<div className="example-input-container">
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
/>
<div>Last Committed Value: {lastCommittedTitle}</div>
<div>Commits: {commitsCount}</div>
</div>
);
}
这是:https://codesandbox.io/s/optimistic-perlman-w8uug
这很好,但我担心警告,感觉我做错了什么。
检查我们是否处于第一个渲染中的一种简单方法是设置一个在循环结束时更改的变量。您可以使用组件中的 ref 来实现此目的:
const myComponent = () => {
const is_first_render = useRef(true);
useEffect(() => {
is_first_render.current = false;
}, []);
// ...
您可以将其提取到钩子中,然后简单地将其导入组件中:
const useIsFirstRender = () => {
const is_first_render = useRef(true);
useEffect(() => {
is_first_render.current = false;
}, []);
return is_first_render.current;
};
然后在组件中:
function ExampleTitleInput(props) {
const [title, setTitle] = useState(props.title || "");
const [lastCommittedTitle, setLastCommittedTitle] = useState(title);
const [updatesCount, setUpdatesCount] = useState(0);
const is_first_render = useIsFirstRender(); // Here
const debouncedTitle = useDebounce(title, 1000);
useEffect(() => {
setTitle(props.title || "");
}, [props.title]);
useEffect(() => {
// I don't want this to trigger when the value is passed by the props (i.e. - when initialized)
if (is_first_render) { // Here
return;
}
if (debouncedTitle !== lastCommittedTitle) {
setLastCommittedTitle(debouncedTitle);
setUpdatesCount(updatesCount + 1);
}
}, [debouncedTitle, lastCommittedTitle, updatesCount]);
// ...
您可以更改useDebounce
钩子,以了解应立即设置第一个设置的去抖值。useRef
非常适合:
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
const firstDebounce = useRef(true);
useEffect(() => {
if (value && firstDebounce.current) {
setDebouncedValue(value);
firstDebounce.current = false;
return;
}
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
我认为您可以通过某些方式改进代码:
首先,不要使用 useEffectExampleTitleInput
将props.title
复制到本地状态,因为它可能会导致过多的重新渲染(首先是更改 props,而不是更改状态作为副作用(。直接使用props.title
并将去抖/状态管理部分移动到父组件。你只需要传递一个onChange
回调作为道具(考虑使用useCallback
(。
为了跟踪旧状态,正确的钩子是useRef
(API 参考(。
如果不希望它在第一次渲染中触发,则可以使用自定义钩子,例如useUpdateEffect
,来自react-use
: https://github.com/streamich/react-use/blob/master/src/useUpdateEffect.ts,该钩子已经实现了与useRef
相关的逻辑。