前端開發(fā)中的樂(lè)觀更新

事情的起因是這樣的,在使用react開發(fā)項(xiàng)目的時(shí)候,郵箱規(guī)則列表頁(yè)面有一個(gè)Switch按鈕,點(diǎn)擊按鈕可以切換規(guī)則的啟用或者禁用狀態(tài),剛開始的做法是:用戶點(diǎn)擊了按鈕之后先改變IsEnabled的狀態(tài),然后調(diào)用update接口,更新服務(wù)器端的接口,使用await等到update接口調(diào)用成功之后,再去調(diào)用獲取郵箱規(guī)則列表的接口,更新列表狀態(tài):

const handleEdit = async (flag: boolean, item: MailRuleItem) => {
        // 1. 創(chuàng)建更新后的對(duì)象(使用函數(shù)式更新確保最新狀態(tài))
        const updateItem = {
          ...item,
          IsEnabled: flag ? 1 : 0,
          Actions: {
            ...item.Actions,
            MoveToFolder: item.Actions.MoveToFolder?.UniqueId,
            Delete: item.Actions.Delete ? '1' : '0',
            MarkAsRead: item.Actions.MarkAsRead ? '1' : '0',
            CopyToFolder: item.Actions.CopyToFolder?.UniqueId,
          },
          Conditions: { ...item.Conditions }
        };
        await updatemailrule(updateItem);//更新的接口
       onReload();//刷新列表
      };

遇到的問(wèn)題是:update接口響應(yīng)比較慢,導(dǎo)致點(diǎn)擊完按鈕之后沒(méi)有反應(yīng),好像是按鈕沒(méi)被點(diǎn)擊一樣,用戶體驗(yàn)不好
需求:點(diǎn)擊完之后,立馬更新按鈕的狀態(tài),如果接口發(fā)生錯(cuò)誤,需要恢復(fù)按鈕的狀態(tài)
這里通過(guò)各種搜索,了解到了樂(lè)觀更新,這個(gè)詞聽著很陌生,但其實(shí)很簡(jiǎn)單,原理就是點(diǎn)擊完按鈕之后,立馬把被點(diǎn)擊的數(shù)據(jù)的狀態(tài)更改,給人的感覺(jué)是接口已經(jīng)調(diào)用成功了,但實(shí)際僅僅是前端意義的更改,關(guān)于樂(lè)觀更新解釋如下:
樂(lè)觀更新是一種提升用戶體驗(yàn)的前端優(yōu)化策略,其核心思想是:在等待服務(wù)器響應(yīng)前,先假設(shè)操作會(huì)成功,立即更新本地UI狀態(tài)。如果最終請(qǐng)求失敗,再回滾到之前的狀態(tài)。

核心特點(diǎn)

  1. 即時(shí)反饋

    • 用戶操作后UI立即變化,無(wú)需等待網(wǎng)絡(luò)延遲
    • 典型場(chǎng)景:點(diǎn)贊、開關(guān)切換、列表項(xiàng)操作
  2. 異步補(bǔ)償

    • 請(qǐng)求失敗時(shí)自動(dòng)回滾狀態(tài)
    • 通常會(huì)配合錯(cuò)誤提示
  3. 狀態(tài)可逆

    • 必須保留操作前的狀態(tài)副本
    • 回滾時(shí)需要準(zhǔn)確恢復(fù)上下文

實(shí)現(xiàn)原理(三步流程)

    用戶->>UI: 觸發(fā)操作(如點(diǎn)擊開關(guān))
    UI->>UI: 立即更新本地狀態(tài)(樂(lè)觀更新)
    UI->>服務(wù)端: 發(fā)送異步請(qǐng)求
    alt 請(qǐng)求成功
        服務(wù)端-->>UI: 返回200
        UI->>UI: 保持更新后狀態(tài)(可選二次確認(rèn))
    else 請(qǐng)求失敗
        服務(wù)端-->>UI: 返回錯(cuò)誤
        UI->>UI: 自動(dòng)回滾狀態(tài)+錯(cuò)誤提示
    end

典型實(shí)現(xiàn)代碼(React示例)

const [items, setItems] = useState(data);

const handleToggle = async (id) => {
  // 1. 保存原始狀態(tài)
  const originalItems = [...items];
  
  // 2. 樂(lè)觀更新:立即切換UI狀態(tài)
  setItems(prev => prev.map(item => 
    item.id === id ? { ...item, active: !item.active } : item
  ));

  try {
    // 3. 發(fā)送真實(shí)請(qǐng)求
    await api.toggleItem(id); 
  } catch (error) {
    // 4. 失敗時(shí)回滾
    setItems(originalItems);
    toast.error("更新失敗");
  }
};

適用場(chǎng)景 vs 不適用場(chǎng)景

適合場(chǎng)景 不適合場(chǎng)景
? 高頻交互操作(點(diǎn)贊/收藏) ? 金融交易等關(guān)鍵操作
? 延遲敏感型功能(開關(guān)切換) ? 依賴服務(wù)端復(fù)雜計(jì)算的場(chǎng)景
? 冪等性操作(可重復(fù)執(zhí)行) ? 非冪等性操作

最后使用樂(lè)觀更新問(wèn)題得以解決:

    const handleEdit = async (flag: boolean, item: MailRuleItem) => {
        // 1. 創(chuàng)建更新后的對(duì)象(使用函數(shù)式更新確保最新狀態(tài))
        const updateItem = {
          ...item,
          IsEnabled: flag ? 1 : 0,
          Actions: {
            ...item.Actions,
            MoveToFolder: item.Actions.MoveToFolder?.UniqueId,
            Delete: item.Actions.Delete ? '1' : '0',
            MarkAsRead: item.Actions.MarkAsRead ? '1' : '0',
            CopyToFolder: item.Actions.CopyToFolder?.UniqueId,
          },
          Conditions: { ...item.Conditions }
        };
      
        // 2. 樂(lè)觀更新 - 使用函數(shù)式更新確?;谧钚聽顟B(tài)
        setRules(prevRules => {
          // 返回更新后的狀態(tài)
          return prevRules.map(rule => {
            if (rule.Id === item.Id) {
              return updateItem;
            } else {
              return rule;
            }
          }
            
          );
        });
      
        try {
          // 3. 發(fā)送異步請(qǐng)求
          await updatemailrule(updateItem);
          // 4. 請(qǐng)求成功后,可以調(diào)用onReload獲取最新數(shù)據(jù)
          // 或者直接使用返回的更新后數(shù)據(jù)(更推薦)
          onReload();
        } catch (error) {
          // 5. 請(qǐng)求失敗時(shí),回滾到之前保存的快照
          setRules(prevRules => {
            // 找到當(dāng)前項(xiàng)的原始狀態(tài)
            const originalItem = prevRules.find(r => r.id === item.id);
            if (!originalItem) return prevRules;
            return prevRules.map(rule => {
                if (rule.id === item.id) {
                  return originalItem;
                } else {
                  return rule;
                }
            }
              
            );
          });
          console.error("更新失敗:", error);
        }
      };
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容