給react加try-catch
- 2019 年 12 月 4 日
- 筆記
最近在一個使用fis構建的react.js項目里遇到個問題,render函數里如果發生了運行時錯誤,比如說某個對象沒有判斷就直接去訪問其屬性,那我所知道的就是,頁面不正常了,特別是有嵌套子組件的時候,我可得一個個一層層去排查判斷,去加try-catch。。。
好像react的開發體驗不應該是這樣子的。
通常來說,使用react的時候都配合以webpack構建,再加個webpack-dev-client,不僅有js live reload還能hot module reload,不離開編輯器的情況下就能一直調試下去。而且當出現運行時錯誤時,會有明確的error stack列印在頁面上。為什麼使用fis構建的就不行呢?
先就自己遇到的這個問題來說,我通過多次手動try-catch的方式,找到了render失敗的原因,那麼這個「手動」的方式是不是可以自動?通常就是monkeypatch,在當前類定義之後,藉助於js這種動態修改類定義的特性,可以這樣子:
var unsafeCreateClass = React.createClass; React.createClass = function(spec) { var unsafeRender = spec['render']; spec['render'] = function() { try { return unsafeRender.apply(this, arguments); } catch(e) { console.log(e); } } return unsafeCreateClass.apply(this, arguments); }
社區的一個詳細實現:https://github.com/skiano/react-safe-render/blob/feature/safe-methods/index.js 當然,這種傳統方式在使用ES6的組件上是無效的,所以針對另一種寫法可以這樣子:
class MyComponent extends React.Component { render() { return <div>render something here</div>; } } function wrapTryCatch(Component) { let oldRender = Component.prototype.render; Component.render = function() { try { oldRender.apply(this, arguments); } catch(e) { console.log(e); } } return Component; } exports default wrapTryCatch(MyComponent);
看起來大同小異,不過需要在每個Component的實現之後再wrap一下,包裝出一個新的組件出來。而這種wrapper,藉助於es7的新語法,decorator,(引入babel-plugin-transform-decorators)又可以這麼寫:
@wrapTryCatch class MyComponent extends React.Component { render() { return <div>render something here</div>; } }
社區實現:https://www.npmjs.com/package/react-try-catch-render
然而,這還是不能讓人滿意的,畢竟遺留程式碼那麼多,難道我要一個個去添加這種wrapper? 想想看,現在連decorator這種新語法都能通過babel插件來支援了,為什麼不能再通過類似方法來把decorator都自動加進去呢?
事實上,react-try-catch-render(也就是上個例子)這個文檔是指出其由react-transform-catch-errors得到的啟發,順著這一點,最後是找到了babel-plugin-react-transform這個插件,剛好就能滿足這個需求。而且,它本身已經內置在webpack-dev-client中,所以webpack構建的開發方式才會如此方便看到錯誤。
按照給出的步驟,自行安裝完依賴之後,在fis中對應的babel plugins配置部分添加:
"plugins": [ ["react-transform", { "transforms": [{ "transform": "react-transform-catch-errors", "imports": ["react", "redbox-react"] }] }] ]
配置選項中的imports傳入了兩個參數,這兩個參數是react-transform會傳給transform插件使用的,其中redbox-react 是一個自定義的錯誤處理組件,之前在webpack構建方式下的開發經常看到的紅色框框原來就是它了!在實際使用中,可以按需替換,比如說實現badjs上報等。最後試了一下,在fis的構建方式下,也成功看到了紅色框框,以後開發過程出現運行錯誤就頁面不會安安靜靜地失敗了。
當然,到這裡為止都只是在關心render函數的報錯,其它階段的回調,其實都是類似的實現。
最後一種方式給了很大的啟發和想像空間,現在藉助於babel的幫助,我們可以在語法層面對js進行增強,在構建階段就完成對功能的補充,這種方法現在看來,work like a charm!
惹不住還是看了下babel transform插件是如何開發實現的,文檔在此。babel作為一個源碼轉換編譯器,是一個源碼->ast->源碼的過程,而transform插件所做的事就是在ast->源碼的階段。在對AST遍歷的過程,按插件介面形式提供visitor,可以細緻到對每個AST 節點進行修改(添加,刪除,替換等)。所謂的visitor其實就是訪問節點時候的鉤子/回調。給visitor傳入的參數path,給我一種一沙一宇宙的感覺,path提供的屬性和操作就可以勾畫出整個AST。