大家好,我是若川。持續組織了近一年的源碼共讀活動,感興趣的可以?加我微信?ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北
籍前端群,可加我微信進群。
本文是深入淺出 ahooks 源碼系列文章的第八篇,這個系列的目標主要有以下幾點:
加深對 React hooks 的理解。
學習如何抽象自定義 hooks。構建屬于自己的 React hooks 工具庫。
培養閱讀學習源碼的習慣,工具庫是一個對源碼閱讀不錯的選擇。
注:本系列對 ahooks 的源碼解析是基于 v3.3.13
。自己 folk 了一份源碼,主要是對源碼做了一些解讀,可見 詳情[1]。
本篇文章算是該系列的一個彩蛋篇,記錄一下第一次給開源項目提 PR 的過程(之前好像也有過,不過那個非常小的一個改動),希望能夠幫助更多的人參與到開源項目中來。
起因
在寫了幾篇關于 ahooks 的文章之后,收到了官方同學的私信。

這讓我受寵若驚的同時也有點小興奮和惶恐。
興奮是,之前感覺參與開源是一件遙不可及的事情,現在似乎我也能夠去做了。當然也有私心,假如我的簡歷上有給開源項目做貢獻的經歷,那豈不是一個不錯的加分項?
惶恐的是,我之前沒有參與過開源項目,擔心自己不能做好這件事。
根據大佬的建議,我決定先從一些 issue 入手,也就是幫忙解決一下 issue。
明確問題OR需求
于是我抱著試試看的態度,看了一下官方的 issue,看到這么一條。issue 詳情[2]。

剛好我之前對 useRequest 源碼做過一些分析——如何使用插件化機制優雅的封裝你的請求[3]。于是我決定 fix 一下這個 issue。
這個 issue 的需求很簡單,就是希望輪詢失敗后,能夠支持最大的輪詢次數,假如失敗的次數大于這個值,則停止輪詢。
編碼前準備
首先,從 ahooks 官方 GitHub 中 folk 一份。這個操作我之前已經做了。

