Thực hiện gỡ lỗi trong React.js


496

Làm thế nào để bạn thực hiện gỡ lỗi trong React.js?

Tôi muốn gỡ lỗi xử lýOnChange.

Tôi đã thử với debounce(this.handleOnChange, 200)nhưng nó không hoạt động.

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

Tôi đã gặp vấn đề tương tự với bạn, câu trả lời tuyệt vời dưới đây nhưng tôi nghĩ bạn đã sử dụng sai cách debounce. ở đây, khi nào onChange={debounce(this.handleOnChange, 200)}/>, nó sẽ gọi debounce functionmỗi lần nhưng, trên thực tế, cái chúng ta cần là gọi hàm mà hàm gỡ lỗi trả về.
pingfengafei

Câu trả lời:


835

2019: thử hook + hứa hẹn thảo luận

Đây là phiên bản cập nhật nhất về cách tôi sẽ giải quyết vấn đề này. Tôi sẽ dùng:

Đây là một số hệ thống dây ban đầu nhưng bạn đang tự mình soạn các khối nguyên thủy và bạn có thể tự tạo móc tùy chỉnh để bạn chỉ cần thực hiện việc này một lần.

// 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,
  };
};

Và sau đó bạn có thể sử dụng hook của mình:

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

Bạn sẽ tìm thấy ví dụ này chạy ở đây và bạn nên đọc tài liệu phản ứng-async-hook để biết thêm chi tiết.


2018: thử hứa hẹn thảo luận

Chúng tôi thường muốn gỡ bỏ các lệnh gọi API để tránh làm ngập phần phụ trợ với các yêu cầu vô dụng.

Năm 2018, làm việc với các cuộc gọi lại (Lodash / Underscore) cảm thấy tồi tệ và dễ bị lỗi đối với tôi. Rất dễ gặp phải các vấn đề về nồi hơi và đồng thời do các lệnh gọi API giải quyết theo thứ tự tùy ý.

Tôi đã tạo ra một thư viện nhỏ với React trong tâm trí để giải quyết những khó khăn của bạn: lời hứa tuyệt vời .

Điều này không nên phức tạp hơn thế:

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

Hàm được gỡ lỗi đảm bảo rằng:

  • Các lệnh gọi API sẽ được công bố
  • chức năng được công bố luôn trả lại một lời hứa
  • chỉ lời hứa được trả lại của cuộc gọi cuối cùng sẽ giải quyết
  • một lần duy nhất this.setState({ result });sẽ xảy ra cho mỗi cuộc gọi API

Cuối cùng, bạn có thể thêm một mẹo khác nếu thành phần của bạn ngắt kết nối:

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

Lưu ý rằng Đài quan sát (RxJS) cũng có thể rất phù hợp để gỡ lỗi đầu vào, nhưng đó là một sự trừu tượng mạnh mẽ hơn có thể khó học / sử dụng chính xác hơn.


<2017: vẫn muốn sử dụng gỡ lỗi gọi lại?

Phần quan trọng ở đây là tạo một hàm được gỡ lỗi (hoặc điều chỉnh) cho mỗi thể hiện thành phần . Bạn không muốn tạo lại chức năng gỡ lỗi (hoặc van tiết lưu) mọi lúc và bạn không muốn nhiều trường hợp chia sẻ cùng một chức năng được gỡ lỗi.

Tôi không xác định chức năng thảo luận trong câu trả lời này vì nó không thực sự phù hợp, nhưng câu trả lời này sẽ hoạt động hoàn toàn tốt với _.debouncedấu gạch dưới hoặc dấu gạch ngang, cũng như bất kỳ chức năng gỡ lỗi nào do người dùng cung cấp.


Ý TƯỞNG TỐT:

Vì các hàm bị gỡ lỗi là trạng thái, chúng ta phải tạo một hàm bị lỗi cho mỗi thể hiện thành phần .

ES6 (thuộc tính lớp) : được đề xuất

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

ES6 (lớp xây dựng)

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

Xem JsFiddle : 3 trường hợp đang tạo 1 mục nhập nhật ký cho mỗi phiên bản (tạo 3 mục trên toàn cầu).


Không phải là một ý tưởng tốt:

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

Nó sẽ không hoạt động, bởi vì trong quá trình tạo đối tượng mô tả lớp, thiskhông phải là đối tượng tự tạo. this.methodkhông trả về những gì bạn mong đợi vì thisbối cảnh không phải là chính đối tượng (mà thực sự chưa thực sự tồn tại BTW vì nó mới được tạo ra).


Không phải là một ý tưởng tốt:

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

Lần này, bạn đang tạo một hàm có hiệu quả gọi là của bạn this.method. Vấn đề là bạn đang tạo lại nó trên mỗi debouncedMethodcuộc gọi, vì vậy chức năng gỡ lỗi mới được tạo không biết gì về các cuộc gọi trước đây! Bạn phải sử dụng lại chức năng đã được công bố theo thời gian hoặc việc gỡ lỗi sẽ không xảy ra.


