当用户停止输入搜索框时执行 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 请求的主要内容,如果未能解决你的问题,请参考以下文章

关于页面点击搜索框时,虚拟键盘遮挡搜索框的问题

【js】简单理解节流与防抖,搜索框的场景

Qt 搜索框

点击搜索框时会自动弹出以前的搜索历怎么才能删除 按DEl键不行360清除也不行 修改win7资源管理器也不行

使用 jQuery 在搜索框中输入动画文本

JavaScript_Html5_LocalStorage项目demo