如何进行去抖动?

新手上路,请多包涵

你如何在 React.js 中执行去抖动?

我想去抖动handleOnChange。

我尝试使用 debounce(this.handleOnChange, 200) 但它不起作用。

function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this,
      args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

var SearchBox = React.createClass({
  render: function() {
    return <input type="search" name="p" onChange={this.handleOnChange} />;
  },

  handleOnChange: function(event) {
    // make ajax call
  }
});

原文由 Chetan Ankola 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 545
1 个回答

2019:尝试 hooks + promise 去抖动

这是我如何解决这个问题的最新版本。我会用:

这是一些初始接线,但您正在自己编写原始块,您可以制作自己的自定义挂钩,这样您只需执行一次。

 // Generic reusable hook
const useDebouncedSearch = (searchFunction) => {

  // Handle the input text state
  const [inputText, setInputText] = useState('');

  // Debounce the original search async function
  const debouncedSearchFunction = useConstant(() =>
    AwesomeDebouncePromise(searchFunction, 300)
  );

  // The async callback is run each time the text changes,
  // but as the search function is debounced, it does not
  // fire a new request on each keystroke
  const searchResults = useAsync(
    async () => {
      if (inputText.length === 0) {
        return [];
      } else {
        return debouncedSearchFunction(inputText);
      }
    },
    [debouncedSearchFunction, inputText]
  );

  // Return everything needed for the hook consumer
  return {
    inputText,
    setInputText,
    searchResults,
  };
};

然后你可以使用你的钩子:

 const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))

const SearchStarwarsHeroExample = () => {
  const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
  return (
    <div>
      <input value={inputText} onChange={e => setInputText(e.target.value)} />
      <div>
        {searchResults.loading && <div>...</div>}
        {searchResults.error && <div>Error: {search.error.message}</div>}
        {searchResults.result && (
          <div>
            <div>Results: {search.result.length}</div>
            <ul>
              {searchResults.result.map(hero => (
                <li key={hero.name}>{hero.name}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
};

你会发现这个例子在 这里 运行,你应该阅读 react-async-hook 文档了解更多细节。


2018:尝试 promise 去抖动

我们经常希望对 API 调用进行去抖动处理,以避免后端被无用的请求淹没。

在 2018 年,使用回调 (Lodash/Underscore) 对我来说感觉很糟糕并且容易出错。由于 API 调用以任意顺序解析,因此很容易遇到样板文件和并发问题。

我创建了一个考虑 React 的小库来解决你的痛苦: awesome-debounce-promise

这不应该比那更复杂:

 const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));

const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);

class SearchInputAndResults extends React.Component {
  state = {
    text: '',
    results: null,
  };

  handleTextChange = async text => {
    this.setState({ text, results: null });
    const result = await searchAPIDebounced(text);
    this.setState({ result });
  };
}

debounced 函数确保:

  • API 调用将被去抖动
  • debounced 函数总是返回一个 promise
  • 只有最后一次调用的返回承诺才会解决
  • 每个 API 调用都会发生一个 this.setState({ result });

最后,如果您的组件卸载,您可能会添加另一个技巧:

 componentWillUnmount() {
  this.setState = () => {};
}

请注意, Observables (RxJS) 也非常适合去抖动输入,但它是一种更强大的抽象,可能更难正确学习/使用。


< 2017:还想使用回调去抖动吗?

这里的重要部分是 为每个组件实例创建一个去抖动(或节流)函数。您不想每次都重新创建去抖动(或节流)功能,也不希望多个实例共享相同的去抖动功能。

我没有在这个答案中定义去抖动函数,因为它并不真正相关,但这个答案将与下划线或 lodash 的 _.debounce 以及任何用户提供的去抖动函数完美配合。


好主意:

因为去抖函数是有状态的,我们必须为 每个组件实例创建一个去抖函数

ES6(类属性) :推荐

class SearchBox extends React.Component {
    method = debounce(() => {
      ...
    });
}

ES6(类构造器)

 class SearchBox extends React.Component {
    constructor(props) {
        super(props);
        this.method = debounce(this.method.bind(this),1000);
    }
    method() { ... }
}

ES5

 var SearchBox = React.createClass({
    method: function() {...},
    componentWillMount: function() {
       this.method = debounce(this.method.bind(this),100);
    },
});

请参阅 JsFiddle :3 个实例每个实例生成 1 个日志条目(全局生成 3 个)。


不是一个好主意:

 var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: debounce(this.method, 100);
});

它不会起作用,因为在类描述对象创建期间, this 不是对象本身创建的。 this.method 不会返回您期望的内容,因为 this 上下文不是对象本身(顺便说一句,它实际上并不存在,因为它刚刚被创建)。


不是一个好主意:

 var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: function() {
      var debounced = debounce(this.method,100);
      debounced();
  },
});

这次您有效地创建了一个调用您的 this.method 的去抖函数。问题是你在每次 debouncedMethod 调用时重新创建它,所以新创建的去抖功能对以前的调用一无所知!随着时间的推移,您必须重复使用相同的去抖动函数,否则不会发生去抖动。


不是一个好主意:

 var SearchBox = React.createClass({
  debouncedMethod: debounce(function () {...},100),
});

这在这里有点棘手。

该类的所有已安装实例将共享相同的去抖动函数,而且通常这不是您想要的!。请参阅 JsFiddle :3 个实例在全球范围内仅生成 1 个日志条目。

您必须 为每个组件实例 创建一个去抖动函数,而不是在类级别创建一个由每个组件实例共享的去抖动函数。


照顾 React 的事件池

这是相关的,因为我们经常想要去抖动或限制 DOM 事件。

在 React 中,您在回调中收到的事件对象(即 SyntheticEvent )被合并(现在已 记录)。这意味着在调用事件回调后,您收到的 SyntheticEvent 将以空属性放回池中以减少 GC 压力。

因此,如果您访问 SyntheticEvent 与原始回调异步的属性(如果您限制/去抖动可能就是这种情况),您访问的属性可能会被删除。如果您希望事件永远不会放回池中,可以使用 persist() 方法。

没有持久化(默认行为:合并事件)

 onClick = e => {
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

第二个(异步)将打印 hasNativeEvent=false 因为事件属性已被清理。

随着坚持

onClick = e => {
  e.persist();
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

第二个(异步)将打印 hasNativeEvent=true 因为 persist 允许您避免将事件放回池中。

您可以在这里测试这两种行为: JsFiddle

阅读 Julen 的回答,了解使用 persist() 和节流/去抖功能的示例。

原文由 Sebastien Lorber 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题