Không phải là một ý tưởng tốt:

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

Đây là một chút khó khăn ở đây.

Tất cả các cá thể được gắn kết của lớp sẽ chia sẻ cùng một chức năng đã được công bố và thường thì đây không phải là điều bạn muốn!. Xem JsFiddle : 3 trường hợp chỉ tạo ra 1 mục nhật ký trên toàn cầu.

Bạn phải tạo một hàm bị gỡ lỗi cho từng thể hiện thành phần , và không phải là một hàm bị gỡ lỗi duy nhất ở cấp độ lớp, được chia sẻ bởi mỗi thể hiện thành phần.


Chăm sóc tổng hợp sự kiện của React

Điều này có liên quan vì chúng ta thường muốn gỡ lỗi hoặc điều tiết các sự kiện DOM.

Trong React, các đối tượng sự kiện (tức là SyntheticEvent) mà bạn nhận được trong các cuộc gọi lại được gộp lại (điều này hiện được ghi lại ). Điều này có nghĩa là sau khi gọi lại sự kiện đã được gọi, thì TotalEvent mà bạn nhận được sẽ được đưa trở lại vào nhóm với các thuộc tính trống để giảm áp lực GC.

Vì vậy, nếu bạn truy cập SyntheticEventcác thuộc tính không đồng bộ vào cuộc gọi lại ban đầu (có thể là trường hợp nếu bạn điều tiết / gỡ lỗi), các thuộc tính bạn truy cập có thể bị xóa. Nếu bạn muốn sự kiện không bao giờ được đưa trở lại vào nhóm, bạn có thể sử dụng persist()phương thức này.

Không tồn tại (hành vi mặc định: sự kiện gộp)

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

Thứ 2 (không đồng bộ) sẽ in hasNativeEvent=false vì các thuộc tính sự kiện đã được dọn sạch.

Với sự kiên trì

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

Thứ 2 (không đồng bộ) sẽ in hasNativeEvent=truepersist cho phép bạn tránh đưa sự kiện trở lại nhóm.

Bạn có thể kiểm tra 2 hành vi này tại đây: JsFiddle

Đọc câu trả lời của Julen để biết ví dụ về việc sử dụng persist()chức năng tiết lưu / gỡ lỗi.


3
Câu trả lời tuyệt vời, điều này rất tốt cho việc đặt trạng thái trường biểu mẫu là 'tương tác' trong vài giây sau khi họ dừng nhập và sau đó có thể hủy trên biểu mẫu gửi hoặc
trênBlur

8
Lưu ý rằng trong ES6, thay vì xác định phương thức của bạn bên trong hàm tạo (cảm thấy kỳ lạ), bạn có thể thực hiện handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)ở cấp cao nhất của lớp. Bạn vẫn thiết lập một thành viên cá thể một cách hiệu quả nhưng nó trông hơi giống một định nghĩa phương thức bình thường. Không cần cho constructornếu bạn chưa có một định nghĩa. Tôi cho rằng nó chủ yếu là một sở thích phong cách.
thom_nic

24
Đừng quên hủy phương thức đã gỡ lỗi trong componentWillUnmount: this.method.cancel()- nếu không, nó có thể muốn đặtState trên một thành phần chưa được đếm.
elado

4
@JonasKello bạn không thể gỡ lỗi bên trong một thành phần không trạng thái vì chức năng được công bố là thực sự có trạng thái. Bạn cần một thành phần có trạng thái để giữ chức năng bị gỡ lỗi đó, nhưng bạn có thể gọi một thành phần không trạng thái với chức năng đã được gỡ lỗi nếu cần.
Sebastien Lorber

2
Tại sao tất cả câu trả lời bao gồm _.debounce thay vì viết hàm? Nó cần toàn bộ thư viện cho chức năng đó?
chifliiiii

217

Các thành phần không được kiểm soát

Bạn có thể sử dụng event.persist()phương pháp .

Một ví dụ sau sử dụng dấu gạch dưới _.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} />
    );
  }

});

Chỉnh sửa: Xem JSFiddle này


Linh kiện điều khiển

Cập nhật: ví dụ trên cho thấy một thành phần không được kiểm soát . Tôi luôn luôn sử dụng các yếu tố được kiểm soát vì vậy đây là một ví dụ khác ở trên, nhưng không sử dụngevent.persist() "mánh khóe".

Một JSFiddle cũng có sẵn . Ví dụ không có dấu gạch dưới

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

Chỉnh sửa: các ví dụ được cập nhật và JSFiddles thành React 0.12

Chỉnh sửa: các ví dụ được cập nhật để giải quyết vấn đề do Sebastien Lorber nêu ra

