Debounce

debounce 原意消除抖动,对于事件触发频繁的场景,只有最后由程序控制的事件是有效的。

防抖函数,我们需要做的是在一件事触发的时候设置一个定时器使事件延迟发生,在定时器期间事件再次触发的话则清除重置定时器,直到定时器到时仍不被清除,事件才真正发生。

const debounce = (fun, delay) => {
let timer;
return (...params) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fun(...params);
}, delay);
};
};

如果事件发生使一个变量频繁变化,那么使用debounce可以降低修改次数。通过传入修改函数,获得一个新的修改函数来使用。

如果是class组件,新函数可以挂载到组件this上,但是函数式组件局部变量每次render都会创建,debounce失去作用,这时需要通过useRef来保存成员函数(下文throttle通过useRef保存函数),是不够便捷的,就有了将debounce做成一个hook的必要。

function useDebounceHook(value, delay) {
const [debounceValue, setDebounceValue] = useState(value);
useEffect(() => {
let timer = setTimeout(() => setDebounceValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounceValue;
}

在函数式组件中,可以将目标变量通过useDebounceHook转化一次,只有在满足delay的延迟之后,才会触发,在delay期间的触发都会重置计时。

配合useEffect,在debounce value改变之后才会做出一些动作。下面的text这个state频繁变化,但是依赖的是debounceText,所以引发的useEffect回调函数却是在指定延迟之后才会触发。

const [text,setText]=useState('');
const debounceText = useDebounceHook(text, 2000);
useEffect(() => {
// ...
console.info("change", debounceText);
}, [debounceText]); function onChange(evt){
setText(evt.target.value)
}

上面一个搜索框,输入完成1秒(指定延迟)后才触发搜索请求,已经达到了防抖的目的。


Throttle

throttle 原意节流阀,对于事件频繁触发的场景,采用的另一种降频策略,一个时间段内只能触发一次。

节流函数相对于防抖函数用在事件触发更为频繁的场景上,滑动事件,滚动事件,动画上。

看一下一个常规的节流函数 (ES6):

function throttleES6(fn, duration) {
let flag = true;
let funtimer;
return function () {
if (flag) {
flag = false;
setTimeout(() => {
flag = true;
}, duration);
fn(...arguments);
// fn.call(this, ...arguments);
// fn.apply(this, arguments); // 运行时这里的 this 为 App组件,函数在 App Component 中运行
} else {
clearTimeout(funtimer);
funtimer = setTimeout(() => {
fn.apply(this, arguments);
}, duration);
}
};
}

(使用...arguments和 call 方法调用展开参数及apply 传入argument的效果是一样的)

扩展:在ES6之前,没有箭头函数,需要手动保留闭包函数中的this和参数再传入定时器中的函数调用:

所以,常见的ES5版本的节流函数:

function throttleES5(fn, duration) {
let flag = true;
let funtimer;
return function () {
let context = this,
args = arguments;
if (flag) {
flag = false;
setTimeout(function () {
flag = true;
}, duration);
fn.apply(context, args); // 暂存上一级函数的 this 和 arguments
} else {
clearTimeout(funtimer);
funtimer = setTimeout(function () {
fn.apply(context, args);
}, duration);
}
};
}

如何将节流函数也做成一个自定义Hooks呢?上面的防抖的Hook其实是对一个变量进行防抖的,从一个不间断频繁变化的变量得到一个按照规则(停止变化delay时间后)才能变化的变量。我们对一个变量的变化进行节流控制,也就是从一个不间断频繁变化的变量指定duration期间只能变化一次(结束后也会变化)的变量

throttle对应的Hook实现:

(标志能否调用值变化的函数的flag变量在常规函数中通过闭包环境来保存,在Hook中通过useRef保存)

function useThrottleValue(value, duration) {
const [throttleValue, setThrottleValue] = useState(value);
let Local = useRef({ flag: true }).current;
useEffect(() => {
let timer;
if (Local.flag) {
Local.flag = false;
setThrottleValue(value);
setTimeout(() => (Local.flag = true), duration);
} else {
timer = setTimeout(() => setThrottleValue(value), duration);
}
return () => clearTimeout(timer);
}, [value, duration, Local]);
return throttleValue;
}

对应的在手势滑动中的使用:

export default function App() {
const [yvalue, setYValue] = useState(0); const throttleValue = useThrottleValue(yvalue, 1000); useEffect(() => {
console.info("change", throttleValue);
}, [throttleValue]); function onMoving(event, tag) {
const touchY = event.touches[0].pageY;
setYValue(touchY);
}
return (
<div
onTouchMove={onMoving}
style={{ width: 200, height: 200, backgroundColor: "#a00" }}
/>
);
}

这样以来,手势的yvalue值一直变化,但是因为使用的是throttleValue,引发的useEffect回调函数已经符合规则被节流,每秒只能执行一次,停止变化一秒后最后执行一次。

对值还是对函数控制

上面的Hooks封装其实对值进行控制的,第一个防抖的例子中,输入的text跟随输入的内容不断的更新state,但是因为useEffect是依赖的防抖之后的值,这个useEffect的执行是符合防抖之后的规则的。

可以将这个防抖规则提前吗? 提前到更新state就是符合防抖规则的,也就是只有指定延迟之后才能将新的value进行setState,当然是可行的。但是这里搜索框的例子并不好,对值变化之后发起的请求可以进行节流,但是因为搜索框需要实时呈现输入的内容,就需要实时的text值。

对手势触摸,滑动进行节流的例子就比较好了,可以通过设置duration来控制频率,给手势值的setState降频,每秒只能setState一次:

export default function App() {
const [yvalue, setYValue] = useState(0);
const Local = useRef({ newMoving: throttleFun(setYValue, 1000) }).current; useEffect(() => {
console.info("change", yvalue);
}, [yvalue]); function onMoving(event, tag) {
const touchY = event.touches[0].pageY;
Local.newMoving(touchY);
}
return (
<div
onTouchMove={onMoving}
style={{ width: 200, height: 200, backgroundColor: "#a00" }}
/>
);
} //常规节流函数
function throttleFun(fn, duration) {
let flag = true;
let funtimer;
return function () {
if (flag) {
flag = false;
setTimeout(() => (flag = true), duration);
fn(...arguments);
} else {
clearTimeout(funtimer);
funtimer = setTimeout(() => fn.apply(this, arguments), duration);
}
};
}

这里就是对函数进行控制了,控制函数setYValue的频率,将setYValue函数传入节流函数,得到一个新函数,手势事件中使用新函数,那么setYValue的调用就符合了节流规则。如果这里依然是对手势值节流的话,其实会有很多的不必要的setYValue执行,这里对setYValue函数进行节流控制显然更好。

需要注意的是,得到的新函数需要通过useRef作为“实例变量”暂存,否则会因为函数组件每次render执行重新创建。

防抖和节流及对应的React Hooks封装的更多相关文章

  1. react hooks 如何自定义组件(react函数组件的封装)

    前言 这里写一下如何封装可复用组件.首先技术栈 react hooks + props-type + jsx封装纯函数组件.类组件和typeScript在这不做讨论,大家别白跑一趟. 接下来会说一下封 ...

  2. React 实现input输入框的防抖和节流

    1.为什么使用防抖和节流对于频繁触发的事件 比如keydown keyup事件 当频繁点击时候 会多次触发事件 页面出现卡顿 影响性能 2.函数防抖(debounce):间隔时间内只执行一次   函数 ...

  3. js高阶函数应用—函数防抖和节流

    高阶函数指的是至少满足下列两个条件之一的函数: 1. 函数可以作为参数被传递:2.函数可以作为返回值输出: javaScript中的函数显然具备高级函数的特征,这使得函数运用更灵活,作为学习js必定会 ...

  4. 通过 React Hooks 声明式地使用 setInterval

    本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...

  5. js防抖和节流

    今天在网上看到的,里面的内容非常多.说下我自己的理解. 所谓的防抖就是利用延时器来使你的最后一次操作执行.而节流是利用时间差的办法,每一段时间执行一次.下面是我的代码: 这段代码是右侧的小滑块跟随页面 ...

  6. 初探React Hooks & SSR改造

    Hooks React v16.8 发布了 Hooks,其主要是解决跨组件.组件复用的状态管理问题. 在 class 中组件的状态封装在对象中,然后通过单向数据流来组织组件间的状态交互.这种模式下,跨 ...

  7. 2019 面试准备 - JS 防抖与节流 (超级 重要!!!!!)

    Hello 小伙伴们,如果觉得本文还不错,记得给个 star , 你们的 star 是我学习的动力!GitHub 地址 本文涉及知识点: 防抖与节流 重绘与回流 浏览器解析 URL DNS 域名解析 ...

  8. 详谈js防抖和节流

    本文由小芭乐发表 0. 引入 首先举一个例子: 模拟在输入框输入后做ajax查询请求,没有加入防抖和节流的效果,这里附上完整可执行代码: <!DOCTYPE html> <html ...

  9. React hooks实践

    前言 最近要对旧的项目进行重构,统一使用全新的react技术栈.同时,我们也决定尝试使用React hooks来进行开发,但是,由于React hooks崇尚的是使用(也只能使用)function c ...

随机推荐

  1. Linux——软件安装

    Linux--软件安装 一.gcc 二.make 三.rpm 四.yum 一.gcc gcc是Linux上面最标准的C语言的编译程序,用来源代码的编译链接. gcc -c hello.c 编译产生目标 ...

  2. snmp协议 及snmpwalk

    推荐阅读: snmp及工具:https://www.jianshu.com/p/dc2dc0222940 snmp协议详解:https://blog.csdn.net/shanzhizi/articl ...

  3. 8.Cisco DHCP中继详解

    1.网络中的终端通过发送DHCP广播的方式来获取IP地址信息.由于VLAN隔离广播,当终端与DHCP服务器不在同一广播域时,就需要用到DHCP中继. 2.DHCP服务的原理概述: DHCP服务器想要给 ...

  4. 8. Linux重要的环境变量

    1.Linux 系统执行命令的4个步骤 第 1 步:判断用户是否以绝对路径或相对路径的方式输入命令(如/bin/ls),如果是的话则直接执行.  第 2 步:Linux 系统检查用户输入的命令是否为& ...

  5. AtCoder Beginner Contest 171

    比赛链接:https://atcoder.jp/contests/abc171/tasks A - αlphabet 题意 给出一个字母,如果为大写输出 'A',如果为小写输出 'a' . 代码 #i ...

  6. cf-1230C Anadi and Domino

    题目链接:http://codeforces.com/contest/1230/problem/C 题意: 有21 个多米诺骨牌,给定一个无向图(无自环,无重边),一条边上可以放一个多米诺骨牌.如果两 ...

  7. 牛客编程巅峰赛S2第7场 - 钻石&王者 A.牛牛的独特子序列 (字符串,二分)

    题意:给你一个字符串,找出一个类似为\(aaabbbccc\)这样的由连续的\(abc\)构成的子序列,其中\(|a|=|b|=|c|\),问字符串中能构造出的子序列的最大长度. 题解:这题刚开始一直 ...

  8. HDU - 4221 贪心

    题意: 你有n个任务,每一个任务有一个完成所需时间AI,和一个截止时间BI.时间从0开始,如果完成任务的时间(设这个时间为ans)大于BI那么就会收到ans-BI的惩罚,问你完成所有这些任务你会收到的 ...

  9. JavaScript——面向对象与原型

    在最外面使用this,此时this是window作用域下的,因此他指向全局变量 对象冒充: 实例属性不会共享!

  10. Codeforces Round #527 (Div. 3) C. Prefixes and Suffixes (思维,字符串)

    题意:给你某个字符串的\(n-1\)个前缀和\(n-1\)个后缀,保证每个所给的前缀后缀长度从\([1,n-1]\)都有,问你所给的子串是前缀还是后缀. 题解:这题最关键的是那两个长度为\(n-1\) ...