什么是乐观 UI
乐观 UI ( optimistic UI
) 是一种 UI
设计方式 , 它在用户执行操作后 , 不用等待服务器响应 , 立即更新 UI
。 如果操作失败 , 则回滚 UI
到之前的状态。
为什么要使用乐观 UI
- 给用户即时的反馈,界面感觉很响应迅速,带来很好的用户体验
provider a responsive user experience.
- 由于界面很流畅,可以更好地吸引和维持用户的注意力与参与。
keeps the user engaged.
- 尽管后端处理需要时间,但用户基本无感知,因为UI已经立即更新了
reduces the perceived latency.
乐观 UI 的缺点
- 如果用户在操作后离开页面,则可能会出现不一致的状态。
inconsistent state if the user leaves the page after the operation.
- 如果操作失败,则需要回滚
rollback
。这可能会导致复杂的代码,因为您需要跟踪所有可能的回滚操作。complex code to track all possible rollback operations.
如何在你的项目中实践乐观 UI
下面我将用两个简单的例子来说明如何在你的项目中实践乐观 UI。
1. 点赞评论以及收藏
在收藏,点赞,评论或者是在列表中更新状态,是乐观 UI
应用最普遍的场景,使用 swr
可以很方便实现这个功能
import useSWR from 'swr'
// req_get 是 axios 的封装,你可以换成自己项目中的请求
const fetcher = url => req_get(url);
function useList() {
const { data, isLoading} = useSWR('/api/list', fetcher)
// 建议请求的后台接口都放在这里统一处理成自己想要的格式
return { data: data.list,isLoading }
}
首先我们准备上面的列表接口, 使用 swr
加乐观 UI
可以避免大多数本地状态管理的问题,我们不需要使用 useState
来管理本地状态,只需要在请求成功后验证数据即可
import {mutate} from 'swr';
function List() {
const {data, isLoading} = useList();
const handleLike = (id) => {
const optimisticData = data.map((item) => {
if (item.id === id) {
return {
...item,
like: item.like + 1,
};
}
return item;
});
const options = {
optimisticData,
rollbackOnError(error) {
// 如果超时中止请求的错误,不执行回滚
return error.name !== 'AbortError';
},
};
// 立即更新本地数据
// 发送一个请求以更新数据
// 触发重新验证(重新请求)确保本地数据正确
// req_post 应该是一个 promise 或者异步函数以处理远程数据更改,它应该返回更新后的数据
mutate("/api/list", req_post('/api/like', {id}), options);
};
return (
<div>
{isLoading ? (
<div>loading...</div>
) : (
data.map((item) => (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.like}</span>
<button onClick={() => handleLike(item.id)}>点赞</button>
</div>
))
)}
</div>
);
}
可以看到如果后端愿意配合,将执行操作后的数据返回,我们就可以先直接更新本地数据,然后再发送请求,最后再重新验证数据,这样就可以实现乐观 UI
了。
2. 拖拽排序
拖拽排序是一个很常见的功能,也是乐观 UI
的一个很好的应用场景,我们可以使用 react-beautiful-dnd
来实现这个功能
import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd';
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function List() {
// 不再需要 useState 管理本地状态
const { data:list, isLoading} = useList();
const handleDragEnd = (result) => {
if (!result.destination) {
return;
}
const items = reorder(
list,
result.source.index,
result.destination.index
);
const options = {
optimisticData: items,
};
// req_post 也是 axios 的封装,你可以换成自己项目中的请求,需要返回更新后的数据
mutate("/api/list", req_post('/api/sort', {list: items}), options);
};
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{list.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<span>{item.title}</span>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
总结
乐观 UI
是一个很好的用户体验,但是它也有一些缺点,在实际项目中,我们需要根据实际情况来决定是否使用乐观 UI
。如果你的项目中有类似的场景,可以尝试使用 swr
来实现乐观 UI
。