功夫在平時

code_shuai發表於2022-05-12

前言

事情源於一次跟CTO的交流,當時由於緊張而且距離處理這些 bug 的時間過得較長,所以當時答的一塌糊塗,所以就想著重新梳理下當時回答的問題,講清楚問題的原由,做到功夫在平時,同時加深記憶。

總共說了兩件事兒:

1. 翻頁問題的異常。
2. SSR 與 CSR 界限的區分。

總的原因

發生這些問題有一個大的前提是因為,專案中了用到了服務端渲染(SSR),那麼就需要去區分 SSR 渲染與 CSR 渲染的情況。也因為之前沒有處理過 SSR 的專案,所以在前期一些元件跟業務處理思想還是 CSR 的模式,導致了一系列的問題。

分頁問題的異常

  • BUG 描述

    專案已經上線了很長一段時間了,突然有使用者反饋,瀏覽器回退按鈕回退頁面的時候路由已經回退成功,但是頁面資料還是保持的當前的資料。

  • BUG 分析

    分頁元件時統一封裝的,那就去看看咋回事兒吧!先看程式碼

    const clickPage = (page) => {
      if (callBack && typeof callBack === 'function') {
        // 這個方法是去請求分頁的資料
        callBack(page);
      }
    
      if (pathName.includes('?')) {
        history.push(`${pathName}&page=${page}`);
      } else {
        history.push(`${pathName}?page=${page}`);
      }
     };
    
    <Pagination.Item
    href={href}
    onClick={(e) => {
      e.preventDefault();
      clickPage();
    }}
    >
    頁碼
    </Pagination.Item>

    上面給出一個頁碼的例子,Pagination.Item 會被渲染成 a 標籤, 所以從這裡就能確定產生這個問題的原因了,我們先去拿了當前點選頁面的資料,然後再去跳轉了頁面。

當時看到這就懵逼了,怎麼會有這麼奇葩的操作呢?分頁不是應該先跳轉頁面,然後在頁面內去監聽頁面的變化,在去請求資料這個流程麼?為啥子要這樣處理呢?

這就要回到前面所說的大原因了,SSR 跟 CSR 載入場景需要區分,為什麼要去區分?因為如果所有的模式都去走 SSR 的話,服務端的壓力會很大,所以一些使用者的點選操作比如翻頁、排序等,這些操作就可以採用 CSR 的模式,所以當時在寫一些公共元件(分頁,排序)時,就採用了這種寫法:

  1. 分頁上面保留地址,有利於搜尋引擎的收錄跟 SEO,然後實際上採用客戶端跳轉方式去改變路由在跳轉前去拿資料。
  2. 這樣能保證肯定是 CSR 的方式渲染,不用去區分 SSR 的影響。
    所以基於上面兩種原因,採用了這種方式去實現,其實這套邏輯如果不考慮瀏覽器回退(低頻操作)的方式,是完全能夠使用的,這也是一開始為什麼沒有發現這個bug的原因。
  • BUG 解決

    既然 BUG 是由上面那兩種‘優點’造成的,那就對症下藥吧。

    1. 需要去監聽路由的變化再去根據對應得引數獲取資料。
    2. 保證 SSR 跟 CSR 互不影響,不然服務端渲染過了客戶端在執行一遍,太浪費資源了,載入效果也會受影響。
      按照這個思路去修改,首先去掉所有傳入的回撥函式,讓元件只是完成路由的跳轉,這樣整理下來居然發現了一個好處,避免了回撥函式的層層傳入,竟然去除了大量冗餘的程式碼,唯一需要調整並且需要思考的就是如何區分 SSR 跟 CSR就可以了(具體的後面再聊)。
// 調整後的程式碼  大致

// 元件
const clickPage = (page) => {
    if (pathName.includes('?')) {
       history.push(`${pathName}&page=${page}`);
    } else {
       history.push(`${pathName}?page=${page}`);
    }
 };

<Pagination.Item
  href={href}
  onClick={(e) => {
    e.preventDefault();
    clickPage();
  }}
  >
  頁碼
</Pagination.Item>

// 頁面內
useEffect(()=> {
// 次數省略了區分服務端跟客戶端渲染的條件
 loadDataByPage(page)
}, [page])

SSR 與 CSR 界限的區分

這個問題是由於上面的改造所帶來的思考,怎麼區分這個條件?區分了怎麼樣才是最好的方式?其實到現在我覺得也只是做到了去如何區分條件,有沒有更好的方式暫時還沒有正確的思路。

  • 當時的思考

    1. 增加一個標識,SSR 跟 CSR 的時候分別去改變這個標識。
    2. 利用現有的資料條件去做區分,只需要儲存資料的時候將路由引數儲存即可。

對於第一種方法,優點能夠嚴格區分出來兩種情況,但是需要增加額外欄位。而且標識需要頻繁更改,並且每個頁面都需要增加,即使放到公共資料裡,每個頁面也需要單獨引用,工程量比較大,並且會使程式碼的可讀性變差,並且頁面邏輯,引數多的時候,修改的地方也多,容易出問題,在嘗試了一些頁面後選擇了放棄這種方式。

對於第二種方法,只需要在儲存資料的時候,增加路由引數即可,只需要調整存資料的方法,頁面內引用以及請求方法都不需要調整,但是觸發邏輯需要寫嚴謹一些,否則容易造成重複請求,資源浪費。

繼續拿分頁舉例

// 將觸發方法放在頁面內,能夠觸發這裡的一定是 CSR, 因為 SSR 請求不在這裡觸發
// 改造後 Query.page 路由攜帶的引數 dataPage頁面儲存的當前分頁資料
useEffect(() => {
  // 處理 pagenation 分頁
  // 增加這些條件判斷 是為了防止 SSR 模式後 進入瀏覽器又觸發一遍請求
  if (Query.page && dataPage !== Number(Query.page)) {
    loadDataByPage(Query.page);
  }

  if (dataPage > 1 && !Query?.page
  ) {
    loadDataByPage(1);
  }
}, [Query.page]);

到這裡修改一個瀏覽器回退頁面資料不正確的Bug已經解決掉了,這些事情的前因後果已經講明白了,自己也算是彌補了上次沒說好的遺憾跟懊悔。

可能只看這裡的例子你感覺也不是很複雜的邏輯,但是這其中一個條件的改變,對於單一的列表頁確實也只需要修改這麼多久夠了,但是我們有一個非常複雜的頁面(個人主頁),路由組合,外加分頁,篩選條件等組合起來有幾十種的變化,要處理好這些需要組好仔細的區分,也是相當複雜的,而且在處理這些條件時,儘量選擇單一條件單一處理,避免同時依賴多個條件,否則會造成額外的影響因素,這些都是一點一點的除錯出來的。

結尾

日常多思考和積累,避免臨時抱佛腳,才不會關鍵時刻卡殼,心裡素質有待加強,只是一普普通通的談話為什麼會這麼緊張?亡羊補牢,為時未晚,對於自己的不足要做到及時補充,加油打工人!!!

題外話

大多數程式設計師其實都在處理業務,如何從業務中體現價值?或者說在彙報的時候如何去展示業務中的亮點?當你覺得處理的業務沒什麼亮點又該怎麼取講?歡迎大佬給出建議跟討論!

相關文章