Lodash debounce TypeError: ожидается реакция функции

Я пытаюсь использовать функцию debounce, чтобы избежать множественных вызовов, когда я печатаю и ищу что-то в таблице данных. То, что я делаю сейчас, находится на входе

onChange={(e) => {
            const delayedQuery = useCallback(debounce(this.handleSearch(e.target.value), 500));
            return delayedQuery
          }}

где handeSearch

handleSearch(filter) {
      service.getData(filter).subscribe((data) => {console.log(data)})
    }

но у меня эта ошибка TypeError: Expected a function. Сервис работает, но дебаунда нет. Он пишет посимвольно, когда я набираю текст, а это неверно.


person Atlas91    schedule 13.11.2020    source источник
comment
Похоже, что this.handleSearch(e.target.value) не выполняет функцию. Должен ли он? Или вам следует преобразовать это в ссылку на функцию?   -  person VLAZ    schedule 13.11.2020
comment
ну, handleSearch - это функция, которую я вызываю, когда ввожу ввод, который фактически запускает службу с новыми данными. Нехорошо?   -  person Atlas91    schedule 13.11.2020
comment
В дополнение к тому, что сказал @VLAZ, вы не сможете использовать React.useCallback внутри обратного вызова   -  person ypahalajani    schedule 13.11.2020
comment
debounce принимает в качестве параметра ссылку на функцию. Однако вы предоставляете this.handleSearch(e.target.value) - результат вызова метода. Результатом вызова этого конкретного метода будет undefined, поскольку у вас нет явного return оператора. Итак, вам нужно либо изменить handleSearch, чтобы вернуть функцию, либо вы измените этот вызов функции на обратный вызов.   -  person VLAZ    schedule 13.11.2020
comment
@ End.Game Вы не можете использовать ловушку useCallback, потому что вы не можете использовать никакие ловушки в компонентах класса.   -  person HMR    schedule 13.11.2020


Ответы (1)


Вы не можете использовать хуки в компонентах класса или в функциях обработчика событий, и вам нужно передать функцию в lodash debounce.

Также; если вы попытаетесь асинхронно прочитать target.value из события, вы получите это синтетическое событие повторно из соображений производительности.

В чате я предполагаю, что вы используете rxjs from(promise), поэтому у вас все еще есть проблема, заключающаяся в том, что когда пользователь вводит и вы запускаете асинхронный запрос, они могут разрешаться в другом порядке.

Ниже приведен пример неработающего кода (быстро введите hai и подождите 2 секунды, он разрешится до ha)

//simple debounce implementation
const debounce = (fn, delay = 50) => {
  let time;
  return (...args) => {
    clearTimeout(time);
    time = setTimeout(() => fn(...args), delay);
  };
};
//service where you can subscribe to
const service = (() => {
  let listeners = [];
  const later = (value, time = 50) =>
    new Promise((r) =>
      //if search is 2 characters long it'll resolve much later
      setTimeout(
        () => r(value),
        value.length === 2 ? 2000 : time
      )
    );
  //subscribe returns an unsubscribe function
  const subScribe = (fn) => {
    listeners.push(fn);
    //return the Subscriber object
    //https://rxjs-dev.firebaseapp.com/api/index/class/Subscriber
    return {
      unsubscribe: () =>
        (listeners = listeners.filter(
          (listener) => listener !== fn
        )),
    };
  };
  //notify all listeners
  const notify = (value) =>
    listeners.forEach((listener) => listener(value));
  return {
    getData: (value) => ({
      subScribe: (fn) => {
        const unsub = subScribe(fn);
        later(value).then((value) => notify(value));
        return unsub;
      },
    }),
  };
})();

class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = { asyncSearchResult: '' };
    //use handleSearch to create a debounced version of it
    this.handleSearchDebounced = debounce(
      this.handleSearch
    ).bind(this); //you need to bind it to guearantee correct context
  }
  rendered = 0;
  handleSearch = (value) =>
    service
      .getData(value)
      .subScribe((value) =>
        this.setState({ asyncSearchResult: value })
      );
  render() {
    return (
      <div>
        <input
          type="text"
          // pass value to the handler, not the event
          onChange={(e) =>
            this.handleSearchDebounced(e.target.value)
          }
        />
        async result: {this.state.asyncSearchResult}
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Вот решение:

//simple debounce implementation
const debounce = (fn, delay = 50) => {
  let time;
  return (...args) => {
    clearTimeout(time);
    time = setTimeout(() => fn(...args), delay);
  };
};
//service where you can subscribe to
const service = (() => {
  let listeners = [];
  const later = (value, time = 50) =>
    new Promise((r) =>
      //if search is 2 characters long it'll resolve much later
      setTimeout(
        () => r(value),
        value.length === 2 ? 2000 : time
      )
    );
  //subscribe returns an unsubscribe function
  const subScribe = (fn) => {
    listeners.push(fn);
    //return the Subscriber object
    //https://rxjs-dev.firebaseapp.com/api/index/class/Subscriber
    return {
      unsubscribe: () =>
        (listeners = listeners.filter(
          (listener) => listener !== fn
        )),
    };
  };
  //notify all listeners
  const notify = (value) =>
    listeners.forEach((listener) => listener(value));
  return {
    getData: (value) => ({
      subScribe: (fn) => {
        const unsub = subScribe(fn);
        later(value).then((value) => notify(value));
        return unsub;
      },
    }),
  };
})();
const latestGetData = (value) => {
  const shared = {};
  return {
    subScribe: (fn) => {
      const current = {};
      shared.current = current;
      const subscriber = service
        .getData(value)
        .subScribe((result) => {
          //only call subscriber of latest resolved
          if (shared.current === current) {
            fn(result);
          }
          subscriber.unsubscribe();
        });
    },
  };
};
class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = { asyncSearchResult: '' };
    //use handleSearch to create a debounced version of it
    this.handleSearchDebounced = debounce(
      this.handleSearch
    ).bind(this); //you need to bind it to guearantee correct context
  }
  rendered = 0;
  handleSearch = (value) =>
    latestGetData(value).subScribe((value) =>
      this.setState({ asyncSearchResult: value })
    );
  render() {
    return (
      <div>
        <input
          type="text"
          // pass value to the handler, not the event
          onChange={(e) =>
            this.handleSearchDebounced(e.target.value)
          }
        />
        async result: {this.state.asyncSearchResult}
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

person HMR    schedule 13.11.2020
comment
Спасибо. Я понял тебя, и теперь мой код работает хорошо. Извините, но я новичок в реакции - person Atlas91; 13.11.2020
comment
Неа. Это постгрес. @HMR скажите мне, если я ошибаюсь, если я не использую bind (this), handleSearch может иметь this undefined, например? - person Atlas91; 13.11.2020
comment
Я не знаю, как на самом деле бэкэнд. У меня есть неожиданный токен в rendered = 0 ;. Это возможно? - person Atlas91; 13.11.2020
comment
Также в handleSearch = (значение) в символе =. - person Atlas91; 13.11.2020
comment
Позвольте нам продолжить это обсуждение в чате. - person HMR; 13.11.2020
comment
@ End.Game Обновлен ответ для решения этой проблемы и добавлен поддельный наблюдаемый rxjs, который вы используете в своем коде, который отменит подписку один раз обещание разрешается. - person HMR; 13.11.2020