你描述它的方式,听起来你的组件的其余部分的结构像
[state, setState] => useState(initialState)
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)
useEffect(() => {
fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
.then(response => response.json())
.then(data => setState({
data
}));
}, [paging, sorting]);
return <div>
<span>Current page is {paging}</span>
<Button onClick={() => setPaging(page => page + 1)}>Nav Next</Button>
<Button onClick={() => setPaging(page => page - 1)}>Nav Prev</Button>
// ... and so on and so forth
</div>
并且当您单击 Nav Next 或 Prev 时,您不希望在它触发的效果解决之前更新分页。如果获取失败,您不希望用户看到分页在您需要执行的任何清理操作中上升然后下降。
为了实现这一点,您不能通过设置中间的“有希望”或“待定”状态值来延迟设置“前向”值吗?例如,这行得通吗?
[state, setState] => useState(initialState)
// Component state values
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)
// "Optimist's" state values
[pendingPaging, setPendingPaging] => useState(paging)
[pendingSorting, setPendingSorting] => useState(sorting)
useEffect(() => {
// checks if current state values differ from pending,
// meaning app wants to do something
// If pending and "actual" values match, do nothing
if( paging !== pendingPaging
|| sorting !== pendingSorting
){
fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
.then(response => response.json())
.then(data => {
// things worked, updated component state to match pending
setPaging(pendingPaging);
setSorting(pendingSorting);
setState({data});
})
.catch(err => {
// ugh, I knew things would fail. Let me reset my hopes
setPendingPaging(paging);
setpendingSorting(sorting);
setState({data});
});
}
}, [pendingPaging, pendingSorting, paging, sorting, setState]);
return <div>
// show user "actual" page number
<span>Current page is {paging}</span>
// but update only pending values in response to events
<Button onClick={() => setPendingPaging(page + 1)}>Nav Next</Button>
<Button onClick={() => setPendingPaging(page - 1)}>Nav Prev</Button>
// ... and so on and so forth
</div>
通过这种方式,您可以设置您希望的状态,做某事,然后设置您的状态和期望以匹配。如果成功,显示的状态会更新以匹配乐观值,如果失败,挂起的值将设置回现实。
我看到的最大问题是,当异步调用解决时,同时进行了多个 setState 调用。React 应该对这些调用进行批处理,但您可能希望在单个调用中查找其他处理方法。
无论哪种方式,挂起的值和实际值都是相等的,所以虽然 useEffect 会触发,但它不会运行另一个提取。
编辑 1
感谢您的积极反馈,很高兴您喜欢这个概念。我同意复杂性可能会变得笨拙,但我认为您可以设置一个工厂类来更清楚地管理事物。
例如,您可以使用makePessimistic
最终输出“实际”状态、“希望”状态和至少一个usePessimisticEffect
回调的类。该类可能如下所示:
class pessimistFactory {
constructor(){
this.state = {};
this.effects = [];
}
// util from https://dzone.com/articles/how-to-capitalize-the-first-letter-of-a-string-in
ucfirst = (string) => string.charAt(0).toUpperCase() + string.slice(1);
registerState(param, initialVal){
this.state[param] = initialVal;
}
makePessimisticEffect = (callback, dependencies) => useEffect(() => {
async function handlePessimistically(){
try {
const result = await callback(...dependencies);
// update theState with result
setState({...hopefulState, result})
}catch(err){
// reset hopefulState and handle error
setHopefulState({...theState})
}
}
// Do something if some action pending
if(/* pending and current state not the same */) handlePessimistically();
}, [callback, dependencies]);
registerEffect(callback, dependencies = []){
this.effects.push(
this.makePessimisticEffect(callback, dependencies)
);
}
makeHooks = () => {
const initialState = useState(this.state);
return {
theState: initialState,
hopefulState: initialState,
effects: this.effects
}
}
}
在实践中:
// Make factory instance
const factory = new pessimistFactory();
// Register state and effects
factory.registerState('paging', initialPaging)
factory.registerState('sorting', initialSorting)
factory.registerEffect('fetchData', () => fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`))
// Make the hooks
const {theState, hopefulState, effects} = factory.makeHooks();
// Implement hooks in component
const SomeComponent = () => {
const [state, setState] = theState();
const [pendingState, setPendingState] = hopefulState();
effects.forEach(useEffect => useEffect());
return <div>displayed stuff</div>
}
最棘手的部分是设置makePessimisticEffect
效果以读取和处理由生成setState
的 s 创建的值和回调,我意识到如果在我编写它时完全损坏了。
我现在没有时间真正弄清楚细节,但我认为使用可用的新钩子(如useCallback
.
不过,我确实喜欢这个想法,所以我会在以后有时间的时候尝试解决这个问题。如果您在此之前制作了一个工作工厂课程,或者如果有人有一个完全不同的更好的解决方案,我会非常有兴趣看到它。