【问题标题】:Execute api request when user stops typing search box当用户停止输入搜索框时执行 api 请求
【发布时间】: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);

【问题讨论】:

    标签: javascript reactjs redux react-redux components


    【解决方案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 是的!我不处理类组件,但我确切地知道你想要做什么。我只是没想过,但这确实是实时搜索输入的一个很好的解决方案。
    • 我尝试使用它并将代码添加到我的问题中。出于某种原因,我可以在控制台中看到延迟,但与值的双重绑定会中断...
    • 我刚刚做了并且工作了!谢谢!请参阅此组件的最终代码。 :)!
    猜你喜欢
    • 1970-01-01
    • 2017-07-02
    • 1970-01-01
    • 2020-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-23
    • 2023-04-10
    相关资源
    最近更新 更多