打造首款React質量與性能分析工具

轉自大前端工程師 作者:王洛菲

大家好,我叫王洛菲,人稱腰哥,首屆vueconf講師,來自陸金所大前端,此文在gitchat首發,大家如果對交流實錄感興趣,請點擊文章下面的【閱讀原文】查看。

首先說一下為什麼要寫這款工具,由來是什麼吧。其實我們在不同的公司裡面,都會發現一個問題,框架這這麼一款,官方文檔介紹也很詳細,為什麼能夠寫出各種各樣的代碼,明明框架約束的很好,但是寫出來的代碼五花八門,要好好拜讀別人的代碼,才能知道其中的業務邏輯是什麼,完全是事倍功半啊。如果有好的工具,必然能夠提高公司的整體代碼質量。

其實上面的問題,很早就被各樣的牛人發現了,然後開發出了很好的工具來輔助開發,比如eslint,csslint等,幫助開發者矯正錯誤的代碼風格,統一公司的代碼風格。但是對於最近幾年很火的React框架,是否有一些好的工具來完善react代碼嗎,答案肯定是有的,其實去網上搜,肯定也能找到一些,但是今天將要介紹的一款工具,react-perf工具,是從bable層的AST來分析語法,在編譯階段進行校正,最後在編譯階段沒有問題後,再在運行階段進行性能分析,給出性能建議,提高整體代碼質量。

在給出方案前,讓我們一起看看,在大家開發過程中,都有哪些不好的代碼。在componentWillMount設置setState

componentWillMount(){ n this.setState({count:2})n }n

這個問題是無話可說的,完全不應該存在。

直接賦值state?

clickEvent () {n this.state.name = hellon this.state.age = 20n this.setState(this.state)n}n

背景:這段代碼的歷史背景是這樣的,我們的開發者在使用setState的時候,發現this.setState是非同步的,為了解決可以同步使用this.state,就用了這樣的方式。問題是,現在好多開發者都是Ctrl+c/Ctrl+v,導致好多地方都是用這樣的代碼,在這裡,我們也是堅決杜絕。

這裡只是簡單的列了一些代碼,像這樣的代碼,其實還有很多很多。對於這些代碼,完全可以在編譯階段進行杜絕。編譯階段怎麼杜絕,那就只有靠babel來搞定了,接下來就是我們要說的主題了。

編譯分析

babel這裡就不說了,這裡就直接進階到babel的插件來分析了。如果大家不熟悉babel插件,那我簡單說一下,babel插件就是通過遍歷文件結構樹,提供一種可以對AST進行增,修和查的機制。

插件具體實現步驟如下。

刷選react類

首先我們肯定是需要對react類進行處理,所以基於這一點,我們肯定是要對於類進行刷選,這裡就引出來第一個配置項。

"needAddPerfRule": {n "superClass": ["Component"]n }n

而在babel插件裡面,我們先對Class進行判斷,如果不是繼承Component,或者不是我們設置的父類,程序就返回,我們的實現如下:

Class: function Class(path, state) { n var node = path.node; n var superClass = node.superClass.name; n if (superClass !== Component n && state.opts.needAddPerfRule n && !state.opts.needAddPerfRule.superClass.includes(superClass)) n { n return;n }n path.traverse(classVisitor, { types: t, opts:state.opts })n}n

進入類的方法進行分析

對於類方法進行分析,我們首先會對一些固定的方式進行分析,大家還記得開頭的一些不良示範嗎?對的,對於這些,我們就直接進行分析處理了。

分析this.state的直接賦值

細心的朋友應該發現,constructor被排除了,因為在它裡面,是正常的賦值啦。

AssignmentExpression: function AssignmentExpression(path) { n if ([constructor].includes(method)) { n return;n }n path.traverse({n MemberExpression: function MemberExpression(path) { n var node = path.node; n if (node.property.name === state n && node.object.type === ThisExpression n && path.parentPath.parentKey === left) { n throw pathn .buildCodeFrameError(Do not assign value to `state` directly);n }n }n });n}n

分析無用的setState

明明可以在constructor裡面直接賦值,為什麼要用setState.

