2019年:尝试钩子+承诺跳跃
这是我如何解决这个问题的最新版本。我会用:
awesome- debount -promise来debount async函数
使用-constant将该函数存储到组件中
React-async-hook来获取结果到组件中
这是一些最初的连接,但你是在自己组成原始块,你可以让你自己的自定义钩子,这样你只需要这样做一次。
// 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-debounc -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 });
};
}
debound函数确保:
API调用将被撤销
debented函数总是返回一个promise
只有最后一个电话的承诺才能解决问题
一个这个。setState({result});每个API调用会发生什么
最后,如果你的组件卸载了,你可以添加另一个技巧:
componentWillUnmount() {
this.setState = () => {};
}
注意,Observables (RxJS)也非常适合用于分解输入,但它是一个更强大的抽象,可能更难正确学习/使用。
< 2017:还想用回调解调吗?
这里的重要部分是为每个组件实例创建单个debpublished(或throttled)函数。您不希望每次都重新创建debounce(或throttle)函数,也不希望多个实例共享同一个debounce函数。
我没有在这个答案中定义一个deboundation函数,因为它并不真正相关,但这个答案将与_.debounce的下划线或lodash以及任何用户提供的deboundation函数完美地工作。
好主意:
因为debound函数是有状态的,所以我们必须为每个组件实例创建一个debound函数。
ES6 (class属性):推荐
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);
});
它不会工作,因为在类描述对象创建期间,这不是创建的对象本身。这一点。方法不会返回你期望的内容,因为这个上下文不是对象本身(它实际上还不存在,因为它刚刚被创建)。
这不是个好主意:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
这一次,您有效地创建了一个调用This .method的debaded函数。问题是您在每次debouncedMethod调用时都要重新创建它,因此新创建的debounce函数不知道以前的调用!您必须在一段时间内重用相同的回调函数,否则将不会发生回调。
这不是个好主意:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
这有点棘手。
类的所有挂载实例都将共享相同的debked函数,这通常不是您想要的!请参阅JsFiddle: 3个实例全局只生成1个日志条目。
您必须为每个组件实例创建一个debked函数,而不是在类级别上创建一个由每个组件实例共享的debked函数。
负责React的事件池
这是相关的,因为我们经常想要反弹或抑制DOM事件。
在React中,你在回调中接收到的事件对象(即SyntheticEvent)是池化的(现在有文档了)。这意味着在事件回调被调用之后,您接收到的SyntheticEvent将被放回具有空属性的池中,以减少GC压力。
因此,如果您以异步方式访问SyntheticEvent属性到原始回调(如果您节流/debounce,可能会出现这种情况),您访问的属性可能会被擦除。如果希望事件永远不被放回池中,可以使用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,因为持久化允许您避免将事件放回池中。
您可以在这里测试这两个行为:JsFiddle
请阅读Julen的回答,了解在throttle/debounce函数中使用persist()的示例。