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);
});
它不起作用,因为在类描述对象创建期间,不是创建的对象本身。 不会返回您期望的内容,因为上下文不是对象本身(实际上尚未真正存在,因为它刚刚被创建)。this
this.method
this
不是一个好主意:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
这一次,您将有效地创建一个取消取消的函数,该函数调用您的 .问题是您在每次调用时都重新创建它,因此新创建的deboounce函数对以前的调用一无所知!随着时间的推移,您必须重用相同的去抖动函数,否则去抖动将不会发生。this.method
debouncedMethod
不是一个好主意:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
这有点棘手。
该类的所有已挂载实例将共享相同的取消抖动函数,大多数情况下,这不是您想要的!请参阅 JsFiddle:3 个实例在全局范围内仅产生 1 个日志条目。
您必须为每个组件实例创建一个去抖动函数,而不是在每个组件实例共享的类级别创建单个去抖动函数。
处理 React 的事件池
这是相关的,因为我们经常想要去抖动或限制 DOM 事件。
在 React 中,您在回调中接收的事件对象(即 )是池化的(现在已记录在案)。这意味着在调用事件回调后,您收到的 SyntheticEvent 将被放回具有空属性的池中,以降低 GC 压力。SyntheticEvent
因此,如果以异步方式访问原始回调的属性(如果限制/取消声明,则可能会删除所访问的属性。如果希望事件永远不要放回池中,可以使用该方法。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);
};
将打印第 2 个(异步),因为允许您避免将事件放回池中。hasNativeEvent=true
persist
您可以在此处测试这两种行为:JsFiddle
阅读Julen的答案,了解使用节流/去抖功能的示例。persist()