Обновление: Спасибо за вашу поддержку.Недавно официальный сайт блога был выкинут, чтобы каждый мог его систематически читать.В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть
------ Далее идет текст ------
оригинальныйImprove Your React App Performance by Using Throttling and Debouncing
введение
При создании приложений с помощью React мы всегда сталкиваемся с такими ограничениями, как большое количество вызовов, асинхронные сетевые запросы, обновления DOM и т. д. Мы можем использовать функции, предоставляемые React, для их проверки.
-
shouldComponentUpdate(...)
крючки жизненного цикла React.PureComponent
React.memo
- Windowing and Virtualization
- Memoization
- Hydration
- Hooks (
useState
,useMemo
,useContext
,useReducer
, Ждать)
В этом посте мы посмотрим, как улучшить производительность приложений ACT без использования объектов React предоставляет, мы будем использовать технику, которая не только применима к реакции: дросселирование (Throttle) и защита от сотрясений (Debounce).
начни с примера
Пример 1
Следующий пример может быть хорошим объяснением преимуществ троттлинга и подавления сотрясений.autocomp
компоненты
import React from 'react';
import './autocomp.css';
class autocomp extends React.Component {
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 }))
}
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;
в нашемautocomp
компонента, как только мы введем слово в поле ввода, он запроситapi/users
Получить список пользователей для отображения. После ввода каждой буквы запускайте асинхронный сетевой запрос и передайте успехthis.setState
Обновите ДОМ.
Теперь представьте, что вы вводитеfidudusola
попробуйте результаты поискаfidudusolanke
, будет много имён сfidudusola
появляются вместе.
1. f
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
]
class autocomp extends React.Component {
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 })
}
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>
);
}
}
Пример 2
Другой пример - использоватьresize
а такжеscroll
и т.д. события. В большинстве случаев сайт прокручивается 1000 раз в секунду, представьте себе.scroll
Добавьте обработчик события к событию.
document.body.addEventListener('scroll', ()=> {
console.log('Scrolled !!!')
})
Вы обнаружите, что эта функция выполняется 1000 раз в секунду! Если этот обработчик событий выполняет много вычислений или много манипулирует DOM, он столкнется с худшим случаем.
function longOp(ms) {
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 !!!')
})
У нас есть операция, которая занимает 9 секунд и, наконец, выводитScrolled !!!
, допустим, мы прокручиваем 5000 пикселей, и будет запущено более 200 событий. Таким образом, для события, которое занимает 9 секунд, для выполнения всех 200 событий требуется около 9 * 200 = 1800 секунд. Поэтому на выполнение уйдет 30 минут (полчаса).
Таким образом, вы обязательно найдете тормозящий и не отвечающий браузер, поэтому лучше всего писать обработчики событий, которые выполняются в течение короткого периода времени.
Мы обнаружили, что это создает огромное узкое место в производительности нашего приложения, нам не нужно выполнять запросы API и обновления DOM для каждой набранной буквы, нам нужно ждать, пока пользователь перестанет печатать, или после периода ввода, ждать, пока пользователь останавливает прокрутку или После прокрутки некоторое время выполняется обработчик события.
Все это обеспечивает хорошую производительность для нашего приложения, давайте посмотрим, как мы можем использовать регулирование и стабилизацию, чтобы избежать этого узкого места в производительности.
Дросселирование
Регулирование обеспечивает максимальное количество вызовов функции в течение определенного периода времени, например каждые 100 миллисекунд.
Дросселирование означает однократное выполнение заданной функции в течение заданного времени. Это ограничивает количество вызовов функции, поэтому повторные вызовы функции не сбрасывают данные.
Допустим, мы обычно вызываем функцию 1000 раз/20 секунд. Если мы используем регулирование, чтобы ограничить его каждые 500 мс, мы увидим, что функция будет выполняться 40 раз за 20 секунд.
1000 * 20 secs = 20,000ms
20,000ms / 500ms = 40 times
Это огромная оптимизация с 1000 раз до 40 раз.
Далее будут представлены примеры использования дросселирования в React, которые будут использоваться отдельно.underscore
,lodash
,RxJS
и пользовательские реализации.
использовать подчеркивание
мы будем использоватьunderscore
Предоставленная функция дросселирования обрабатывает нашиautocomp
компоненты.
Сначала установите зависимости.
npm i underscore
Затем импортируйте его в компонент:
// ...
import * as _ from underscore;
class autocomp extends React.Component {
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 })
}
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>
);
}
}
Функция дросселирования получает два параметра: функцию, которую нужно ограничить, и разницу во времени, и возвращает функцию дросселирования. В нашем случаеhandleInput
метод передаетсяthrottle
функция с разницей во времени 1000 мс.
Теперь предположим, что мы вводим фидудудусолу с нормальной скоростью 1 буква каждые 200 мс, для завершения ввода требуется 10 * 200 мс = (2000 мс) 2 с, тогдаhandleInput
Метод будет называться только 2 (2000 мс / 1000 мс = 2) раз вместо начальных 10.
использовать лодаш
lodash
также обеспечиваетthrottle
функцию, мы можем использовать ее в нашей JS-программе.
Во-первых, нам нужно установить зависимости.
npm i lodash
использоватьlodash
,нашautocomp
будет вот так.
// ...
import { throttle } from lodash;
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 })
}
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>
);
}
}
а такжеunderscore
Тот же эффект, никакой другой разницы.
Использование RxJS
в JSReactive Extensions
Предоставляется оператор регулирования, который мы можем использовать для реализации функциональности.
Сначала мы устанавливаемrxjs
.
npm i rxjs
мы начинаем сrxjs
импорт библиотекиthrottle
// ...
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()
}
componentDidMount() {
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>
);
}
}
мы начинаем сrxjs
импортировано вthrottle
а такжеBehaviorSubject
, который инициализируетBehaviorSubject
Экземпляры хранятся вinputStream
свойство, в componentDidMount мы будемinputStream
Поток передается оператору дроссельной заслонки, и передается 1000, что указывает на то, что управление дроссельной заслонкой RxJS составляет 1000 мс, а поток, возвращаемый операцией дроссельной заслонки, подписывается для получения значения потока.
Поскольку inputStream подписывается, когда компонент загружается, когда мы начинаем печатать, ввод отправляется вinputStream
в потоке. В начале из-заthrottle
Оператор не будет отправлять контент в течение 1000 мс, после этого будет отправлено самое последнее значение, и после отправки начнется расчет с получением результата.
Если мы напечатаем 1 букву за 200 мсfidudusola
, компонент будет перерисовывать 2000 мс / 1000 мс = 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();
}
}
Примечание: есть проблема с функцией дросселирования оригинальной кастомной реализации.Подробную реализацию и анализ функции дросселирования можно найти в другой моей статье.Нажмите, чтобы просмотреть
Моя реализация выглядит следующим образом:
// fn 是需要执行的函数
// wait 是时间间隔
const throttle = (fn, wait = 50) => {
// 上一次执行 fn 的时间
let previous = 0
// 将 throttle 处理结果当作函数返回
return function(...args) {
// 获取当前时间,转换成时间戳,单位毫秒
let now = +new Date()
// 将当前时间和上一次执行函数的时间进行对比
// 大于等待时间就把 previous 设置为当前时间并执行函数 fn
if (now - previous > wait) {
previous = now
fn.apply(this, args)
}
}
}
Приведенная выше реализация очень проста и используется в проекте React следующим образом.
// ...
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 })
}
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>
);
}
}
Защита от сотрясений
Отказ вызывает повторный вызов функции по прошествии определенного времени с момента последнего вызова, например, только после того, как прошло определенное количество времени (например, 100 мс) без вызова.
При отклонении он игнорирует все вызовы функции и не выполняется снова, пока функция не прекратит вызов в течение определенного периода времени.
Далее будет представлен пример использования debounce в проекте.
использовать подчеркивание
// ...
import * as _ from 'underscore';
class autocomp extends React.Component {
constructor(props) {
super(props);
this.state = {
results: []
}
this.handleInputThrottled = _.debounce(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 })
}
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>
);
}
}
использовать лодаш
// ...
import { debounce } from 'lodash';
class autocomp extends React.Component {
constructor(props) {
super(props);
this.state = {
results: []
}
this.handleInputThrottled = debounce(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 })
}
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>
);
}
}
Использование RxJS
// ...
import { BehaviorSubject } from 'rxjs';
import { debounce } from 'rxjs/operators';
class autocomp extends React.Component {
constructor(props) {
super(props);
this.state = {
results: []
}
this.inputStream = new BehaviorSubject()
}
componentDidMount() {
this.inputStream
.pipe(
debounce(100)
)
.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>
);
}
}
Важная область: игры
Есть много ситуаций, когда необходимы троттлинг и стабилизация, и область, где они нужны больше всего, — это игры. Чаще всего в играх используются нажатия клавиш на клавиатуре компьютера или геймпада. Игроки часто могут нажимать одну и ту же клавишу несколько раз (40 раз в 20 секунд или 2 раза в секунду) для таких действий, как стрельба и ускорение, но неважно сколько раз игрок нажимает одну и ту же клавишу Сколько раз нажимается кнопка огня, она срабатывает только один раз (скажем, каждую секунду). Поэтому используйте управление дроссельной заслонкой на 1 секунду, чтобы второе нажатие кнопки было проигнорировано.
В заключение
Мы увидели, как регулирование и устранение дребезга могут улучшить производительность приложений React, и как повторные вызовы могут повлиять на производительность, поскольку компоненты и их поддеревья будут перерисовываться без необходимости, поэтому следует избегать повторных вызовов методов в приложениях React. В небольших программах это не будет заметно, но в больших программах эффект будет заметен.
❤️ После прочтения трех вещей
Если вы найдете этот контент вдохновляющим, я хотел бы пригласить вас сделать мне три небольших одолжения:
- подобно, чтобы этот контент могли увидеть больше людей (Если тебе это не нравится, ты хулиган -_-)
- Подписывайтесь на меняGitHub, пусть будут долгосрочные отношения
- Обратите внимание на публичный аккаунт "Advanced Front-end Advanced",Мы сосредотачиваемся на том, чтобы каждую неделю преодолевать сложные внешние собеседования, и официальная учетная запись будет отвечать на «данные» в фоновом режиме, чтобы отправлять вам выбранные высококачественные внешние данные.