进阶 6-5 期[译] Throttle 和 Debounce 在 React 中的应用
Posted 程序员依扬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶 6-5 期[译] Throttle 和 Debounce 在 React 中的应用相关的知识,希望对你有一定的参考价值。
本篇是译文,原文是 Improve Your React App Performance by Using Throttling and Debouncing
引言
使用 React 构建应用程序时,我们总是会遇到一些限制问题,比如大量的调用、异步网络请求和 DOM 更新等,我们可以使用 React 提供的功能来检查这些。
在这篇文章中,我们将研究如何在不使用 React 提供的功能下来改进 React 应用程序性能,我们将使用一种不仅仅适用于 React 的技术:节流(Throttle)和防抖(Debounce)。
从一个例子开始
例子 1
下面这个例子可以很好的解释节流和防抖带给我们的好处,假设我们有一个 autocomp
组件
import React from 'react';import './autocomp.css';from 'react';
import './autocomp.css';
class autocomp extends React.Component constructor(props) super(props); this.state= results: []
constructor(props)
super(props);
this.state=
results: []
handleInput = evt => const value = evt.target.value fetch(`/api/users`) .then(res => res.json()) .then(result => this.setState( results: result.users ))
const value = evt.target.value
fetch(`/api/users`)
.then(res => res.json())
.then(result => this.setState( results: result.users ))
render() let results = this.state; return ( <div className='autocomp_wrapper'> <input placeholder="Enter your search.." onChange=this.handleInput /> <div> results.map(item=>item) </div> </div> ); export default autocomp;let results = this.state;
return (
<div className='autocomp_wrapper'>
<input placeholder="Enter your search.." onChange=this.handleInput />
<div>
results.map(item=>item)
</div>
</div>
);
export default autocomp;
在我们的 autocomp
组件中,一旦我们在输入框中输入一个单词,它就会请求 api/users
获取要显示的用户列表。 在每个字母输入后,触发异步网络请求,并且成功后通过 this.setState
更新DOM。
现在,想象一下输入 fidudusola
尝试搜索结果 fidudusolanke
,将有许多名称与 fidudusola
一起出现。
1. f2. fi3. fid4. fidu5. fidud6. fidudu7. fidudus8. fiduduso9. fidudusol10. fidudusola
2. fi
3. fid
4. fidu
5. fidud
6. fidudu
7. fidudus
8. fiduduso
9. fidudusol
10. fidudusola
这个名字有 10 个字母,所以我们将有 10 次 API 请求和 10 次 DOM 更新,这只是一个用户而已!! 输入完成后最终看到我们预期的名字 fidudusolanke
和其他结果一起出现。
即使 autocomp
可以在没有网络请求的情况下完成(例如,内存中有一个本地“数据库”),仍然需要为输入的每个字符/单词进行昂贵的 DOM 更新。
const data = [ name: 'nnamdi' , name: 'fidudusola' , name: 'fashola' , name: 'fidudusolanke' , // ... up to 10,000 records]
name: 'nnamdi'
,
name: 'fidudusola'
,
name: 'fashola'
,
name: 'fidudusolanke'
,
// ... up to 10,000 records
]
class autocomp extends React.Component constructor(props) super(props); this.state= results: []
constructor(props)
super(props);
this.state=
results: []
handleInput = evt => const value = evt.target.value const filteredRes = data.filter((item)=> // algorithm to search through the `data` array ) this.setState( results: filteredRes )
const value = evt.target.value
const filteredRes = data.filter((item)=>
// algorithm to search through the `data` array
)
this.setState( results: filteredRes )
render() let results = this.state; return ( <div className='autocomp_wrapper'> <input placeholder="Enter your search.." onChange=this.handleInput /> <div> results.map(result=>result) </div> </div> ); let results = this.state;
return (
<div className='autocomp_wrapper'>
<input placeholder="Enter your search.." onChange=this.handleInput />
<div>
results.map(result=>result)
</div>
</div>
);
例子 2
另一个例子是使用 resize
和 scroll
等事件。大多数情况下,网站每秒滚动 1000 次,想象一下在 scroll
事件中添加一个事件处理。
document.body.addEventListener('scroll', ()=> console.log('Scrolled !!!'))'scroll', ()=>
console.log('Scrolled !!!')
)
你会发现这个函数每秒被执行 1000 次!如果这个事件处理函数执行大量计算或大量 DOM 操作,将面临最坏的情况。
function longOp(ms) var now = Date.now() var end = now + ms while(now < end) now = Date.now()
var now = Date.now()
var end = now + ms
while(now < end)
now = Date.now()
document.body.addEventListener('scroll', ()=> // simulating a heavy operation longOp(9000) console.log('Scrolled !!!'))'scroll', ()=>
// simulating a heavy operation
longOp(9000)
console.log('Scrolled !!!')
)
我们有一个需要 9 秒才能完成的操作,最后输出 Scrolled !!!
,假设我们滚动 5000 像素会有 200 多个事件被触发。 因此,需要 9 秒才能完成一次的事件,大约需要 9 * 200 = 1800s 来运行全部的 200 个事件。 因此,全部完成需要 30 分钟(半小时)。
所以肯定会发现一个滞后且无响应的浏览器,因此编写的事件处理函数最好在较短的时间内执行完成。
我们发现这会在我们的应用程序中产生巨大的性能瓶颈,我们不需要在输入的每个字母上执行 API 请求和 DOM 更新,我们需要等到用户停止输入或者输入一段时间之后,等到用户停止滚动或者滚动一段时间之后,再去执行事件处理函数。
所有这些确保我们的应用程序有良好性能,让我们看看如何使用节流和防抖来避免这种性能瓶颈。
节流 Throttle
节流强制一个函数在一段时间内可以调用的最大次数,例如每 100 毫秒最多执行一次函数。
节流是指在指定的时间内执行一次给定的函数。这限制了函数被调用的次数,所以重复的函数调用不会重置任何数据。
假设我们通常以 1000 次 / 20 秒的速度调用函数。 如果我们使用节流将它限制为每 500 毫秒执行一次,我们会看到函数在 20 秒内将执行 40 次。
1000 * 20 secs = 20,000ms20,000ms / 500ms = 40 times20 secs = 20,000ms
20,000ms / 500ms = 40 times
这是从 1000 次到 40 次的极大优化。
下面将介绍在 React 中使用节流的例子,将分别使用 underscore
、 lodash
、RxJS
以及自定义实现。
使用 underscore
我们将使用 underscore
提供的节流函数处理我们的 autocomp
组件。
先安装依赖。
npm i underscore
然后在组件中导入它:
// ...import * as _ from underscore;
import * as _ from underscore;
class autocomp extends React.Component constructor(props) super(props); this.state = results: [] this.handleInputThrottled = _.throttle(this.handleInput, 1000)
constructor(props)
super(props);
this.state =
results: []
this.handleInputThrottled = _.throttle(this.handleInput, 1000)
handleInput = evt => const value = evt.target.value const filteredRes = data.filter((item)=> // algorithm to search through the `data` array ) this.setState( results: filteredRes )
const value = evt.target.value
const filteredRes = data.filter((item)=>
// algorithm to search through the `data` array
)
this.setState( results: filteredRes )
render() let results = this.state; return ( <div className='autocomp_wrapper'> <input placeholder="Enter your search.." onChange=this.handleInputThrottled /> <div> results.map(result=>result) </div> </div> ); let results = this.state;
return (
<div className='autocomp_wrapper'>
<input placeholder="Enter your search.." onChange=this.handleInputThrottled />
<div>
results.map(result=>result)
</div>
</div>
);
节流函数接收两个参数,分别是需要被限制的函数和时间差,返回一个节流处理后的函数。 在我们的例子中,handleInput
方法被传递给 throttle
函数,时间差为 1000ms。
现在,假设我们以每 200ms 1 个字母的正常速度输入 fidudusola,输入完成需要10 * 200ms =(2000ms)2s,这时 handleInput
方法将只调用 2(2000ms / 1000ms = 2)次而不是最初的 10 次。
使用 lodash
lodash
也提供了一个 throttle
函数,我们可以在 JS 程序中使用它。
首先,我们需要安装依赖。
npm i lodash
使用 lodash
,我们的 autocomp
将是这样的。
// ...import throttle from lodash;
import throttle from lodash;
class autocomp extends React.Component constructor(props) super(props); this.state = results: [] this.handleInputThrottled = throttle(this.handleInput, 100)
constructor(props)
super(props);
this.state =
results: []
this.handleInputThrottled = throttle(this.handleInput, 100)
handleInput = evt => const value = evt.target.value const filteredRes = data.filter((item)=> // algorithm to search through the `data` array ) this.setState( results: filteredRes )
const value = evt.target.value
const filteredRes = data.filter((item)=>
// algorithm to search through the `data` array
)
this.setState( results: filteredRes )
render() let results = this.state; return ( <div className='autocomp_wrapper'> <input placeholder="Enter your search.." onChange=this.handleInputThrottled /> <div> results.map(result=>result) </div> </div> ); let results = this.state;
return (
<div className='autocomp_wrapper'>
<input placeholder="Enter your search.." onChange=this.handleInputThrottled />
<div>
results.map(result=>result)
</div>
</div>
);
和 underscore
一样的效果,没有其他区别。
使用 RxJS
JS 中的 Reactive Extensions
提供了一个节流运算符,我们可以使用它来实现功能。
首先,我们安装 rxjs
。
npm i rxjs
我们从 rxjs
库导入 throttle
// ...import BehaviorSubject from 'rxjs';import throttle from 'rxjs/operators';
import BehaviorSubject from 'rxjs';
import throttle from 'rxjs/operators';
class autocomp extends React.Component constructor(props) super(props); this.state = results: [] this.inputStream = new BehaviorSubject()
constructor(props)
super(props);
this.state =
results: []
this.inputStream = new BehaviorSubject()
componentDidMount() this.inputStream .pipe( throttle(1000) ) .subscribe(v => const filteredRes = data.filter((item)=> // algorithm to search through the `data` array ) this.setState( results: filteredRes ) ) this.inputStream
.pipe(
throttle(1000)
)
.subscribe(v =>
const filteredRes = data.filter((item)=>
// algorithm to search through the `data` array
)
this.setState( results: filteredRes )
)
render() let results = this.state; return ( <div className='autocomp_wrapper'> <input placeholder="Enter your search.." onChange=e => this.inputStream.next(e.target.value) /> <div> results.map(result => result ) </div> </div> ); let results = this.state;
return (
<div className='autocomp_wrapper'>
<input placeholder="Enter your search.." onChange=e => this.inputStream.next(e.target.value) />
<div>
results.map(result => result )
</div>
</div>
);
我们从 rxjs
中导入了 throttle
和 BehaviorSubject
,初始化了一个 BehaviorSubject
实例保存在 inputStream
属性,在 componentDidMount 中,我们将 inputStream
流传递给节流操作符,传入 1000,表示 RxJS 节流控制为 1000ms,节流操作返回的流被订阅以获得流值。
因为在组件加载时订阅了 inputStream,所以我们开始输入时,输入的内容就被发送到 inputStream
流中。 刚开始时,由于 throttle
操作符 1000ms 内不会发送内容,在这之后发送最新值, 发送之后就开始计算得到结果。
如果我们以 200ms 1 个字母的速度输入 fidudusola
,该组件将重新渲染 2000ms / 1000ms = 2次。
使用自定义实现
我们实现自己的节流函数,方便更好的理解节流如何工作。
我们知道在一个节流控制的函数中,它会根据指定的时间间隔调用,我们将使用 setTimeout 函数实现这一点。
function throttle(fn, ms) let timeout function exec() fn.apply() function clear() timeout == undefined ? null : clearTimeout(timeout) if(fn !== undefined && ms !== undefined) timeout = setTimeout(exec, ms) else console.error('callback function and the timeout must be supplied') // API to clear the timeout throttle.clearTimeout = function() clear();
let timeout
function exec()
fn.apply()
function clear()
timeout == undefined ? null : clearTimeout(timeout)
if(fn !== undefined && ms !== undefined)
timeout = setTimeout(exec, ms)
else
console.error('callback function and the timeout must be supplied')
// API to clear the timeout
throttle.clearTimeout = function()
clear();
上面的实现非常简单,直接使用 setTimeout API 实现,在 React 项目中使用方式如下。
// ...class autocomp extends React.Component constructor(props) super(props); this.state = results: [] this.handleInputThrottled = throttle(this.handleInput, 100)
class autocomp extends React.Component
constructor(props)
super(props);
this.state =
results: []
this.handleInputThrottled = throttle(this.handleInput, 100)
handleInput = evt => const value = evt.target.value const filteredRes = data.filter((item)=> // algorithm to search through the `data` array ) this.setState( results: filteredRes )
const value = evt.target.value
const filteredRes = data.filter((item)=>
// algorithm to search through the `data` array
)
<以上是关于进阶 6-5 期[译] Throttle 和 Debounce 在 React 中的应用的主要内容,如果未能解决你的问题,请参考以下文章