Chỉnh sửa: được cập nhật với jsfiddle không sử dụng dấu gạch dưới và sử dụng gỡ lỗi javascript đơn giản.


Điều này không làm việc cho đầu vào. Mục tiêu sự kiện trong chức năng được công bố không còn giá trị ... vì vậy đầu vào vẫn trống.
Etai

1
Hơi phức tạp, cái này. Bạn phải cẩn thận một chút về đạo cụ. Nếu bạn đã đặt <input value={this.props.someprop}...thì nó sẽ không hiển thị chính xác vì bản cập nhật trên phím nhấn sẽ không đưa nó trở lại thành phần cho đến khi phát hành. Sẽ tốt hơn value=nếu bạn bỏ qua nếu bạn không vui vì điều này không được quản lý, nhưng nếu bạn muốn điền trước giá trị và / hoặc ràng buộc nó ở một nơi khác thì rõ ràng điều này không hiệu quả.
Alastair Maw

1
@AlastairMaw câu hỏi có một thành phần không được kiểm soát, đó là lý do tại sao câu trả lời cũng có nó. Tôi đã thêm vào bên dưới một phiên bản thay thế cho các thành phần được kiểm soát, với giá trị được điền trước.
Julen

2
Điều này rất nguy hiểm nếu bạn gắn kết thành phần nhân đôi lần trong DOM, xem stackoverflow.com/questions/23123138/ Khăn
Sebastien Lorber

4
Mặc dù đây là một câu trả lời tuyệt vời, tôi không khuyên bạn nên sử dụng persistđặc biệt là khi có thể có nhiều sự kiện, như trên mousemove. Tôi đã thấy mã trở nên hoàn toàn không phản hồi theo cách đó. Sẽ hiệu quả hơn nhiều khi trích xuất dữ liệu cần thiết từ sự kiện gốc trong cuộc gọi sự kiện và sau đó gọi hàm được gỡ lỗi / điều chỉnh chỉ với dữ liệu, KHÔNG phải là sự kiện. Không cần phải tiếp tục sự kiện theo cách đó
MrE

31

2019: Sử dụng móc phản ứng 'useCallback'

Sau khi thử nhiều cách tiếp cận khác nhau, tôi thấy việc sử dụng useCallbacklà cách đơn giản và hiệu quả nhất để giải quyết vấn đề nhiều cuộc gọi sử dụng debouncetrong một onChangesự kiện.

Theo tài liệu API của Hook ,

useCallback trả về một phiên bản ghi nhớ của cuộc gọi lại chỉ thay đổi nếu một trong các phụ thuộc đã thay đổi.

Vượt qua một mảng trống như một phụ thuộc đảm bảo gọi lại chỉ được gọi một lần. Đây là một cách thực hiện đơn giản:

import React, { useCallback } from "react";
import { debounce } from "lodash";

const handler = useCallback(debounce(someFunction, 2000), []);

const onChange = (event) => {
    // perform any event related action here

    handler();
 };

Hi vọng điêu nay co ich!


3
Giải pháp tuyệt vời nếu bạn đang sử dụng móc. Bạn đã cứu tôi nhiều giờ thất vọng. Cảm ơn!
Carl Edwards

Bạn có thể vui lòng giải thích tại sao nhiều cuộc gọi xảy ra ở nơi đầu tiên? Không debounce()coi cuộc onChange()gọi lại là cùng một phương thức gọi lại?
El Anonimo

Tôi đã sửa đổi giải pháp này để làm cho nó hoạt động trong ứng dụng của tôi. Đầu tiên tôi phải di chuyển dòng const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);bên trong thân của thành phần chức năng hoặc React đưa ra một thông báo lỗi về việc sử dụng hook bên ngoài nó. Sau đó, trong onChangexử lý sự kiện : <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
El Anonimo

Dưới đây là cách tôi sử dụng giải pháp này để cho phép người dùng nhập vào đầu vào sau đó gửi lệnh gọi API đã bị lỗi với giá trị đầu vào sau khi nhập xong. stackoverflow.com/questions/59358092/ cấp .
El Anonimo

14

Tôi thấy bài đăng này của Justin Tulk rất hữu ích. Sau một vài lần thử, theo cách mà người ta cho là cách chính thức hơn với Reac / redux, nó cho thấy nó thất bại do sự kiện tổng hợp của React . Giải pháp của anh ta sau đó sử dụng một số trạng thái nội bộ để theo dõi giá trị được thay đổi / nhập vào đầu vào, với một cuộc gọi lại ngay sau setStateđó gọi một hành động chuyển hướng được điều chỉnh / gỡ lỗi cho thấy một số kết quả trong thời gian thực.

import React, {Component} from 'react'
import TextField from 'material-ui/TextField'
import { debounce } from 'lodash'

class TableSearch extends Component {

