從工程化角度討論如何快速構建可靠React元件

lcxfs1991發表於2017-03-07

原文連結

前言

React 的開發也已經有2年時間了,先從QQ的家校群,轉成做互動直播,主要是花樣直播這一塊。切換過來的時候,業務非常繁忙,接手過來的業務比較凌亂,也沒有任何元件複用可言。

為了提高開發效率,去年10月份也開始有意識地私下封裝一些元件,並且於今年年初在專案組裡發起了百日效率提升計劃,其中就包含元件化開發這一塊。

本文並不是要談如何去寫一個 React 元件,這一塊已經有不少精彩的文章。本文關鍵詞是三個,工程化、快速和可靠。工程化是手段和工具,快速和可靠,是我們希望達到的目標。

前端工程化不外乎兩點,規範和自動化。

讀文先看此圖,能先有個大體概念:

從工程化角度討論如何快速構建可靠React元件
default

規範

目錄與命令規範

規範,主要就是目錄規範和程式碼規範。跟同事合作,經過將近20個的元件開發後,我們大概形成了一定的目錄規範,以下是我們大致的目錄約定。哪裡放原始碼,哪裡放生產程式碼,哪裡是構建工具,哪裡是例子等。有了這些的約定,日後開發和使用並一目瞭然。

__tests__ -- 測試用例
|
example -- 真實demo
|
dist -- 開發者使用程式碼
|
src -- 原始碼
|
config -- 專案配置
|------project.js -- 專案配置,主要被 webpack,gulp 等使用
|      
|   
tools  -- 構建工具
|  
|——————start.js -- 開發環境執行命令
|——————start.code.js -- 開發環境生成編譯後程式碼命令
|
package.json複製程式碼

命令我們也進行了規範,如下,

// 開發環境,伺服器編譯
npm start 或者 npm run dev

// 開發環境,生成程式碼
npm run start.code

// 生產環境
npm run dist

// 測試
npm test

// 測試覆蓋率
npm run coverage

// 檢查你的程式碼是否符合規範
npm run lint複製程式碼

程式碼規範

程式碼規範,主要是寫 js,csshtml 的規範,基本我們都是沿用團隊之前制定好的規範,如果之前並沒有制定,例如 React 的 jsx 的寫法,那麼我們就參考業界比較優秀的標準,並進行微調,例如 airbnbJavaScript 規範,是不錯的參考。

自動化

開發與釋出自動化

規範是比較人性的東西,憑著人對之的熟悉就可以提高效率了,至於那些工作繁複的流程,單憑人的熟悉也會達到極限,那麼我們就需要藉助自動化的工具去突破這重極限。

例如程式碼規範,單憑人的肉眼難以識別所有不合規範的程式碼,而且效率低下,藉助程式碼檢測工具就可讓人卸下這個重擔。如 css ,我們推薦使用 stylelint ,js 則是 eslint。有這種自動化的工具協助開發者進行檢查,能更好地保障我們的程式碼質量。

自動化最為重要的任務是,去保證開發過程良好的體驗還有釋出生產程式碼。實際上,開發和釋出元件的整個過程跟平時開發一個任務很像,但卻又略有差異。

首先是開發過程中,我們希望一邊開發的時候,我們開發的功能能夠顯示出來,這時最好能搭建一個demo,我們把 demo 放到了 example 目錄下,這點對 UI 元件(像toast, tips等元件) 尤為重要,邏輯元件(像ajax, utils等元件),可以有 demo,也可以採取測試驅動開發的方式,先制定部份測試用例,然後邊開發邊進行測試驗證。

