2019:フックを試す+デバウンスの約束
これは、私がこの問題を解決する方法の最新バージョンです。私は使うだろう:
これは初期の配線ですが、プリミティブブロックを自分で作成しているので、独自のカスタムフックを作成して、これを1回だけ行う必要があります。
// 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を念頭に置いて小さなライブラリー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 });
};
}
デバウンス機能により、以下が保証されます。
- API呼び出しはデバウンスされます
- デバウンスされた関数は常にpromiseを返します
- 最後の呼び出しで返されたpromiseのみが解決されます
this.setState({ result });
API呼び出しごとに1 つ発生します
最終的に、コンポーネントがマウント解除された場合は、別のトリックを追加できます。
componentWillUnmount() {
this.setState = () => {};
}
なお、観測(RxJS)も入力をデバウンスにぴったりすることができますが、それは正しく/使用を学ぶために難しいかもしれより強力な抽象化です。
<2017:コールバックデバウンスを引き続き使用しますか?
ここで重要なのは、コンポーネントインスタンスごとに単一のデバウンス(またはスロットル)関数を作成することです。毎回デバウンス(またはスロットル)関数を再作成する必要はありません。また、複数のインスタンスが同じデバウンス関数を共有する必要はありません。
私はこの回答でデバウンス関数を定義していません。これは実際には関連がないためですが、この回答は_.debounce
、アンダースコアまたはロダッシュのほか、ユーザーが提供するデバウンス関数でも完全に機能します。
良いアイデア:
デバウンスされた関数はステートフルであるため、コンポーネントインスタンスごとに1つのデバウンスされた関数を作成する必要があります。
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
イベントプロパティがクリーンアップされているため、2番目(非同期)が印刷されます。
持続あり
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
第2回(非同期)が印刷されますhasNativeEvent=true
ので、persist
あなたがプール内のイベントのバックを入れないようにすることができます。
ここでこれら2つの動作をテストできます:JsFiddle
スロットル/デバウンス機能での使用例については、Julenの回答を読んでくださいpersist()
。
debounce
。ここで、の場合onChange={debounce(this.handleOnChange, 200)}/>
、debounce function
毎回呼び出されます。しかし、実際には、必要なのは、デバウンス関数が返した関数を呼び出すことです。