  constructor(props){
    super(props)

    this.state = {
        value: props.value
    }

    this.changeSearch = debounce(this.props.changeSearch, 250)
  }

  handleChange = (e) => {
    const val = e.target.value

    this.setState({ value: val }, () => {
      this.changeSearch(val)
    })
  }

  render() {

    return (
        <TextField
            className = {styles.field}
            onChange = {this.handleChange}
            value = {this.props.value}
        />
    )
  }
}

14

Nếu tất cả những gì bạn cần từ đối tượng sự kiện là lấy phần tử đầu vào DOM, thì giải pháp đơn giản hơn nhiều - chỉ cần sử dụng ref. Lưu ý rằng điều này đòi hỏi Underscore :

class Item extends React.Component {
    constructor(props) {
        super(props);
        this.saveTitle = _.throttle(this.saveTitle.bind(this), 1000);
    }
    saveTitle(){
        let val = this.inputTitle.value;
        // make the ajax call
    }
    render() {
        return <input 
                    ref={ el => this.inputTitle = el } 
                    type="text" 
                    defaultValue={this.props.title} 
                    onChange={this.saveTitle} />
    }
}

2
defaultValue là những gì tôi muốn! Cảm ơn bạn rất mach :)
Tazo leladze

14

Sau khi vật lộn với các đầu vào văn bản trong một thời gian và không tự mình tìm ra một giải pháp hoàn hảo, tôi đã tìm thấy điều này trên npm: Reac -debounce-input .

Đây là một ví dụ đơn giản:

import React from 'react';
import ReactDOM from 'react-dom';
import {DebounceInput} from 'react-debounce-input';

class App extends React.Component {
state = {
    value: ''
};

render() {
    return (
    <div>
        <DebounceInput
        minLength={2}
        debounceTimeout={300}
        onChange={event => this.setState({value: event.target.value})} />

        <p>Value: {this.state.value}</p>
    </div>
    );
}
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

Thành phần DebounceInput chấp nhận tất cả các đạo cụ bạn có thể gán cho một phần tử đầu vào bình thường. Dùng thử trên codepen

Tôi hy vọng nó cũng giúp người khác và tiết kiệm thời gian cho họ.


Sau khi thử nhiều giải pháp được liệt kê ở đây, chắc chắn là dễ nhất.
Vadorequest

9

Với debouncebạn cần phải giữ sự kiện tổng hợp ban đầu xung quanh với event.persist(). Dưới đây là ví dụ làm việc được thử nghiệm với React 16+.

import React, { Component } from 'react';
import debounce from 'lodash/debounce'

class ItemType extends Component {

  evntHandler = debounce((e) => {
    console.log(e)
  }, 500);

  render() {
    return (
      <div className="form-field-wrap"
      onClick={e => {
        e.persist()
        this.evntHandler(e)
      }}>
        ...
      </div>
    );
  }
}
export default ItemType;

Với thành phần chức năng, bạn có thể làm điều này -

const Search = ({ getBooks, query }) => {

  const handleOnSubmit = (e) => {
    e.preventDefault();
  }
  const debouncedGetBooks = debounce(query => {
    getBooks(query);
  }, 700);

  const onInputChange = e => {
    debouncedGetBooks(e.target.value)
  }

  return (
    <div className="search-books">
      <Form className="search-books--form" onSubmit={handleOnSubmit}>
        <Form.Group controlId="formBasicEmail">
          <Form.Control type="text" onChange={onInputChange} placeholder="Harry Potter" />
          <Form.Text className="text-muted">
            Search the world's most comprehensive index of full-text books.
          </Form.Text>
        </Form.Group>
        <Button variant="primary" type="submit">
          Search
        </Button>
      </Form>
    </div>
  )
}

Tài liệu tham khảo - - https://gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709 - https://blog.revathskumar.com/2016/02/reactjs-USE-debounce-in-react-components.html


1
hoạt động tốt nhất thực hiện tốt nhất tôi tìm thấy cho đến nay
Vincent Tang

8

Nếu bạn đang sử dụng redux, bạn có thể làm điều này một cách rất thanh lịch với phần mềm trung gian. Bạn có thể định nghĩa một Debouncephần mềm trung gian là:

var timeout;
export default store => next => action => {
  const { meta = {} } = action;
  if(meta.debounce){
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      next(action)
    }, meta.debounce)
  }else{
    next(action)
  }
}

Sau đó, bạn có thể thêm gỡ lỗi cho người tạo hành động, chẳng hạn như:

