我创建了这个实时搜索组件:
class SearchEngine extends Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
}
handleChange (e) {
this.props.handleInput(e.target.value); //Redux
}
handleSearch (input, token) {
this.props.handleSearch(input, token) //Redux
};
componentWillUpdate(nextProps) {
if(this.props.input !== nextProps.input){
this.handleSearch(nextProps.input, this.props.loginToken);
}
}
render () {
let data= this.props.result;
let searchResults = data.map(item=> {
return (
<div key={item.id}>
<h3>{item.title}</h3>
<hr />
<h4>by {item.artist}</h4>
<img alt={item.id} src={item.front_picture} />
</div>
)
});
}
return (
<div>
<input name='input'
type='text'
placeholder="Search..."
value={this.props.input}
onChange={this.handleChange} />
<button onClick={() => this.handleSearch(this.props.input, this.props.loginToken)}>
Go
</button>
<div className='search_results'>
{searchResults}
</div>
</div>
)
}
它是React&amp; amp;的一部分。 Redux app我正在工作并连接到Redux商店。 问题是,当用户键入搜索查询时,它会为输入中的每个字符触发API调用,并创建过多的API调用,从而导致显示先前查询结果的错误,而不是跟进当前查询搜索输入。
我的api电话(this.props.handleSearch):
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
fetch(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No items found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}
};
我的意图是它将是一个实时搜索,并根据用户输入进行更新。但我试图找到一种方法等待用户完成输入,然后应用更改以防止过多的API调用和错误。
建议?
修改
这对我有用。 感谢Hammerbot's amazing answer我设法创建了自己的QueueHandler类。
export default class QueueHandler {
constructor () { // not passing any "queryFunction" parameter
this.requesting = false;
this.stack = [];
}
//instead of an "options" object I pass the api and the token for the "add" function.
//Using the options object caused errors.
add (api, token) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
api,
token,
resolve,
reject
});
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
api,
token,
resolve,
reject
};
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true;
// here I call fetch as a default with my api and token
fetch(this.stack[0].api, {
headers: {
'Content-Type': 'application/json',
'Authorization': this.stack[0].token
}
}).then(response => {
this.stack[0].resolve(response);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
})
}
}
我做了一些修改,以便这对我有用(见评论)。
我导入了它并分配了一个变量:
//searchActions.js file which contains my search related Redux actions
import QueueHandler from '../utils/QueueHandler';
let queue = new QueueHandler();
然后在我原来的handleSearch函数中:
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
queue.add(API, loginToken).then... //queue.add instead of fetch.
希望这对任何人都有帮助!
答案 0 :(得分:2)
我认为他们是处理这个问题的几种策略。我将在这里讨论3种方式。
两种第一种方式是&#34;限制&#34;和&#34;去抖&#34;你的意见。这里有一篇非常好的文章解释了不同的技巧:https://css-tricks.com/debouncing-throttling-explained-examples/
Debounce等待给定时间实际执行您要执行的函数。如果在这个给定的时间内你拨打同一个电话,它会在这个给定时间再次等待,看你是否再次拨打电话。如果你不这样做,它将执行该功能。这是用这张图片说明的(取自上面提到的文章):
Throttle直接执行该功能,等待给定时间进行新呼叫并执行在给定时间内进行的最后一次呼叫。以下架构解释了它(摘自本文http://artemdemo.me/blog/throttling-vs-debouncing/):
我最初使用的是第一种技术,但我发现它有一些缺点。主要的是我无法真正控制组件的渲染。
让我们想象以下功能:
function makeApiCall () {
api.request({
url: '/api/foo',
method: 'get'
}).then(response => {
// Assign response data to some vars here
})
}
如您所见,请求使用异步进程,稍后将分配响应数据。现在让我们想象两个请求,我们总是希望使用已完成的最后一个请求的结果。 (这就是你想要的搜索输入)。但第二个请求的结果首先出现在第一个请求的结果之前。这将导致您的数据包含错误的响应:
1. 0ms -> makeApiCall() -> 100ms -> assigns response to data
2. 10ms -> makeApiCall() -> 50ms -> assigns response to data
我的解决方案是创建某种&#34;队列&#34;。此队列的行为是:
1 - 如果我们向队列添加任务,则任务将在队列前面。 2 - 如果我们向队列添加第二个任务,则任务进入第二个位置。 3 - 如果我们向队列添加第三个任务,则该任务将替换第二个任务。
因此队列中最多有两个任务。第一个任务结束后,第二个任务就会执行等......
因此,您始终拥有相同的结果,并且您可以使用许多参数限制api调用。如果用户的互联网连接速度较慢,则第一个请求将需要一些时间来执行,因此不会有很多请求。
以下是我用于此队列的代码:
export default class HttpQueue {
constructor (queryFunction) {
this.requesting = false
this.stack = []
this.queryFunction = queryFunction
}
add (options) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
options,
resolve,
reject
})
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
options,
resolve,
reject
}
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true
this.queryFunction(this.stack[0].options).then(response => {
this.stack[0].resolve(response)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
})
}
}
你可以像这样使用它:
// First, you create a new HttpQueue and tell it what to use to make your api calls. In your case, that would be your "fetch()" function:
let queue = new HttpQueue(fetch)
// Then, you can add calls to the queue, and handle the response as you would have done it before:
queue.add(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No vinyls found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}