if ([constructor, componentWillMount].includes(method)) {n path.traverse({n MemberExpression: function MemberExpression(path) { n var node = path.node; n if (node.property.name === setState n && node.object.type === ThisExpression) { n throw pathn .buildCodeFrameError(Please use `this.state` in the constructor);n }n }n });n}n

錯誤演示

enter image description heren

分析render函數

我之前看到好多人在render裡面寫了好多原生元素,一看代碼,直接就暈了,完全看不懂了,對於這點,我們將要引出另外一個配置項,用於檢測最大的組件數目,保證代碼的清晰和組件的顆粒度,提高react的diff效率。

"maxRenderElements": 50n

代碼實現

enter image description here

自定義過濾

在不同企業,使用方式不同,對框架的理解也不同,有些時候,不能完全限制一些使用方式,需要自定化。我們將使用另一個配置屬性,該選項的值都是字元串,而且是正則,通過符合正則的語句,來過濾不好的語法。

"invalidStatements": [n "this.refs.w+.values*=s*S+"n ]n

相應代碼實現部分,通過對表達式的分析,對於符合正則的語句,進行拋錯。

ExpressionStatement: function ExpressionStatement(path) { n var invalidStatements = []; n var file = void 0; n var code = void 0; n try {n file = path.hub.file.code;n code = file.slice(path.node.start, path.node.end);n invalidStatements = state.opts.invalidStatements;n } n catch (e) { n console.log(e);n } n for (var i = 0; i < invalidStatements.length; i++) { n var reg = new RegExp(invalidStatements[i]); n if (reg.test(code)) { n throw pathn .buildCodeFrameError(Invalid Statement is checked by n + invalidStatements[i] + );n }n }n}n

編譯拋錯演示

enter image description here

運行分析

運行分析,是在編譯沒有出錯後,在運行我們的應用程序時發現的問題,比如有超過指定時間渲染,或者有不必要的重新渲染操作,這些操作,在手機上運行web app,還是可以節省很多資源的。接下來就分析其本質。

其實對於分析工具,react就有react-addons-perf這個工具,但是有一個問題是,難道我們每次都要去引用相關的組件,再把相應的代碼寫到react的指定方法裡面,測試好了之後,再去掉,完全不符合工業化和自動化的標準,所以本插件就是基於react-addons-perf這個工具,進行自動注入相應的組件和代碼,方便用戶去使用,省去一大堆麻煩,本插件還可以根據不同react版本,進行適配。

分析是否需要導入插件

首先去判斷,該類是否是我們想要的類,如果是,那就自動導入perf 類。

enter image description here

enter image description here

對類的非生命周期方法進行自動性能代碼注入

enter image description here

輸出監控信息

這裡完全是正常的js語法了,沒有用babel的語法,很簡單,一看就知道,但是這裡是有缺陷的,大家可以思考一下,缺陷是什麼?

enter image description here

運行演示:

超過運行配置時間的(maxExecuteLimit配置)。

enter image description here

有不必要渲染的。

enter image description here

配置

對於配置的話,其實我們有兩種方式。

babelrc裡面進行設置,這樣的話,完全會在開發,測試,線上等環境生效,其實對於這個插件,我們有時只要在開發上用就行。

可以在webpack的babel-loader裡面配置,這樣可以只在開發的webpack配置進行運行這個插件。

配置文件結構

"plugins": [n ["react-perf",{n "maxExecuteLimit": 5,n "maxRenderElements": 50,n "needAddPerfRule": {n "superClass": ["Component"]n },n "invalidStatements": [n "this.refs.w+.values*=s*S+"n ]n }n ]n ]n

插件git地址

地址:github.com/vicwang163/b

結束

其實對於這個插件,未來還有很多可以開發的地方,更多是要大家多多提意見,把東西做好,服務好大家。 題外話:大家對自己寫的react代碼自信的話,可以用該插件跑一下,偷偷告訴大家,作者自己用了該插件測試了一下自己曾經寫的react代碼,心裡陰影那麼大。


推薦閱讀:

setState的函數參數叫啥名好
React中組件通信的幾種方式
React 的數據載體:state、props、context
ReactEurope 2016 小記 - 下

TAG:React | 性能分析 | 前端开发工具 |