export default debouncedAction = (payload) => ({
  type : 'DEBOUNCED_ACTION',
  payload : payload,
  meta : {debounce : 300}
}

Thực sự đã có phần mềm trung gian, bạn có thể rời khỏi npm để làm điều này cho bạn.


Tôi nghĩ phần mềm trung gian này phải là phần mềm đầu tiên được thực thi trong applyMiddleware(...)chuỗi nếu chúng ta có nhiều
Youssef

Thời gian chờ không được khởi tạo và ClearTimeout đầu tiên sẽ xử lý không xác định cho một param. Không tốt.
Jason Rice

7

Sử dụng ES6 Class và React 15.xx & lodash.debounce Im bằng React's ref tại đây vì sự kiện làm mất liên kết này trong nội bộ.

class UserInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userInput: ""
    };
    this.updateInput = _.debounce(this.updateInput, 500);
  }


  updateInput(userInput) {
    this.setState({
      userInput
    });
    //OrderActions.updateValue(userInput);//do some server stuff
  }


  render() {
    return ( <div>
      <p> User typed: {
        this.state.userInput
      } </p>
      <input ref = "userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type = "text" / >
      </div>
    );
  }
}

ReactDOM.render( <
  UserInput / > ,
  document.getElementById('root')
);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="root"></div>


7

Có rất nhiều thông tin tốt ở đây, nhưng phải ngắn gọn. Điều này làm việc cho tôi ...

import React, {Component} from 'react';
import _ from 'lodash';

class MyComponent extends Component{
      constructor(props){
        super(props);
        this.handleChange = _.debounce(this.handleChange.bind(this),700);
      }; 

Điều này không làm việc cho tôi. Nhà nước không cập nhật. Nếu tôi loại bỏ _debounce bọc nó hoạt động. Tôi thích ý tưởng này mặc dù!
Mote Zart

Tôi sẽ phải xem mã của bạn để cung cấp nhiều ở đây, nhưng tôi nghi ngờ có điều gì khác đang diễn ra ... hy vọng câu trả lời kỹ lưỡng hơn nhiều này sẽ làm sáng tỏ. stackoverflow.com/questions/23123138/ từ
chad Steele

6

Bạn có thể sử dụng phương thức gỡ lỗi Lodash https://lodash.com/docs/4.17.5#debounce . Nó đơn giản và hiệu quả.

import * as lodash from lodash;

const update = (input) => {
    // Update the input here.
    console.log(`Input ${input}`);     
}

const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});

doHandleChange() {
   debounceHandleUpdate(input);
}

Bạn cũng có thể hủy phương thức gỡ lỗi bằng cách sử dụng phương thức bên dưới.

this.debounceHandleUpdate.cancel();

Hy vọng nó sẽ giúp bạn. Chúc mừng !!


5

FYI

Đây là một triển khai PoC khác:

  • không có bất kỳ thư viện nào (ví dụ: lodash) để thảo luận
  • sử dụng API React Hook

Tôi hy vọng nó sẽ giúp :)

import React, { useState, useEffect, ChangeEvent } from 'react';

export default function DebouncedSearchBox({
  inputType,
  handleSearch,
  placeholder,
  debounceInterval,
}: {
  inputType?: string;
  handleSearch: (q: string) => void;
  placeholder: string;
  debounceInterval: number;
}) {
  const [query, setQuery] = useState<string>('');
  const [timer, setTimer] = useState<NodeJS.Timer | undefined>();

  useEffect(() => {
    if (timer) {
      clearTimeout(timer);
    }
    setTimer(setTimeout(() => {
      handleSearch(query);
    }, debounceInterval));
  }, [query]);

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
    setQuery(e.target.value);
  };

  return (
    <input
      type={inputType || 'text'}
      className="form-control"
      placeholder={placeholder}
      value={query}
      onChange={handleOnChange}
    />
  );
}

4

Có một use-debouncegói mà bạn có thể sử dụng với móc ReactJS.

Từ gói README:

import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

Như bạn có thể thấy từ ví dụ trên, nó được thiết lập để cập nhật biến valuechỉ một lần mỗi giây (1000 mili giây).


3

Chỉ là một biến thể khác với phản ứng gần đây và lodash.

class Filter extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired
  }

  state = {
    initialText: '',
    text: ''
  }

  constructor (props) {
    super(props)

    this.setText = this.setText.bind(this)
    this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
  }

  static getDerivedStateFromProps (nextProps, prevState) {
    const { text } = nextProps

    if (text !== prevState.initialText) {
      return { initialText: text, text }
    }

    return null
  }

  setText (text) {
    this.setState({ text })
    this.onChange(text)
  }

  onChange (text) {
    this.props.onChange(text)
  }

  render () {
    return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
  }
}


3

Bạn đã thử chưa?

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) {
    debounce(\\ Your handleChange code , 200);
  }
});

2

Thay vì gói xử lýOnChange trong debounce (), tại sao không bọc lệnh gọi ajax bên trong hàm gọi lại bên trong gỡ lỗi, do đó không phá hủy đối tượng sự kiện. Vì vậy, một cái gì đó như thế này:

