如何使用钩子去抖功能组件中的回调
How to debounce a callback in functional component using hooks
我怎样才能在 React 功能组件去抖动回调中获得实际的 prop 值,它在 React Class 组件中工作,但我不知道如何使用钩子在功能组件中实现这种行为。
import React from "react";
import ReactDOM from "react-dom";
import debounce from "lodash.debounce";
const TestFunc = ({ count, onClick }) => {
const handleClick = debounce(() => {
onClick();
console.log(count);
}, 500);
return (
<div>
<button type="button" onClick={handleClick}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(countFunc + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
当你点击功能组件按钮时,它会将之前的 count
属性值记录到控制台,但它已经通过调用 onClick
处理程序进行了更改,同时 class组件按钮将在 onClick
处理程序递增后记录实际的 count
道具值。那么,我怎样才能在功能组件中获得实际的 prop 值呢?
您需要进行一些更改才能使用 debounced method
和 hook
- 您需要使用
useCallback
挂钩,以便在初始渲染时只创建一次去抖功能。
- 现在,如果您必须确保 debounced 在执行时获得正确的计数值,您需要将其作为参数传递,否则它将在创建时使用其封闭闭包中的值,即初始计数值。
- 您需要使用父组件中的回调模式更新 onClick 方法调用的计数值,例如
setCountFunc(count => count + 1)
,以便子组件 re-render 具有更新后的值
下面的工作演示
const TestFunc = ({ count, onClick }) => {
const handleClick = React.useCallback((count) =>{
const click = _.debounce((count) => {
onClick();
console.log(count);
}, 500)
click(count);
}, []);
console.log(count, 'render');
return (
<div>
<button type="button" onClick={() => handleClick(count)}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = _.debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(count => count + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root" />
这是一个简单的去抖挂钩(用 TypeScript 编写)
import { useEffect, useRef } from "react";
export function useDebouncedCallback<A extends any[]>(
callback: (...args: A) => void,
wait: number
) {
// track args & timeout handle between calls
const argsRef = useRef<A>();
const timeout = useRef<ReturnType<typeof setTimeout>>();
function cleanup() {
if(timeout.current) {
clearTimeout(timeout.current);
}
}
// make sure our timeout gets cleared if
// our consuming component gets unmounted
useEffect(() => cleanup, []);
return function debouncedCallback(
...args: A
) {
// capture latest args
argsRef.current = args;
// clear debounce timer
cleanup();
// start waiting again
timeout.current = setTimeout(() => {
if(argsRef.current) {
callback(...argsRef.current);
}
}, wait);
};
}
您的用例示例:
const handleClick = useDebouncedCallback(() => {
onClick();
console.log(count);
}, 500);
...
<button type="button" onClick={handleClick}>
Func: {count}
</button>
也适用于传递参数的情况:
const handleChange = useDebouncedCallback((event) => {
console.log(event.currentTarget.value);
}, 500);
<input onChange={handleChange}/>
去抖API调用功能组件
<input
type="text"
placeholder="Search"
onChange={(e) => search(e.target.value)}
/>
=========================================================================
const [typingTimeout, setTypingTimeout] = useState(0);
const search = async (value) => {
if (typingTimeout) {
clearTimeout(typingTimeout);
}
setTypingTimeout( setTimeout(() => {
goToSearch(value);
}, 1000)
);
}
const goToSearch = async (value) => {
const response = await getData(args);
}
class 个组件
与上述相同,但搜索功能发生变化
const search = (event.target.value) =>{
if (this.state.typingTimeout) {
clearTimeout(this.state.typingTimeout);
}
this.setState({
typingTimeout: setTimeout(()=> {
this.goToSearch(event.target.value);
}, 1000)
});
}
打字稿
setTypingTimeout( window.setTimeout(() => {
goToSearch(value);
}, 1000)
);
使用window.setTimeout
我怎样才能在 React 功能组件去抖动回调中获得实际的 prop 值,它在 React Class 组件中工作,但我不知道如何使用钩子在功能组件中实现这种行为。
import React from "react";
import ReactDOM from "react-dom";
import debounce from "lodash.debounce";
const TestFunc = ({ count, onClick }) => {
const handleClick = debounce(() => {
onClick();
console.log(count);
}, 500);
return (
<div>
<button type="button" onClick={handleClick}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(countFunc + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
当你点击功能组件按钮时,它会将之前的 count
属性值记录到控制台,但它已经通过调用 onClick
处理程序进行了更改,同时 class组件按钮将在 onClick
处理程序递增后记录实际的 count
道具值。那么,我怎样才能在功能组件中获得实际的 prop 值呢?
您需要进行一些更改才能使用 debounced method
和 hook
- 您需要使用
useCallback
挂钩,以便在初始渲染时只创建一次去抖功能。 - 现在,如果您必须确保 debounced 在执行时获得正确的计数值,您需要将其作为参数传递,否则它将在创建时使用其封闭闭包中的值,即初始计数值。
- 您需要使用父组件中的回调模式更新 onClick 方法调用的计数值,例如
setCountFunc(count => count + 1)
,以便子组件 re-render 具有更新后的值
下面的工作演示
const TestFunc = ({ count, onClick }) => {
const handleClick = React.useCallback((count) =>{
const click = _.debounce((count) => {
onClick();
console.log(count);
}, 500)
click(count);
}, []);
console.log(count, 'render');
return (
<div>
<button type="button" onClick={() => handleClick(count)}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = _.debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(count => count + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root" />
这是一个简单的去抖挂钩(用 TypeScript 编写)
import { useEffect, useRef } from "react";
export function useDebouncedCallback<A extends any[]>(
callback: (...args: A) => void,
wait: number
) {
// track args & timeout handle between calls
const argsRef = useRef<A>();
const timeout = useRef<ReturnType<typeof setTimeout>>();
function cleanup() {
if(timeout.current) {
clearTimeout(timeout.current);
}
}
// make sure our timeout gets cleared if
// our consuming component gets unmounted
useEffect(() => cleanup, []);
return function debouncedCallback(
...args: A
) {
// capture latest args
argsRef.current = args;
// clear debounce timer
cleanup();
// start waiting again
timeout.current = setTimeout(() => {
if(argsRef.current) {
callback(...argsRef.current);
}
}, wait);
};
}
您的用例示例:
const handleClick = useDebouncedCallback(() => {
onClick();
console.log(count);
}, 500);
...
<button type="button" onClick={handleClick}>
Func: {count}
</button>
也适用于传递参数的情况:
const handleChange = useDebouncedCallback((event) => {
console.log(event.currentTarget.value);
}, 500);
<input onChange={handleChange}/>
去抖API调用功能组件
<input
type="text"
placeholder="Search"
onChange={(e) => search(e.target.value)}
/>
=========================================================================
const [typingTimeout, setTypingTimeout] = useState(0);
const search = async (value) => {
if (typingTimeout) {
clearTimeout(typingTimeout);
}
setTypingTimeout( setTimeout(() => {
goToSearch(value);
}, 1000)
);
}
const goToSearch = async (value) => {
const response = await getData(args);
}
class 个组件 与上述相同,但搜索功能发生变化
const search = (event.target.value) =>{
if (this.state.typingTimeout) {
clearTimeout(this.state.typingTimeout);
}
this.setState({
typingTimeout: setTimeout(()=> {
this.goToSearch(event.target.value);
}, 1000)
});
}
打字稿
setTypingTimeout( window.setTimeout(() => {
goToSearch(value);
}, 1000)
);
使用window.setTimeout