The debounce function has become a common optimization method to reduce the firing frequency of certain operations.

How does Debounce reduce function calls?

Let’s start with a simple debounce function

function debounce(fn, ms) {
    let timer = null;
    return (. args) = > {
        clearTimeout(timer);
        timer = setTimeout(() = > {
           fn(...args); 
        }, ms);
    }
}
Copy the code

The idea is to cache timer variables using closures and use clearTimeout to cancel unnecessary operations.

But what does it mean to use debounce properly in a hook

Write a common everyday use of debounce that is’ problematic ‘:

function App() {
    const fn = debounce(() = > console.log(1), 500);
    
    return (
        <div>
            <input onChange={fn} />
        </div>
    );
}
Copy the code

Does this debounce function work?

.

.

.

The answer is yes, so why is it ok? Let’s take it one step at a time.

Let’s start with the test results

After we put in a couple of ones in a row,

There will only be a 1 by 1 output, which proves that our debounce function is valid.

Requirement – PM needs a button to clear input values with one click

Simply bind value.

function App() {
    const [value, setValue] = useState(' ');
    const fn = debounce(() = > console.log(1), 500);
    
    return (
        <div>
            <input 
                value={value} 
                onChange={(event)= >{ const _v = event.target.value; setValue(_v); fn(); }} / ><br />
            <button onClick={()= >SetValue (' ')} > empty</button>
        </div>
    );
}
Copy the code

Test steps

  1. The input111111And each interval is 300ms
  2. Click on the empty
  3. Result Input box has no value

The results of

No problem. Next problem!

but

Did it really turn out that way?

Let’s open console and retry the test steps

There are five ones, no debounce effect, and it looks like a delayed input

❓ ❓ ❓

Instead of adding a state to the debounce function, it’s not an ounce.

Why did this happen — the molesetValue

How does setValue mess up?

Enter 1 for the first time

Input onChange is triggered, event.target.value is 1, setValue is triggered to update the React view, and the fn function is executed

Enter 1 for the second time

React execute App(), debounce(() => console.log(1), 500); Return a new function (note that the function is not the previous fn, but a new fn, fn! == fn), input onChange is triggered, event.target.value is 11, setValue is triggered to update the React view, and then fn function is executed

.

You can actually see it here.

Debounce cashes the timer variable by executing a function, using a closure. However, debounce returns multiple functions multiple times, in which case the same closure is not shared. SetValue triggers the execution of the function, generating a new function with each execution. Fn no longer shares the same closure as the previous fn.

change

As long as debounce returns the same function, we use useCallback to implement this

function App() {
    const [value, setValue] = useState(' ');
    const fn = useCallback(debounce(() = > console.log(1), 500), []);
    
    return (
        <div>
            <input 
                value={value} 
                onChange={(event)= >{ const _v = event.target.value; setValue(_v); fn(); }} / ><br />
            <button onClick={()= >SetValue (' ')} > empty</button>
        </div>
    );
}
Copy the code

Complete:

But is it okay?

Let’s try to read the value inside of this function.

function App() {
    const [value, setValue] = useState(' ');
    const fn = useCallback(
        debounce(() = > console.log(value), 500), 
    []);
    
    return (
        <div>
            <input 
                value={value} 
                onChange={(event)= >{ const _v = event.target.value; setValue(_v); fn(); }} / ><br />
            <button onClick={()= >SetValue (' ')} > empty</button>
        </div>
    );
}
Copy the code

You’ll notice that no matter how you type it, the value is always a null character, which is the initial value of value.

Because useCallback caches this function, the function still retains its own closure.

Let’s do this with useRef

function App() {
    const [value, setValue] = useState(' ');
    
    const f = () = > console.log(value);
    const fRef = useRef();
    fRef.current = f;
    
    const fn = useCallback(
        debounce(() = > fRef.current(), 500), 
    []);
    
    return (
        <div>
            <input 
                value={value} 
                onChange={(event)= >{ const _v = event.target.value; setValue(_v); fn(); }} / ><br />
            <button onClick={()= >SetValue (' ')} > empty</button>
        </div>
    );
}
Copy the code

The paper comes zhongjue shallow, you can try yourself. You can join me in the discussion

Custom hooks

function useDebounce(fn, ms) {
    const fRef = useRef();
    fRef.current = fn;
    
    const result = useCallback(
        debounce(() = > fRef.current(), ms), 
    []);
    return result;
}
Copy the code

Voom, this is Uther of the Front Order.

If you need to push inward, if you need to touch fish, plus group plus me ++++js_xiaomayi