handleOnChange: function (event) {
   debounce(
     $.ajax({})
  , 250);
}

4
Bởi vì đối tượng sự kiện không phải là bất biến và bị ReactJS phá hủy, do đó ngay cả khi bạn bọc và đạt được một bản chụp đóng, mã sẽ thất bại.
Henrik

2

Dưới đây là một ví dụ tôi đã đưa ra kết thúc một lớp khác với một người gỡ lỗi. Điều này cho vay độc đáo để được làm thành một chức năng trang trí / thứ tự cao hơn:

export class DebouncedThingy extends React.Component {
    static ToDebounce = ['someProp', 'someProp2'];
    constructor(props) {
        super(props);
        this.state = {};
    }
    // On prop maybe changed
    componentWillReceiveProps = (nextProps) => {
        this.debouncedSetState();
    };
    // Before initial render
    componentWillMount = () => {
        // Set state then debounce it from here on out (consider using _.throttle)
        this.debouncedSetState();
        this.debouncedSetState = _.debounce(this.debouncedSetState, 300);
    };
    debouncedSetState = () => {
        this.setState(_.pick(this.props, DebouncedThingy.ToDebounce));
    };
    render() {
        const restOfProps = _.omit(this.props, DebouncedThingy.ToDebounce);
        return <Thingy {...restOfProps} {...this.state} />
    }
}

2

Hiện tại có một giải pháp khác cho React và React Native vào cuối / 2019 :

phản ứng-gỡ lỗi-thành phần

<input>
<Debounce ms={500}>
  <List/>
</Debounce>

Đây là một thành phần, dễ sử dụng, được hỗ trợ nhỏ và rộng

Thí dụ:

nhập mô tả hình ảnh ở đây

import React from 'react';
import Debounce from 'react-debounce-component';

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {value: 'Hello'}
  }
  render () {
    return (
      <div>
        <input value={this.state.value} onChange={(event) => {this.setState({value: event.target.value})}}/>
        <Debounce ms={1000}>
          <div>{this.state.value}</div>
        </Debounce>
      </div>
    );
  }
}

export default App;

* Tôi là người tạo ra thành phần này


1

Tôi đang tìm kiếm một giải pháp cho cùng một vấn đề và tình cờ gặp chủ đề này cũng như một số vấn đề khác nhưng họ có cùng một vấn đề: nếu bạn đang cố gắng thực hiện một handleOnChangechức năng và bạn cần giá trị từ mục tiêu sự kiện, bạn sẽ nhận được cannot read property value of nullhoặc một số lỗi như vậy. Trong trường hợp của tôi, tôi cũng cần phải duy trì bối cảnh thisbên trong hàm bị gỡ lỗi kể từ khi tôi thực hiện một hành động thay đổi. Đây là giải pháp của tôi, nó hoạt động tốt cho trường hợp sử dụng của tôi vì vậy tôi sẽ để nó ở đây trong trường hợp có ai gặp phải chủ đề này:

// at top of file:
var myAction = require('../actions/someAction');

// inside React.createClass({...});

handleOnChange: function (event) {
    var value = event.target.value;
    var doAction = _.curry(this.context.executeAction, 2);

    // only one parameter gets passed into the curried function,
    // so the function passed as the first parameter to _.curry()
    // will not be executed until the second parameter is passed
    // which happens in the next function that is wrapped in _.debounce()
    debouncedOnChange(doAction(myAction), value);
},

debouncedOnChange: _.debounce(function(action, value) {
    action(value);
}, 300)

1

cho throttlehoặc debouncecách tốt nhất là tạo một hàm tạo để bạn có thể sử dụng nó ở bất kỳ đâu, ví dụ:

  updateUserProfileField(fieldName) {
    const handler = throttle(value => {
      console.log(fieldName, value);
    }, 400);
    return evt => handler(evt.target.value.trim());
  }

và trong renderphương pháp của bạn, bạn có thể làm:

<input onChange={this.updateUserProfileField("givenName").bind(this)}/>

các updateUserProfileField phương pháp sẽ tạo ra một chức năng tách mỗi lần bạn gọi nó.

Lưu ý đừng cố trả lại trình xử lý trực tiếp, ví dụ như điều này sẽ không hoạt động:

 updateUserProfileField(fieldName) {
    return evt => throttle(value => {
      console.log(fieldName, value);
    }, 400)(evt.target.value.trim());
  }

lý do tại sao điều này sẽ không hoạt động bởi vì điều này sẽ tạo ra một chức năng tiết lưu mới mỗi khi sự kiện được gọi thay vì sử dụng cùng một chức năng điều tiết, vì vậy về cơ bản, điều tiết sẽ vô dụng;)

Ngoài ra nếu bạn sử dụng debouncehoặc throttlebạn không cần setTimeouthoặc clearTimeout, đây thực sự là lý do tại sao chúng tôi sử dụng chúng: P


