2019: coba kait + janji tolak
Ini adalah versi terbaru tentang bagaimana saya akan menyelesaikan masalah ini. Saya akan menggunakan:
Ini adalah beberapa pengkabelan awal tetapi Anda membuat blok primitif sendiri, dan Anda dapat membuat kait kustom Anda sendiri sehingga Anda hanya perlu melakukan ini satu kali.
// 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,
};
};
Dan kemudian Anda bisa menggunakan hook Anda:
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>
);
};
Anda akan menemukan contoh ini berjalan di sini dan Anda harus membaca dokumentasi react-async-hook untuk lebih jelasnya.
2018: cobalah berjanji untuk melonggarkan
Kami sering ingin mendebo panggilan API untuk menghindari membanjiri backend dengan permintaan yang tidak berguna.
Pada tahun 2018, bekerja dengan callback (Lodash / Underscore) terasa buruk dan rentan kesalahan bagi saya. Sangat mudah untuk menemukan masalah boilerplate dan konkurensi karena penyelesaian panggilan API dalam urutan arbitrer.
Saya telah membuat perpustakaan kecil dengan React dalam pikiran untuk menyelesaikan rasa sakit Anda: mengagumkan-debounce-janji .
Ini seharusnya tidak lebih rumit dari itu:
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 });
};
}
Fungsi yang didebok memastikan bahwa:
- Panggilan API akan dideboed
- fungsi yang didebo selalu mengembalikan janji
- hanya janji panggilan terakhir yang akan menyelesaikan
- satu
this.setState({ result });
akan terjadi per panggilan API
Akhirnya, Anda dapat menambahkan trik lain jika komponen Anda dilepas:
componentWillUnmount() {
this.setState = () => {};
}
Perhatikan bahwa Observables (RxJS) juga bisa sangat cocok untuk mendebit input, tetapi ini adalah abstraksi yang lebih kuat yang mungkin lebih sulit untuk dipelajari / digunakan dengan benar.
<2017: masih ingin menggunakan pembatalan panggilan balik?
Bagian penting di sini adalah membuat fungsi debouncing tunggal (atau dibatasi) per instance komponen . Anda tidak ingin membuat ulang fungsi debounce (atau throttle) setiap kali, dan Anda tidak ingin salah satu dari beberapa instance berbagi fungsi debounce yang sama.
Saya tidak mendefinisikan fungsi debouncing dalam jawaban ini karena itu tidak benar-benar relevan, tetapi jawaban ini akan berfungsi dengan baik dengan _.debounce
garis bawah atau lodash, serta fungsi debouncing yang disediakan pengguna.
IDE BAGUS:
Karena fungsi yang di-debo-kan adalah stateful, kita harus membuat satu fungsi yang di-debo-kan per instance komponen .
ES6 (properti kelas) : direkomendasikan
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (konstruktor kelas)
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);
},
});
Lihat JsFiddle : 3 instance menghasilkan 1 entri log per instance (yang menghasilkan 3 global).
BUKAN ide yang bagus:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
Ini tidak akan berfungsi, karena selama pembuatan objek deskripsi kelas, this
bukan objek yang dibuat sendiri. this.method
tidak mengembalikan apa yang Anda harapkan karena this
konteksnya bukan objek itu sendiri (yang sebenarnya belum benar-benar ada BTW karena baru saja dibuat).
BUKAN ide yang bagus:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Kali ini Anda secara efektif membuat fungsi terbebas yang memanggil Anda this.method
. Masalahnya adalah Anda membuat ulang pada setiap debouncedMethod
panggilan, sehingga fungsi debounce yang baru dibuat tidak tahu apa-apa tentang panggilan sebelumnya! Anda harus menggunakan kembali fungsi debouncing yang sama dari waktu ke waktu atau debouncing tidak akan terjadi.
BUKAN ide yang bagus:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
Ini agak sulit di sini.
Semua instance yang terpasang dari kelas akan berbagi fungsi yang sama, dan paling sering ini bukan yang Anda inginkan !. Lihat JsFiddle : 3 instance hanya menghasilkan 1 entri log secara global.
Anda harus membuat fungsi terbebas untuk setiap instance komponen , dan bukan fungsi terbebas tunggal di tingkat kelas, dibagikan oleh setiap instance komponen.
Jaga pooling acara React
Ini terkait karena kita sering ingin men-debo atau mencekik acara DOM.
Di Bereaksi, objek acara (yaitu, SyntheticEvent
) yang Anda terima di panggilan balik dikumpulkan (ini sekarang didokumentasikan ). Ini berarti bahwa setelah event callback dipanggil, SyntheticEvent yang Anda terima akan dimasukkan kembali ke kolam dengan atribut kosong untuk mengurangi tekanan GC.
Jadi, jika Anda mengakses SyntheticEvent
properti secara tidak sinkron ke callback asli (seperti yang terjadi jika Anda mencekik / debounce), properti yang Anda akses dapat dihapus. Jika Anda ingin acara tersebut tidak dimasukkan kembali ke dalam kolam, Anda dapat menggunakan persist()
metode ini.
Tanpa bertahan (perilaku default: acara gabungan)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Kedua (async) akan mencetak hasNativeEvent=false
karena properti acara telah dibersihkan.
Dengan bertahan
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
Kedua (async) akan mencetak hasNativeEvent=true
karenapersist
memungkinkan Anda untuk menghindari meletakkan kembali acara di kolam.
Anda dapat menguji 2 perilaku ini di sini: JsFiddle
Baca jawaban Julen untuk contoh penggunaan persist()
dengan fungsi throttle / debounce.
debounce
. di sini, kapanonChange={debounce(this.handleOnChange, 200)}/>
, itu akan memanggildebounce function
setiap waktu. tetapi, pada kenyataannya, yang kita butuhkan adalah memanggil fungsi apa fungsi debounce dikembalikan.