如何进行去抖动?2019年:尝试钩子+承诺去抖动2018年:尝试承诺去抖动2017<:还想使用回调去抖动?处理 React 的事件池不受控制的组件受控组件

2022-08-29 22:37:36

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

我想去掉手柄OnChange。

我试过,但它不起作用。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
  }
});

答案 1

2019年:尝试钩子+承诺去抖动

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

这是一些初始连接,但您正在自己组合原始块,您可以制作自己的自定义钩子,这样您只需要这样做一次。

// 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年:尝试承诺去抖动

我们经常希望取消 API 调用,以避免用无用的请求淹没后端。

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

我创建了一个考虑到 React 的小库来解决你的难题:令人敬畏的去抖动承诺

这不应该比这更复杂:

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 });
  };
}

去抖动函数可确保:

  • API 调用将被解除
  • 取消键的函数始终返回承诺
  • 只有最后一个调用返回的承诺才会解析
  • 每个 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() { ... }
}

夏令时

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);
});

它不起作用,因为在类描述对象创建期间,不是创建的对象本身。 不会返回您期望的内容,因为上下文不是对象本身(实际上尚未真正存在,因为它刚刚被创建)。thisthis.methodthis


不是一个好主意:

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

这一次,您将有效地创建一个取消取消的函数,该函数调用您的 .问题是您在每次调用时都重新创建它,因此新创建的deboounce函数对以前的调用一无所知!随着时间的推移,您必须重用相同的去抖动函数,否则去抖动将不会发生。this.methoddebouncedMethod


不是一个好主意:

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

这有点棘手。

该类的所有已挂载实例将共享相同的取消抖动函数,大多数情况下,这不是您想要的!请参阅 JsFiddle:3 个实例在全局范围内仅产生 1 个日志条目。

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


处理 React 的事件池

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

在 React 中,您在回调中接收的事件对象(即 )是池化的(现在已记录在案)。这意味着在调用事件回调后,您收到的 SyntheticEvent 将被放回具有空属性的池中,以降低 GC 压力。SyntheticEvent

因此,如果以异步方式访问原始回调的属性(如果限制/取消声明,则可能会删除所访问的属性。如果希望事件永远不要放回池中,可以使用该方法。SyntheticEventpersist()

不保留(默认行为:池化事件)

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);
};

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

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

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


答案 2

不受控制的组件

可以使用 event.persist() 方法

下面使用下划线的示例:_.debounce()

var SearchBox = React.createClass({

  componentWillMount: function () {
     this.delayedCallback = _.debounce(function (event) {
       // `event.target` is accessible now
     }, 1000);
  },

  onChange: function (event) {
    event.persist();
    this.delayedCallback(event);
  },

  render: function () {
    return (
      <input type="search" onChange={this.onChange} />
    );
  }

});

编辑:看到这个JSFiddle


受控组件

更新:上面的示例显示了一个不受控制的组件。我一直使用受控元素,所以这是上面的另一个例子,但没有使用“欺骗”。event.persist()

JSFiddle也是可用的不带下划线的示例

var SearchBox = React.createClass({
    getInitialState: function () {
        return {
            query: this.props.query
        };
    },

    componentWillMount: function () {
       this.handleSearchDebounced = _.debounce(function () {
           this.props.handleSearch.apply(this, [this.state.query]);
       }, 500);
    },

    onChange: function (event) {
      this.setState({query: event.target.value});
      this.handleSearchDebounced();
    },

    render: function () {
      return (
        <input type="search"
               value={this.state.query}
               onChange={this.onChange} />
      );
    }
});


var Search = React.createClass({
    getInitialState: function () {
        return {
            result: this.props.query
        };
    },

    handleSearch: function (query) {
        this.setState({result: query});
    },

    render: function () {
      return (
        <div id="search">
          <SearchBox query={this.state.result}
                     handleSearch={this.handleSearch} />
          <p>You searched for: <strong>{this.state.result}</strong></p>
        </div>
      );
    }
});

React.render(<Search query="Initial query" />, document.body);

编辑:更新了示例和 JSFiddles 到 React 0.12

编辑:更新了示例以解决塞巴斯蒂安·洛伯提出的问题

编辑:使用不使用下划线并使用纯javascript去抖动的jsfiddle进行了更新。