带去抖动的 useEffect [英] useEffect with debounce

查看:32
本文介绍了带去抖动的 useEffect的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个输入字段,该字段的值已去抖动(以避免不必要的服务器行程).我第一次渲染我的组件时,我从服务器获取它的值(有一个加载状态等等).

这是我所拥有的(为了示例的目的,我省略了不相关的代码).

这是我的去抖钩:

导出函数 useDebounce(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value);useEffect(() => {const 处理程序 = setTimeout(() => {setDebouncedValue(value);}, 延迟);返回 () =>清除超时(处理程序);}, [值, 延迟]);返回去抖动值;}

(我从: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]);返回 (<div className="example-input-container"><输入类型=文本"值={标题}onChange={e =>setTitle(e.target.value)}/><div>最后提交的值:{lastCommittedTitle}</div><div>提交:{commitsCount}</div>

);}

这是父组件:

function App() {const [title, setTitle] = useState("");useEffect(() => {setTimeout(() => setTitle("这是从服务器异步来的"), 2000);}, []);返回 (<div className="应用程序"><h1>示例</h1><ExampleTitleInput title={title}/>

);}

当我运行此代码时,我希望它第一次(仅)忽略 debounce 值更改,因此它应该显示提交次数为 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 || "");//我在这里添加了这一行setLastCommittedTitle(props.title || "");}, [道具]);useEffect(() => {if (debouncedTitle !== lastCommittedTitle) {setLastCommittedTitle(debouncedTitle);setCommitsCount(commitsCount + 1);}}, [debouncedTitle]);//在这里删除了其余的依赖项,但是现在 eslint 正在抱怨并警告我我使用了未在 deps 数组中列出的依赖项返回 (<div className="example-input-container"><输入类型=文本"值={标题}onChange={e =>setTitle(e.target.value)}/><div>最后提交的值:{lastCommittedTitle}</div><div>提交:{commitsCount}</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;}, []);返回 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();//这里const debouncedTitle = useDebounce(title, 1000);useEffect(() => {setTitle(props.title || "");}, [props.title]);useEffect(() => {//我不希望在 props 传递值时触发它(即 - 初始化时)if (is_first_render) {//这里返回;}if (debouncedTitle !== lastCommittedTitle) {setLastCommittedTitle(debouncedTitle);setUpdatesCount(updatesCount + 1);}}, [debouncedTitle, lastCommittedTitle, updatesCount]);//...

I'm trying to create an input field that has its value de-bounced (to avoid unnecessary server trips). The first time I render my component I fetch its value from the server (there is a loading state and all).

Here is what I have (I omitted the irrelevant code, for the purpose of the example).

This is my debounce hook:

export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

(I got this from: https://usehooks.com/useDebounce/)

Right, here is my component and how I use the useDebounce hook:

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>
  );
}

Here is the parent component:

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>
  );
}

When I run this code, I would like it to ignore the debounce value change the first time around (only), so it should show that the number of commits are 0, because the value is passed from the props. Any other change should be tracked. Sorry I've had a long day and I'm a bit confused at this point (I've been staring at this "problem" for far too long I think).

I've created a sample:

https://codesandbox.io/s/zen-dust-mih5d

It should show the number of commits being 0 and the value set correctly without the debounce to change.

I hope I'm making sense, please let me know if I can provide more info.

Edit

This works exactly as I expect it, however it's giving me "warnings" (notice dependencies are missing from the deps array):

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>
  );
}

Here it is: https://codesandbox.io/s/optimistic-perlman-w8uug

This works, fine, but I'm worried about the warning, it feels like I'm doing something wrong.

解决方案

A simple way to check if we are in the first render is to set a variable that changes at the end of the cycle. You could achieve this using a ref inside your component:

const myComponent = () => {
    const is_first_render = useRef(true);

    useEffect(() => {
        is_first_render.current = false;
    }, []);

    // ...

You can extract it into a hook and simply import it in your component:

const useIsFirstRender = () => {
    const is_first_render = useRef(true);

    useEffect(() => {
        is_first_render.current = false;
    }, []);

    return is_first_render.current;
};

Then in your component:

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]);

    // ...

这篇关于带去抖动的 useEffect的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