第二步,基于 master 切換一個功能分支。如下:
git?checkout?-b?fix/pollingSupportRetryCount
最后就是環境的一些初始化操作,不同的倉庫不同,ahooks 如下:
yarn?run?init
yarn?start
功能實現
我們先來看下現在 useRequest 的輪詢的實現,其原理主要是在一個請求結束的時候(不管成功與失敗),通過 setTimeout 進行重新請求,達到輪詢的效果。
onFinally:?()?=>?{//?省略部分代碼...//?通過?setTimeout?進行輪詢timerRef.current?=?setTimeout(()?=>?{fetchInstance.refresh();},?pollingInterval);
},
我的想法是,定義一個 options 參數,pollingErrorRetryCount
,默認為 -1,代表沒有限制。
另外定義一個變量,記錄當前重試的次數:
const?countRef?=?useRef<number>(0);
當開發者設置了 pollingErrorRetryCount,并且重試的數量大于該值,我們就直接返回,不執行輪詢的邏輯。
當成功或者失敗的時候,更新當前重試的次數:
onError:?()?=>?{countRef.current?+=?1;
},
onSuccess:?()?=>?{countRef.current?=?0;
},
然后在請求結束的時候,判斷重試的次數有沒有達到了開發設置的次數,假如沒有則執行重試操作。有則重置重試的次數,停止輪詢。
onFinally:?()?=>?{if?(pollingErrorRetryCount?===?-1?||//?When?an?error?occurs,?the?request?is?not?repeated?after?pollingErrorRetryCount?retries(pollingErrorRetryCount?!==?-1?&&?countRef.current?<=?pollingErrorRetryCount))?{//?忽略部分代碼timerRef.current?=?setTimeout(()?=>?{fetchInstance.refresh();},?pollingInterval);}?else?{countRef.current?=?0;}
},
測試用例
上述整體的改造并不困難,但是我在寫測試用例的時候,就開始踩坑了,因為我很少書寫前端的測試用例,還是針對于 hooks 的測試用例。這里是我耗時最多的地方。
最終用例如下:
//?省略部分代碼...
//?if?request?error?and?set?pollingErrorRetryCount
//?and?the?number?of?consecutive?failures?exceeds?pollingErrorRetryCount,?polling?stops
let?hook2;
let?errorCallback;
act(()?=>?{errorCallback?=?jest.fn();hook2?=?setUp(()?=>?request(0),?{pollingErrorRetryCount:?3,pollingInterval:?100,pollingWhenHidden:?true,onError:?errorCallback,});
});expect(hook2.result.current.loading).toEqual(true);
expect(errorCallback).toHaveBeenCalledTimes(0);act(()?=>?{jest.runAllTimers();
});
await?hook2.waitForNextUpdate();
expect(hook2.result.current.loading).toEqual(false);
expect(errorCallback).toHaveBeenCalledTimes(1);act(()?=>?{jest.runAllTimers();
});
await?hook2.waitForNextUpdate();
expect(errorCallback).toHaveBeenCalledTimes(2);act(()?=>?{jest.runAllTimers();
});
await?hook2.waitForNextUpdate();
expect(errorCallback).toHaveBeenCalledTimes(3);act(()?=>?{jest.runAllTimers();
});
await?hook2.waitForNextUpdate();
expect(errorCallback).toHaveBeenCalledTimes(4);act(()?=>?{jest.runAllTimers();
});
expect(errorCallback).toHaveBeenCalledTimes(4);act(()?=>?{hook2.result.current.run();
});
act(()?=>?{jest.runAllTimers();
});
await?hook2.waitForNextUpdate();
expect(errorCallback).toHaveBeenCalledTimes(5);hook2.unmount();
//?省略部分代碼...
大致解釋下該測試用例的邏輯,我設置了重試三次,錯誤之后,運行了三次,errorCallback
就會被調用了 4 次(包括錯誤那次)。在第五次執行的時候,就不會執行 errorCallback
,也就還是 4 次。然后我們手動 run 一次請求,期待 errorCallback
應該執行 5 次。
這里踩了一個坑,就是第五次請求的時候,我之前是會寫一個等待定時器執行的操作,但實際上這里它是不會執行定時器的,導致一直報錯,在這里折騰了很久。后來刪除了下面的代碼才執行成功。
act(()?=>?{jest.runAllTimers();
});
-?await?hook2.waitForNextUpdate();
expect(errorCallback).toHaveBeenCalledTimes(4);
文檔以及 Demo 補充
畢竟加了一個新的 API 參數,需要在文檔中注明,而且中英文文檔都需要補充,還加上了一個 Demo 示例。


提 PR
上述都完成之后,就可以提交你的代碼了,提交完,去到在你 folk 過來的項目中,可以看到這個。

我們需要點擊圖中框起來的「Compare & pull request 」,之后就會出現如下圖

默認會幫我們選好分支的,我們只需要完善其中的信息,還有我們之前提交的 message 也可以修改。最好可以用英文來解釋,本次提交的內容。
最后點擊提交之后就好了。
還有一個提 PR 的入口,如下所示:

最后等待官方 CR 就可以了(上面的實現其實部分是 CR 后改的)。目前該 PR 已經被合入到 master。
總結思考
給開源項目提 PR 操作過程不是一件很復雜的事情,重點在于需求的修改。往往需要考慮到多種邊界場景,這個時候,我們就需要前端的單元測試來幫助我們覆蓋全面的場景。
另外,對于一些還沒有參與開源項目經驗的同學來講,我覺得類似 ahooks 這種工具庫是一個不錯的選擇:
它的模塊劃分更加清晰,你改了一個模塊的功能,影響面可以更好的預估。對新人比較友好。
邏輯相對簡單,其實你會發現很多代碼說不定在你們的業務項目中的 utils/hooks 文件夾中就有。
社區比較活躍,維護者能夠較快的響應。
希望對大家有所幫助。
系列文章:
大家都能看得懂的源碼(一)ahooks 整體架構篇[4]
如何使用插件化機制優雅的封裝你的請求hook [5]
ahooks 是怎么解決 React 的閉包問題的?[6]
ahooks 是怎么解決用戶多次提交問題?[7]
ahooks 中那些控制“時機”的hook都是怎么實現的?[8]
如何讓 useEffect 支持 async...await?[9]
如何讓定時器在頁面最小化的時候不執行?[10]
參考資料
[1]
詳情: https://github.com/GpingFeng/hooks
[2]issue 詳情: https://github.com/alibaba/hooks/issues/1645
[3]如何使用插件化機制優雅的封裝你的請求: https://juejin.cn/post/7105733829972721677
[4]大家都能看得懂的源碼(一)ahooks 整體架構篇: https://juejin.cn/post/7105396478268407815
[5]如何使用插件化機制優雅的封裝你的請求hook : https://juejin.cn/post/7105733829972721677
[6]ahooks 是怎么解決 React 的閉包問題的?: https://juejin.cn/post/7106061970184339464
[7]ahooks 是怎么解決用戶多次提交問題?: https://juejin.cn/post/7106461530232717326
[8]ahooks 中那些控制“時機”的hook都是怎么實現的?: https://juejin.cn/post/7107189225509879838
[9]如何讓 useEffect 支持 async...await?: https://juejin.cn/post/7108675095958126629
[10]如何讓定時器在頁面最小化的時候不執行?: https://juejin.cn/post/7109399243202232357
我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
如何準備20K+的大廠前端面試
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 lxchuan12、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 lxchuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~