前言
事情源於一次跟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 的模式,所以當時在寫一些公共元件(分頁,排序)時,就採用了這種寫法:
- 分頁上面保留地址,有利於搜尋引擎的收錄跟 SEO,然後實際上採用客戶端跳轉方式去改變路由在跳轉前去拿資料。
- 這樣能保證肯定是 CSR 的方式渲染,不用去區分 SSR 的影響。
所以基於上面兩種原因,採用了這種方式去實現,其實這套邏輯如果不考慮瀏覽器回退(低頻操作)的方式,是完全能夠使用的,這也是一開始為什麼沒有發現這個bug的原因。
BUG 解決
既然 BUG 是由上面那兩種‘優點’造成的,那就對症下藥吧。
- 需要去監聽路由的變化再去根據對應得引數獲取資料。
- 保證 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 界限的區分
這個問題是由於上面的改造所帶來的思考,怎麼區分這個條件?區分了怎麼樣才是最好的方式?其實到現在我覺得也只是做到了去如何區分條件,有沒有更好的方式暫時還沒有正確的思路。
當時的思考
- 增加一個標識,SSR 跟 CSR 的時候分別去改變這個標識。
- 利用現有的資料條件去做區分,只需要儲存資料的時候將路由引數儲存即可。
對於第一種方法,優點能夠嚴格區分出來兩種情況,但是需要增加額外欄位。而且標識需要頻繁更改,並且每個頁面都需要增加,即使放到公共資料裡,每個頁面也需要單獨引用,工程量比較大,並且會使程式碼的可讀性變差,並且頁面邏輯,引數多的時候,修改的地方也多,容易出問題,在嘗試了一些頁面後選擇了放棄這種方式。
對於第二種方法,只需要在儲存資料的時候,增加路由引數即可,只需要調整存資料的方法,頁面內引用以及請求方法都不需要調整,但是觸發邏輯需要寫嚴謹一些,否則容易造成重複請求,資源浪費。
繼續拿分頁舉例
// 將觸發方法放在頁面內,能夠觸發這裡的一定是 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已經解決掉了,這些事情的前因後果已經講明白了,自己也算是彌補了上次沒說好的遺憾跟懊悔。
可能只看這裡的例子你感覺也不是很複雜的邏輯,但是這其中一個條件的改變,對於單一的列表頁確實也只需要修改這麼多久夠了,但是我們有一個非常複雜的頁面(個人主頁),路由組合,外加分頁,篩選條件等組合起來有幾十種的變化,要處理好這些需要組好仔細的區分,也是相當複雜的,而且在處理這些條件時,儘量選擇單一條件單一處理,避免同時依賴多個條件,否則會造成額外的影響因素,這些都是一點一點的除錯出來的。
結尾
日常多思考和積累,避免臨時抱佛腳,才不會關鍵時刻卡殼,心裡素質有待加強,只是一普普通通的談話為什麼會這麼緊張?亡羊補牢,為時未晚,對於自己的不足要做到及時補充,加油打工人!!!
題外話
大多數程式設計師其實都在處理業務,如何從業務中體現價值?或者說在彙報的時候如何去展示業務中的亮點?當你覺得處理的業務沒什麼亮點又該怎麼取講?歡迎大佬給出建議跟討論!