1

Đây là đoạn trích sử dụng cách tiếp cận của @ Abra được gói trong một thành phần chức năng (chúng tôi sử dụng vải cho giao diện người dùng, chỉ cần thay thế nó bằng một nút đơn giản)

import React, { useCallback } from "react";
import { debounce } from "lodash";

import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';

const debounceTimeInMS = 2000;

export const PrimaryButtonDebounced = (props) => {

    const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });

    const clickHandlerDebounced = useCallback((e, value) => {

        debouncedOnClick(e, value);

    },[]);

    const onClick = (e, value) => {

        clickHandlerDebounced(e, value);
    };

    return (
        <PrimaryButton {...props}
            onClick={onClick}
        />
    );
}

1

Giải pháp của tôi là hook dựa (được viết bằng Bản in).

Tôi đã có 2 móc chính useDebouncedValueuseDebouncedCallback

Đầu tiên - useDebouncedValue

Giả sử chúng ta có một hộp tìm kiếm, nhưng chúng tôi muốn hỏi máy chủ về kết quả tìm kiếm sau khi người dùng đã dừng gõ 0,5 giây

function SearchInput() {
  const [realTimeValue, setRealTimeValue] = useState('');

  const debouncedValue = useDebouncedValue(realTimeValue, 500); // this value will pick real time value, but will change it's result only when it's seattled for 500ms

  useEffect(() => {
    // this effect will be called on seattled values
    api.fetchSearchResults(debouncedValue);
  }, [debouncedValue])

  return <input onChange={event => setRealTimeValue(event.target.value)} />
}

Thực hiện

import { useState, useEffect } from "react";

export function useDebouncedValue<T>(input: T, time = 500) {
  const [debouncedValue, setDebouncedValue] = useState(input);

  // every time input value has changed - set interval before it's actually commited
  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValue(input);
    }, time);

    return () => {
      clearTimeout(timeout);
    };
  }, [input, time]);

  return debouncedValue;
}

Thứ hai useDebouncedCallback

Nó chỉ tạo ra một chức năng 'được công bố' trong phạm vi thành phần của bạn.

Giả sử chúng ta có một thành phần với một nút sẽ hiển thị cảnh báo 500ms sau khi bạn ngừng nhấp vào nó.

function AlertButton() {
  function showAlert() {
    alert('Clicking has seattled');
  }

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

  return <button onClick={debouncedShowAlert}>Click</button>
}

Triển khai (lưu ý tôi đang sử dụng lodash / debounce như một người trợ giúp)

import debounce from 'lodash/debounce';
import { useMemo } from 'react';

export function useDebouncedCallback<T extends (...args: any) => any>(callback: T, wait?: number) {
  const debouncedCallback = useMemo(() => debounce(callback, wait), [callback, wait]);

  return debouncedCallback;
}

0

Đây là một ví dụ TypeScript hoạt động cho những người sử dụng TS và muốn gỡ lỗi các asynchàm.

function debounce<T extends (...args: any[]) => any>(time: number, func: T): (...funcArgs: Parameters<T>) => Promise<ReturnType<T>> {
     let timeout: Timeout;

     return (...args: Parameters<T>): Promise<ReturnType<T>> => new Promise((resolve) => {
         clearTimeout(timeout);
         timeout = setTimeout(() => {
             resolve(func(...args));
         }, time)
     });
 }

0

hơi muộn ở đây nhưng điều này sẽ giúp. tạo lớp này (nó được viết bằng bản thảo nhưng dễ dàng chuyển đổi nó thành javascript)

export class debouncedMethod<T>{
  constructor(method:T, debounceTime:number){
    this._method = method;
    this._debounceTime = debounceTime;
  }
  private _method:T;
  private _timeout:number;
  private _debounceTime:number;
  public invoke:T = ((...args:any[])=>{
    this._timeout && window.clearTimeout(this._timeout);
    this._timeout = window.setTimeout(()=>{
      (this._method as any)(...args);
    },this._debounceTime);
  }) as any;
}

và sử dụng

var foo = new debouncedMethod((name,age)=>{
 console.log(name,age);
},500);
foo.invoke("john",31);

0

bạn có thể sử dụng tlence tlence

function log(server) {
  console.log('connecting to', server);
}

const debounceLog = debounce(log, 5000);
// just run last call to 5s
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');

0

Giải pháp Julen là loại khó đọc, đây là mã phản ứng rõ ràng và chính xác hơn cho bất kỳ ai vấp ngã anh ta dựa trên tiêu đề và không phải là chi tiết nhỏ của câu hỏi.

phiên bản tl; dr : khi bạn sẽ cập nhật cho người quan sát, hãy gọi phương thức lịch biểu thay vào đó và đến lượt nó sẽ thực sự thông báo cho người quan sát (hoặc thực hiện ajax, v.v.)