開發過程中的這個 demo, 跟平時開發專案基本一致,我們就是通過配置,把 html,js, css 都搭建好,而且我們是開發 React 元件,引入熱替換的功能令整個開發流程非常流暢。這裡分別是 webpack 和配合 `webpack 開發的靜態資源伺服器的兩份配置: webpack & server

但是釋出元件的這個過程跟開發專案卻又很不同。開發專案,我們需要把所有的依賴都打包好,然後一併釋出。但對於元件來說,我們只需要單獨將它的功能釋出就好了,它的相關依賴可以在實際開發專案中引用時一併再打包。因此這裡的 package.json 寫的時候也要有所區分。跟只跟開發流程、構建、測試相關的,我們一律放在 devDependencies 中,元件實際依賴的庫,則主要放在 dependencies 中。

鑑於我們專案一般採用 webpack 打包,因此我們一般只需要 es6 import 的引入方式,那我們直接用 babel 幫我們的專案進行生產程式碼的編譯打包就可以了,這樣能有效減少冗餘程式碼。配置好 .babelrc,然後配置 package.json 的打包命令即可。要注意的是,你的元件可能含有樣式檔案,配置命令的時候要記得將樣式檔案也複製過去,像下面的命令,--copy-files 引數就是為了將樣式檔案直接拷貝到 dist 目錄下。

babel src --out-dir dist --copy-files複製程式碼

但有時候,你也想元件能相容多種引用方式,即 umd,那 babel 的這種打包就不夠了。這時你可以藉助 webpack 打包 library 的能力。可參考此 配置。主要是配置 output.libraryoutput.libraryTarget

output: {
       // other config
        library: "lib",   // 表示以什麼名字輸出,這裡,會輸出為如 exports["lib"]
        libraryTarget: "umd", // 表示打包的方式
},複製程式碼

另一點要注意的是,我們只需打包元件的邏輯就好了,那些依賴,可以等實際生產專案的時候再進行解析。但 webpack 預設會將依賴也打包進行,為了避免這點,你需要將這些依賴一一配置成為 external,這就告訴了 webpack 它們是外部引用的,可以不用打包進來。

打包完成之後,根據指引進行 npm publish 就可以了。這裡大體總結了一下我們開發元件的一些流程和注意事項

測試自動化

上述講的都跟如何提升開發效率有關的,即滿足 “快速” 這個目標,對 ”可靠“ 有一定幫助,如穩定的流程和良好的程式碼規範,但並沒有非常好地保證元件地穩定可靠。需要 ”可靠“的元件,還需要測試來保證。

不少開發者做測試會使用 mocha,如果是 UI 元件可能會配置上 karma。而 React 元件測試還有一個更好的選擇,就是官方推薦的 jest + enzyme

jestjasmine 有點類似,將一個測試庫的功能大部份整合好了(如斷言等工具),一鍵安裝 babel-jest 可以用 es6 直接寫測試用例,搭配 jest-environment-jsdomjsdom 能夠模擬瀏覽器環境,結合 airbnb 寫的 react 測試庫 enzyme, 基本能滿足大部份的 React 測試需求。確實符合官方的宣傳語 painless,這是一個無痛的測試工具。

測試邏輯元件問題倒不大,UI元件對於大部份的情況都可以,許多事件都可以通過enzyme 模擬事件進行測試。但這裡舉的例子, react-list-scroll 元件,一個 React 的滾動列表元件,碰巧遇到一種比較難模擬的情況,就是對 scroll 事件的模擬。這裡想展開說一下。

對於 Reactscroll 事件而言,必須要繫結在某個元素裡才能進行模擬,不巧,對於安卓手機來說,大部份 scroll 事件都是繫結在 window 物件下的。這就非常尷尬了,需要藉助到 jsdom 的功能。通過 jest-environment-jsdom,它能夠將 jsdom 注入到 node 執行環境中,因此你可以在測試檔案中直接使用 window 物件進行模擬。例如下面程式碼,模擬滾動到最底部:

test('scroll to bottom', (done) => {

    const wrapper = mount(<Wrapper />);

    window.addEventListener('scroll', function(e) {
        setTimeout(() => {
            try {
                // expect 邏輯
                done();
            }
            catch(err) {
                done.fail(err);
            }
        }, 100);
        jest.runAllTimers();

    });

    let scrollTop = 768;
    window.document.body.scrollTop = scrollTop; // 指明當前 scrollTop到了哪個位置
    window.dispatchEvent(new window.Event('scroll', {
        scrollTop: scrollTop
    }));

});複製程式碼

細心的你會發現,上圖還有一些定時器的邏輯。原因是在元件中會有一些截流的邏輯,滾動時間隔一段時間才去檢測滾動的位置,避免效能問題,因此加一個定時器,等待資料的返回,而 jest.runAllTimers(); 則是用於告訴定時器馬上跑完。

除此之外,定時器裡還有個 try catch 的邏輯,主要是如果 expect 驗證不通過,jest 會報告錯誤,這時需用錯誤捕獲的辦法將錯誤傳給 done (非同步測試的回撥),這樣才能正常退出這一個測試用例,否則會返回超時錯誤。

安卓測完了,那iPhone呢?iPhone 的 scroll 事件是繫結在具體某個元素裡的,但我這裡又不是通過 React 的 onScroll 來繫結。首先我們得通過 window.navigator.userAgent 來區分手機型別。但由於 userAgent 只有 getter 函式,直接設定值會報錯,因此我們要新增一個 setter 函式給它,用這段示例程式碼:

Object.defineProperty(window.navigator, "userAgent", (function(_value){
  return {
    get: function _get() {
      return _value;
    },
    set: function _set(v) {
        _value = v;
    }
  };
})(window.navigator.userAgent));

let str = "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1";
window.navigator.userAgent = str;複製程式碼

然後,去找到這個繫結的元素,進行事件監聽和分發就好了:

const wrapper = mount(<Wrapper />),
      scrollComp = wrapper.find(Scroll),
      scrollContainer = scrollComp.nodes[0].scrollContainer;

scrollContainer.addEventListener('scroll', function(e) { //... });

scrollContainer.dispatchEvent(// ... );複製程式碼

總結

本文主要是提取了開發元件工程化的一些關鍵要點,具體的開發腳手架可以參考 steamer-react-component,裡面主要舉了pure-render-deepCompare-decoratorreact-list-scroll,一個邏輯元件,一個UI元件,共兩個示例,對照著腳手架的文件,從目錄規範、開發流程、釋出都寫得較為清楚,大家開發元件的時候,可以根據情況做些調整。

如有謬誤,懇請斧正。


本文對你有幫助?歡迎掃碼加入前端學習小組微信群:

從工程化角度討論如何快速構建可靠React元件

相關文章