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年:尝试Promise反弹
我们通常希望对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调用将被删除
- 去抖功能总是返回一个承诺
- 只有最后一次通话的返回承诺会解决
- 一个
this.setState({ result });
将每个API调用发生
最终,如果卸载了组件,您可能会添加另一个技巧:
componentWillUnmount() {
this.setState = () => {};
}
请注意,Observables(RxJS)也可以非常适合去抖动输入,但是它是更强大的抽象,可能很难正确学习/使用。
<2017:仍要使用回调反跳吗?
这里的重要部分是为每个组件实例创建一个单独的去抖动(或抑制)功能。您不想每次都重新创建去抖动(或调节)功能,也不想多个实例共享相同的去抖动功能。
我没有在此答案中定义反跳功能,因为它实际上并不相关,但是该答案与_.debounce
下划线或lodash以及任何用户提供的反跳功能完美配合。
好主意:
因为去抖功能是有状态的,所以我们必须为每个组件实例创建一个去抖功能。
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
因为事件属性已被清除。
与坚持
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
第二(异步)将打印,hasNativeEvent=true
因为persist
您可以避免将事件放回池中。
您可以在此处测试这两种行为: JsFiddle
阅读Julen的答案,以了解如何使用persist()
油门/反跳功能。
debounce
。在这里,当时onChange={debounce(this.handleOnChange, 200)}/>
,它将debounce function
每次都调用。但是,实际上,我们需要的是调用该函数,而反抖动函数返回的结果。