Hoàn thành jsfiddle với ví dụ thành phần jsfiddle

var InputField = React.createClass({

    getDefaultProps: function () {
        return {
            initialValue: '',
            onChange: null
        };
    },

    getInitialState: function () {
        return {
            value: this.props.initialValue
        };
    },

    render: function () {
        var state = this.state;
        return (
            <input type="text"
                   value={state.value}
                   onChange={this.onVolatileChange} />
        );
    },

    onVolatileChange: function (event) {
        this.setState({ 
            value: event.target.value 
        });

        this.scheduleChange();
    },

    scheduleChange: _.debounce(function () {
        this.onChange();
    }, 250),

    onChange: function () {
        var props = this.props;
        if (props.onChange != null) {
            props.onChange.call(this, this.state.value)
        }
    },

});

3
Điều này sẽ không làm cho trạng thái / thời gian của bản sửa lỗi toàn cầu trên tất cả các phiên bản của InputField, bởi vì nó được tạo với định nghĩa lớp? Có thể đó là những gì bạn muốn, nhưng nó đáng chú ý bất kể.
cướp

1
nguy hiểm nếu được gắn nhiều lần trong dom, kiểm tra stackoverflow.com/questions/23123138/ấc
Sebastien Lorber

2
Đây là một giải pháp tồi, vì các vấn đề gắn kết đôi - bạn đang thực hiện chức năng của mình để lên lịch Thay đổi một đơn vị và đó không phải là một ý tưởng hay. -1
Henrik

0

Tránh sử dụng event.persist()- bạn muốn để React tái chế sự kiện tổng hợp. Tôi nghĩ cách sạch nhất cho dù bạn sử dụng các lớp hay hook là chia cuộc gọi lại thành hai phần:

  1. Cuộc gọi lại không có tranh luận
  2. Gọi một chức năng được công bố chỉ với các phần của sự kiện bạn cần (vì vậy sự kiện tổng hợp có thể được tái chế)

Các lớp học

handleMouseOver = throttle(target => {
  console.log(target);
}, 1000);

onMouseOver = e => {
  this.handleMouseOver(e.target);
};

<div onMouseOver={this.onMouseOver} />

Chức năng

const handleMouseOver = useRef(throttle(target => {
  console.log(target);
}, 1000));

function onMouseOver(e) {
  handleMouseOver.current(e.target);
}

<div onMouseOver={this.onMouseOver} />

Lưu ý rằng nếu handleMouseOverhàm của bạn sử dụng trạng thái từ bên trong thành phần, bạn nên sử dụng useMemothay vì useRefvà chuyển chúng dưới dạng phụ thuộc nếu không bạn sẽ làm việc với dữ liệu cũ (tất nhiên không áp dụng cho các lớp).


0

Mở rộng hook useState

import { useState } from "react";
import _ from "underscore"
export const useDebouncedState = (initialState, durationInMs = 500) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedFunction = _.debounce(setInternalState, durationInMs);
    return [internalState, debouncedFunction];
};
export default useDebouncedState;

Sử dụng móc

import useDebouncedState from "../hooks/useDebouncedState"
//...
const [usernameFilter, setUsernameFilter] = useDebouncedState("")
//...
<input id="username" type="text" onChange={e => setUsernameFilter(e.target.value)}></input>

https://trippingoncode.com/react-debounce-hook/


0

Gặp vấn đề này ngày hôm nay. Giải quyết nó bằng cách sử dụng setTimeoutclearTimeout.

Tôi sẽ đưa ra một ví dụ mà bạn có thể thích nghi:

import React, { Component } from 'react'

const DEBOUNCE_TIME = 500

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  onChangeHandler = (event) => {
    // Clear the last registered timer for the function
    clearTimeout(this.debounceTimer);

    // Set a new timer
    this.debounceTimer = setTimeout(
      // Bind the callback function to pass the current input value as arg
      this.getSuggestions.bind(null, event.target.value), 
      DEBOUNCE_TIME
    )
  }

  // The function that is being debounced
  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <input type="text" onChange={this.onChangeHandler} />
    )
  }
}

export default PlacesAutocomplete

Bạn cũng có thể cấu trúc lại nó trong thành phần chức năng của chính nó:

import React from 'react'

function DebouncedInput({ debounceTime, callback}) {
  let debounceTimer = null
  return (
    <input type="text" onChange={(event) => {
      clearTimeout(debounceTimer);

      debounceTimer = setTimeout(
        callback.bind(null, event.target.value), 
        debounceTime
      )
    }} />
  )
}

export default DebouncedInput

Và sử dụng nó như:

import React, { Component } from 'react'
import DebouncedInput from '../DebouncedInput';

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <DebouncedInput debounceTime={500} callback={this.getSuggestions} />
    )
  }
}

export default PlacesAutocomplete
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.