2023年了你还不会乐观 UI 吗?

19,357 阅读3分钟

什么是乐观 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. 点赞评论以及收藏

image.png

在收藏,点赞,评论或者是在列表中更新状态,是乐观 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. 拖拽排序

image.png

拖拽排序是一个很常见的功能,也是乐观 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