防抖节流以及React Hook中的防抖节流
- 一、什么是节流防抖?
- 二、防抖节流实现方式
- 1.防抖实现方式
- 2.节流实现方式
- 三、在React Hooks中使用防抖节流
- React Hooks 防抖
- React Hooks 节流
- 四、Umi Hooks官方防抖节流
一、什么是节流防抖?
- 防抖:触发事件后在n秒内函数只能执行一次,倘若在n秒内再次触发事件,则会重新计算函数执行时间。比如input输入框的change事件,在600ms内触发一次,如果在300ms时input框的内容再次发生变化,那么change事件将在600ms后触发。
- 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发送一次请求。
二、防抖节流实现方式
1.防抖实现方式
代码如下:
/**
* desc 防抖函数
* @param {*} func 函数
* @param {*} wait 延迟毫秒数
* @returns
*/
function debounce(func,wait){
let timer;
return function(){
const _this = this; // 解决 setTimeout中this的指向问题
let arg = arguments; // 解决 func中参数丢失的问题
if(timer) clearTimeout(timer); // 每次执行的时候把前一个 setTimeout clear 掉
timer = setTimeout(() => {
func.apply(_this,arg)
},wait)
}
}
此时函数不能立即执行,增加immediate参数,如下:
/**
* desc 防抖函数
* @param {*} func 函数
* @param {*} wait 延迟毫秒数
* @param {*} immediate true 立即执行, false 非立即执行
* @returns
*/
function debounce(func,wait,immediate){ // 函数不能立即执行,增加参数immediate
let timer = null;
return function(){
const _this = this; // 解决 setTimeout中this的指向问题
let arg = arguments; // 解决 func中参数丢失的问题
if(timer) clearTimeout(timer);
if(immediate){ // 使函数立即执行
let runNow = !timer;
timer = setTimeout(function () {
timer = null;
},wait)
if(runNow){
func.apply(_this,arg)
}
}else{
timer = setTimeout(() => {
func.apply(_this,arg)
},wait)
}
}
}
2.节流实现方式
代码如下:
/**
* desc 节流函数
* @param {*} func 函数
* @param {*} wait 毫秒数
* @returns
*/
function throttle(func, wait) {
let timer;
return function () {
let _this = this;
if (!timer) {
timer = setTimeout(() => {
timer = null;
func.apply(_this, arguments)
}, wait)
}
}
}
三、在React Hooks中使用防抖节流
首先让我们看看在react类组件中使用防抖:
import debounce from 'lodash/debounce'; // 这里使用lodash
export default class Search extends Component {
constructor(props) {
super(props)
this.handleSearch = debounce(this.handleSearch, 500);
}
handleSubmit = (e) => {
e.preventDefault();
this.handleSearch();
}
handleSearch = () => {
...
}
render() {
return (
<form onSubmit={this.handleSubmit}></form>
)
}
}
当我们在函数式组件使用防抖函数时,自然而然想到这么做:
import React, {useState} from 'react';
import debounce from 'lodash/debounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
const Search = () => {
const [userQuery, setUserQuery] = useState("");
const delayedQuery = debounce(q => sendQuery(q), 500);
const onChange = e => {
setUserQuery(e.target.value);
delayedQuery(e.target.value);
};
return (
<div>
<label>Search:</label>
<input onChange={onChange} value={userQuery} />
</div>
);
}
然而,这样做防抖函数并没有发挥作用,仅仅是把函数时间延后了500毫秒。这是因为函数组件每次渲染结束之后,内部的变量都会被释放,重新渲染时所有的变量会被重新初始化,产生的结果就是每一次都注册和执行了setTimeout函数。想要得到正确的运行结果,必须以某种方式存储那些本会被删除的变量和方法的引用。很遗憾,没办法直接使用useState去存储,但是我们可以利用React组件的缓存机制,通过自定义Hook组件去解决这个问题。
React Hooks 防抖
代码如下:
import { useEffect, useCallback, useRef } from 'react';
// 防抖 hooks
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
export default useDebounce;
React Hooks 防抖使用示例,代码如下:
import React, {useState} from 'react';
import useDebounce from '@/components/UseDebounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
const Search = () => {
const [userQuery, setUserQuery] = useState("");
const delayedQuery = useDebounce(q => sendQuery(q), 500);
const onChange = e => {
setUserQuery(e.target.value);
delayedQuery(e.target.value);
};
return (
<div>
<label>Search:</label>
<input onChange={onChange} value={userQuery} />
</div>
);
}
React Hooks 节流
代码如下:
import { useEffect, useCallback, useRef } from 'react';
function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null })
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer
}, delay)
current.fn.call(this, ...args)
}
}, dep)
}
export default useThrottle;
四、Umi Hooks官方防抖节流
Umi 官方提供有hooks的防抖节流方法,可直接使用。
官方示例 (useDebounce 处理值防抖):
import { Input } from 'antd';
import React, { useState } from 'react';
import { useDebounce } from '@umijs/hooks';
export default () => {
const [value, setValue] = useState();
const debouncedValue = useDebounce(value, 500);
return (
<div>
<Input
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Typed value"
style={{ width: 280 }}
/>
<p style={{ marginTop: 16 }}>DebouncedValue: {debouncedValue}</p>
</div>
);
};
官方示例(useDebounceFn 处理函数防抖):
import React, { useState } from 'react';
import { Button } from 'antd';
import { useDebounceFn } from '@umijs/hooks';
export default () => {
const [value, setValue] = useState(0);
const { run } = useDebounceFn(() => {
setValue(value + 1);
}, 500);
return (
<div>
<p style={{ marginTop: 16 }}> Clicked count: {value} </p>
<Button onClick={run}>Click fast!</Button>
</div>
);
};
官方示例 (useThrottle 处理值节流):
import { Input } from 'antd';
import React, { useState } from 'react';
import { useThrottle } from '@umijs/hooks';
export default () => {
const [value, setValue] = useState();
const throttledValue = useThrottle(value, 500);
return (
<div>
<Input
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Typed value"
style={{ width: 280 }}
/>
<p style={{ marginTop: 16 }}>throttledValue: {throttledValue}</p>
</div>
);
};
官方示例(useThrottleFn 处理函数节流):
import React, { useState } from 'react';
import { Button } from 'antd';
import { useThrottleFn } from '@umijs/hooks';
export default () => {
const [value, setValue] = useState(0);
const { run } = useThrottleFn(() => {
setValue(value + 1);
}, 500);
return (
<div>
<p style={{ marginTop: 16 }}> Clicked count: {value} </p>
<Button onClick={run}>Click fast!</Button>
</div>
);
};
Umi Hooks官网 /zh-CN