当用户停止输入搜索框时执行 api 请求
Posted
技术标签:
【中文标题】当用户停止输入搜索框时执行 api 请求【英文标题】:Execute api request when user stops typing search box 【发布时间】:2021-05-29 08:44:53 【问题描述】:我正在构建一个搜索字段,该字段根据用户输入从数据库中获取,我有点挣扎。目前,它在每次击键时都在获取数据,这并不理想。我查看了不同的答案,似乎最好的选择是在 componentDidUpdate() 中执行此操作并获取输入感觉的 ref 以通过 setTimeout() 将其与当前值进行比较。
我已经尝试过了,但每次击键时我仍然在获取,不知道为什么?请参阅下面的组件示例:
class ItemsHolder extends Component
componentDidMount()
//ensures the page is reloaded at the top when routing
window.scrollTo(0, 0);
this.props.onFetchItems(this.props.search);
componentDidUpdate(prevProps, prevState)
if (prevProps.search !== this.props.search)
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
setTimeout(() =>
console.log(
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
);
if (
this.props.search ===
this.props.searchRef.current.props.value.toUpperCase()
)
this.props.onFetchItems(this.props.search, this.props.category);
, 500);
我正在使用 Redux 进行状态管理。这是获取项目时调用的函数:
export const fetchItemsFromServer = (search) =>
return (dispatch) =>
dispatch(fetchItemsStart());
const query =
search.length === 0 ? '' : `?orderBy="country"&equalTo="$search"`;
axios
.get('/items.json' + query)
.then((res) =>
const fetchedItems = [];
for (let item in res.data)
fetchedItems.push(
...res.data[item],
id: item,
);
dispatch(fetchItemsSuccess(fetchedItems));
)
.catch((error) =>
dispatch(fetchItemsFail(error));
);
;
;
这就是我在搜索组件中设置 ref 的方式:
class Search extends Component
constructor(props)
super(props);
this.searchInput = React.createRef();
componentDidMount()
this.props.onSetRef(this.searchInput);
render()
return (
<Input
ref=this.searchInput
toolbar
elementType=this.props.inputC.elementType
elementConfig=this.props.inputC.elementConfig
value=this.props.inputC.value
changed=(event) => this.props.onChangedHandler(event)
/>
);
根据教程,我发现这应该可行。供您参考,请参阅本教程中的代码。我不明白为什么上述方法不起作用。唯一的区别是本教程使用了钩子。
const Search = React.memo(props =>
const onLoadIngredients = props;
const [enteredFilter, setEnteredFilter] = useState('');
const inputRef = useRef();
useEffect(() =>
const timer = setTimeout(() =>
if (enteredFilter === inputRef.current.value)
const query =
enteredFilter.length === 0
? ''
: `?orderBy="title"&equalTo="$enteredFilter"`;
fetch(
'https://react-hooks-update.firebaseio.com/ingredients.json' + query
)
.then(response => response.json())
.then(responseData =>
const loadedIngredients = [];
for (const key in responseData)
loadedIngredients.push(
id: key,
title: responseData[key].title,
amount: responseData[key].amount
);
onLoadIngredients(loadedIngredients);
);
, 500);
return () =>
clearTimeout(timer);
;
, [enteredFilter, onLoadIngredients, inputRef]);
遵循 debounceInput 的建议:
import React, Component from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import connect from 'react-redux';
class Search extends Component
componentDidUpdate(prevProps, prevState)
if (prevProps.search !== this.props.search)
this.props.onFetchItems(this.props.search, this.props.category);
debounceInput = (fn, delay) =>
let timerId;
return (...args) =>
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
;
;
render()
return (
<Input
toolbar
elementType=this.props.inputC.elementType
elementConfig=this.props.inputC.elementConfig
value=this.props.inputC.value
changed=(event) =>
this.debounceInput(this.props.onChangedHandler(event), 500)
/>
);
const mapStateToProps = (state) =>
return
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
;
;
const mapDispatchToProps = (dispatch) =>
return
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
;
;
export default connect(mapStateToProps, mapDispatchToProps)(Search);
这里是帮助后的最终解决方案:
import React, Component from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import connect from 'react-redux';
const debounceInput = (fn, delay) =>
let timerId;
return (...args) =>
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
;
;
class Search extends Component
componentDidUpdate(prevProps, _prevState)
if (prevProps.search !== this.props.search)
this.responseHandler();
responseHandler = debounceInput(() =>
this.props.onFetchItems(this.props.search, this.props.category);
, 1000);
render()
return (
<Input
toolbar
elementType=this.props.inputC.elementType
elementConfig=this.props.inputC.elementConfig
value=this.props.inputC.value
changed=(event) => this.props.onChangedHandler(event)
/>
);
const mapStateToProps = (state) =>
return
inputC: state.filtersR.inputConfig,
search: state.filtersR.search,
;
;
const mapDispatchToProps = (dispatch) =>
return
onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
onFetchItems: (search, category) =>
dispatch(actions.fetchItemsFromServer(search, category)),
;
;
export default connect(mapStateToProps, mapDispatchToProps)(Search);
【问题讨论】:
【参考方案1】:您真的只需要消除输入的 onChange
处理程序,或者更好的是,实际执行异步工作的函数。
非常简单的去抖高阶函数:
const debounce = (fn, delay) =>
let timerId;
return (...args) =>
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
;
使用示例:
fetchData = debounce(() => fetch(.....).then(....), 500);
componentDidUpdate(.......)
// input value different, call fetchData
<Input
toolbar
elementType=this.props.inputC.elementType
elementConfig=this.props.inputC.elementConfig
value=this.props.inputC.value
changed=this.props.onChangedHandler
/>
演示代码
const debounce = (fn, delay) =>
let timerId;
return (...args) =>
clearTimeout(timerId);
timerId = setTimeout(fn, delay, [...args]);
;
;
const fetch = (url, options) =>
console.log("Fetching", url);
return new Promise((resolve) =>
setTimeout(() =>
console.log("Fetch Resolved");
resolve(`response - $Math.floor(Math.random() * 1000)`);
, 2000);
);
;
export default class App extends Component
state =
search: "",
response: ""
;
changeHandler = (e) =>
const value = e.target;
console.log("search", value);
this.setState( search: value );
;
fetchData = debounce(() =>
const search = this.state;
const query = search.length ? `?orderBy="country"&equalTo="$search"` : "";
fetch(
"https://react-hooks-update.firebaseio.com/ingredients.json" + query
).then((response) => this.setState( response ));
, 500);
componentDidUpdate(prevProps, prevState)
if (prevState.search !== this.state.search)
if (this.state.response)
this.setState( response: "" );
this.fetchData();
render()
const response, search = this.state;
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<label>
Search
<input type="text" value=search onChange=this.changeHandler />
</label>
<div>Debounced Response: response</div>
</div>
);
【讨论】:
这是一个优雅的解决方案!我可能会实现这一点。我总是让它在更改时更新,但延迟到他们停止输入可能会更好。好问题和好答案。 @wjpayne83 谢谢!是的,我在创建演示时就想到了,去抖动 onChange 回调会阻止状态更新,但是通过依赖于更新状态的useEffect
去抖动异步逻辑应该是一件小事。这将允许受控输入而不是阻止状态更新。有机会我会更新沙盒演示。
@Drew Reese 是的!我不处理类组件,但我确切地知道你想要做什么。我只是没想过,但这确实是实时搜索输入的一个很好的解决方案。
我尝试使用它并将代码添加到我的问题中。出于某种原因,我可以在控制台中看到延迟,但与值的双重绑定会中断...
我刚刚做了并且工作了!谢谢!请参阅此组件的最终代码。 :)!以上是关于当用户停止输入搜索框时执行 api 请求的主要内容,如果未能解决你的问题,请参考以下文章