debounce 函数引起的思考
开发中我们经常遇到这种情况,一个输入框内触发onChange事件时调用接口,或者处理某件事情。但是我们就会发现每当我们每当输入一个单词的时候就会发现触发了这个动作,是不是体验不是很友好,如果等用户输入完了在执行那该多好啊,那我们可以在光标离开的时候触发,但是往往事与愿违。产品说我想让他实时搜索,那...。那是不是可以使用延时的方法,比如使用settimeout方法延时执行这个方法,比如延时1秒执行。那我们就可以使用计时器的方式去实现。
- 我们可以看下面那个例子,我们每输入一个字符,控制台就会打印一次。
打开链接,可以看到具体实现
https://codesandbox.io/embed/x9q5nl2lv4
现在我们把让面的代码用下面的替换,然后在看控制台的打印结果。
import React from "react";
import ReactDOM from "react-dom";
import { Input } from "antd";
import "./styles.css";
const Search = Input.Search;
class App extends React.Component {
constructor(props) {
super(props);
this.waitSearch = null;
}
handleOnSearch = e => {
const value = e.target.value;
if (this.waitSearch) {
clearTimeout(this.waitSearch);
}
this.waitSearch = setTimeout(() => console.log(value), 1000);
};
render() {
return (
<div>
<Search
placeholder="input search text"
onChange={this.handleOnSearch}
style={{ width: 200 }}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
我们声明了一个全局变量waitSearch来控制settimeout的执行,在你频繁输入的时候我们将你之前的任务都给清楚掉。这样最后一个action将在你不触发后的某个时间执行。
- 使用过lodash的同学就会发现有一个debounce函数可以满足我们现在的场景,我们将上面的代码替换成下面的代码。
import React from "react";
import ReactDOM from "react-dom";
import { Input } from "antd";
import "./styles.css";
import _ from "lodash";
const Search = Input.Search;
class App extends React.Component {
constructor(props) {
super(props);
this.debouncePrint = _.debounce(this.print, 1000);
}
handleOnSearch = e => {
const value = e.target.value;
this.debouncePrint(value);
};
print = value => {
console.log(value);
};
render() {
return (
<div>
<Search
placeholder="input search text"
onChange={this.handleOnSearch}
style={{ width: 200 }}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
我们看到这句代码,我们将print函数变成了debounce的了。
this.debouncePrint = _.debounce(this.print, 1000);
我们将一个函数传进去,然后返回了一个新的函数。那我们可以使用这个思路自己实现一个自己的debounce。
import React from "react";
import ReactDOM from "react-dom";
import { Input } from "antd";
import "./styles.css";
const Search = Input.Search;
class App extends React.Component {
constructor(props) {
super(props);
this.debouncePrint = this.debounce(this.print, 1000);
}
handleOnSearch = e => {
const value = e.target.value;
this.debouncePrint(value);
};
print = value => {
console.log(value);
};
debounce(fn, time) {
let last = null;
const that = this;
return function(...args) {
if (last) {
clearTimeout(last);
}
last = setTimeout(() => {
fn.apply(that, args);
}, time);
};
}
render() {
return (
<div>
<Search
placeholder="input search text"
onChange={this.handleOnSearch}
style={{ width: 200 }}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
是不是很好用,其实我们可以在进行深入考虑一下,我们知道Decorator修饰器可以修饰一个函数,其实也是高阶函数应用之一,只是修饰器使用了编译特性,将函数上下文注入。如果我们使用装饰器的模式来写一个debouce函数,是不是更加简单哪?
import React from "react";
import ReactDOM from "react-dom";
import { Input } from "antd";
import "./styles.css";
const Search = Input.Search;
const Debounce = (time: number) => {
let last = null;
return (_target, _property, descriptor) => {
const fn = descriptor.value;
descriptor.value = function(...args) {
const that = this;
clearTimeout(last);
last = setTimeout(function() {
fn.apply(that, args);
}, time);
};
return descriptor;
};
};
class App extends React.Component {
handleOnSearch = e => {
const value = e.target.value;
this.print(value);
};
@Debounce(1000)
print(value) {
console.log(value);
}
render() {
return (
<div>
<Search
placeholder="input search text"
onChange={this.handleOnSearch}
style={{ width: 200 }}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
是不是更加简单,我们可以充分使用修饰器的功能,创造出一个个带有特殊功能的函数,让你解放双手。
使用redux-saga
redux-saga这个中间件可以很好的为我们处理异步逻辑,当然也可以帮我们进行debounce。
function* handleInput({ input }) {
// debounce by 500ms
yield call(delay, 500)
...
}
function* watchInput() {
yield takeLatest('INPUT_CHANGED', handleInput);
}
使用redux-observable
redux-observable是以rxjs进行的一个控制异步流程的插件,这个更加强大,当然也更难掌握,需要整个开发团队的水平比较高,下面是rxjs5的写法。
const inputEpic = (action$) =>
action$.ofType('INPUT_CHANGED')
.debounceTime(500)
必备知识
- 高阶函数
- apply函数的使用
- decorator的使用