例子:lesx-example
webpack loader: lesx-loader
一些背景
現在前端框架已經呈現出React、Angular、Vue三足鼎立的局勢,對于三者的對比以及技術選型的思考與爭論也被討論了非常多,比如知乎上的這個問題:react.js,angular.js,vue.js學習哪個好?,對于這個問題我們不再做過多贅述。但不管怎么樣,現在github上star數最多、npm上安裝量最大的還是React
,阿里巴巴很多團隊的技術棧也是基于React的。此篇文章也是基于React
的開發范式來進行討論的。
JSX的模板范式沒有選擇HTML模板,而是完全基于JS的,同時提供了一種JSX的語法糖,方便用戶的開發。這樣做是有幾種考慮的,首先React是跨平臺跨終端的,不僅可以在Web browser中運行,還可以基于RN在移動端APP、服務端基于SSR來運行,基于虛擬DOM的實現讓他可以輕松地做到以上幾點,另外,完全基于JS的開發可以不用掌握類似Vue/angular的指令式的語法,而是更多的偏向于使用純js的語法開發范式,一次學習終身受益,而不用每次在開發的過程中還要去查看API文檔。
但是,React的這種開發模式也帶來了一個額外的問題,就是jQuery時代尊崇的UI與邏輯分離
的最佳實踐在JSX時代又有了極大的后退
。于是我們一直在思考,能不能有一種模式,既能享受像Vue那樣UI、展示(樣式)與邏輯
分離,方便維護與可擴展,又能享受React JSX的語法帶來的便利呢?
Lesx的誕生
基于上面的思考,于是有了Lesx這個構建式的框架
。
構建式的框架
并不是我們的首創,但是這個概念不知道是不是我們第一次正式提出來。業界已經有的AOT(Ahead Of Time)、非侵入式的框架比較知名的是svelte,他的開發范式跟Lesx比較相似,但是他并不是基于React或者哪個框架的,而是自己研發了一套底層組件機制,對于模板代碼的解析也是基于自己實現的一套AST解析實現,語法類似于Handlebars。基于React的開發范式跟Lesx比較相似的還是react-templates,他稱自己是:Lightweight templates for React
。他只是把React Class的render部分抽了出來,DSL會被編譯成React.createElement
,然后生成一個函數作為React Class的render方法。同時,react-templates里還增添了很多類似vue的指令的功能,比如:rt-if
,rt-repeat
等,這樣的框架的問題就是問題解決的并不是很徹底,抽出render部分的同時,我們還是需要對React創建部分需要大量的代碼書寫;同時,對于JSX語法擴展指令的模式增添了開發者的學習成本,后面開發中也需要不斷地去查看文檔如何使用這些指令,這是我們極不推崇的。
在這樣的背景下,我花了兩天時間,早起晚睡、憋屎憋尿的完成了基于React做到UI、展示(樣式)與邏輯
分離的構建式開發框架:Lesx
的初版。
基于Lesx的開發模式
Lesx作為webpack的loader存在,使用類似Vue的單文件的開發范式,方便開發者的代碼組織與開發:
index.lesx:
<style>a {color: red;}
</style><template><div><a onClick={this.func}>點我</a>{console.log(this.props)}<If condition={ this.props.valid }><div>{this.state.name}</div></If><Button type="primary" onClick={() => {alert('I am an antd button!');$setState({name: 'new name'});}}>antd button</Button><My /></div>
</template><script>module.exports = {props: {valid: true},state: {name: 'xiangzhong.wxz'},func({setState,}) {alert('I am a function!');setState({name: 'new name'});}};
</script>
很明顯的,他有幾個特點:
UI、樣式與邏輯分離
lesx文件有style/template/script三個標簽,內部分別存放他們對應的內容代碼。
style
部分我們默認使用跟css完全兼容同時有更多便利性語法的Sass
語言,后面馬上也會支持Less
語法。
tenplate
部分則完全是React的jsx語法,同時由以下幾個擴展:
- 我們基于babel插件
jsx-control-statements
提供了便利性的控制流標簽,比如:If
,For
等等,語法非常簡單,一次學習終生高效!當然,有的同學可能會不認可這種標簽擴展控制流的模式,此時你也可以繼續使用你熟悉的三元運算符、數組map等方式來實現邏輯與展示控制,但是我們相信,標簽控制符是更清晰、更容易維護的開發模式; -
你可以在DSL里面使用一些輔助性全局變量:
-
$setState
:this.setState
的簡便寫法,通過改變state值來觸發UI渲染; -
$getRef
: React通過組件ref屬性獲取組件的簡便寫法; -
$getProps
: 獲取React屬性的簡便性方法,相當于:this.xxx;
-
后面我們還會做一些其他的更高級的便利性擴展,比如:接入axios的異步操作,React的forceUpdate便利性機制等等。
script
部分是用于書寫前端邏輯處理的地方,你可以使用ES6的語法,做各種的數據處理,只需要最后把一個對象交給module.exports
變量即可,這個對象可以包含如下內容:
-
state
: React Component的state初始值,可以是對象也可以是函數; -
props
: React props初始值,可以是對象也可以是函數; -
React組件的生命周期鉤子函數
: 比如:componentDidMount
等,會被自動掛在到最終生成的React Component Class里面去; -
其他任意的屬性或方法
: 均會被掛在到React Component實例(this)上去,而且,對于方法部分會被自動綁定到this作用域(this.xxx.bind(this)) 。
對于異步處理部分,默認可以直接調用this.axios.xxx
的方法來實現,并支持ES7:async/await
語法:
module.exports = {async getData(reqArg = {}) {const res = await this.axios.post('url/post', reqArg);return res;}
};
同時,支持異步請求庫可配置,可以在loader的配置里配置自己的異步請求庫,此時會替換掉默認的axios。但這一塊功能暫時還沒有加入,承諾在接下來的一周之內會加上去。目前可以通過組件props傳遞的方式來使用異步,比如:
import App from './index.lesx';
import axios from 'axios';console.log('App:', App);render(<Appaxios={axios}components={{My,}}
/>, document.querySelector('#root'));
然后在lesx文件的script里面就可以這樣用:
module.exports = {props: {valid: true},state: {id: 1001,},async getData(reqArg = {}) {const res = await this.props.axios.post('url/post', reqArg);return res;},clickHandler({setState,}) {const {id,} = this.state;const userData = this.getData({id,});setState({name: userData.name,});}
};
開發的極大便利:
UI庫是我們在開發中重度依賴的部分,特別是對于像React這種完全組件化的開發框架來說,有個好用的UI框架簡直是如虎添翼,會讓我們的開發效率得到極大地提升!所以,我們的開發框架默認集成了國內最優秀的React UI庫:antd,當然了,你也可以通過loader的配置來更改UI庫,比如可以使用material-ui等。
在配置了UI庫之后,無需做任何工作就可以直接在template
標簽里面使用該UI庫的任意組件了,比如使用Button組件:
<script><Button type="primary" onClick={() => {alert('I am an antd button!');$setState({name: 'new name'});}}>antd button</Button>
</script>
Lesx不僅會自動幫你打包你使用到的組件,同時,還會自動幫你把組件的樣式引入;另外,基于babel的插件:babel-plugin-import
,我們做到了按需打包,只會把你用到的組件給打包進來,保證打包后的文件的最小體積。
開發者不需要書寫React的組件生成代碼
因為我們把React Component生成的過程全部放在了AOT里實現,所以開發者無需寫React組件生成、UI庫組件引入
的操作,其實,開發者甚至不需要知道React的存在,也甚至更不需要學習React,唯一需要做的就是在渲染js文件中做一些組件引入以及渲染執行的操作,但是就這一塊的成本其實是極低的。
目前前端的資源是極度缺乏的,整個互聯網都缺前端
,所以,我們在考慮如何釋放前端人力這個方案的時候,我們是否可以考慮如何降低前端的上手成本,讓后端同學可以上手前端開發,做到網后端開發賦能呢?其實Lesx的開發范式一開始就是為這個方向考慮的,在滿足降低前端開發成本、降低前端開發復雜度、提高代碼可維護性的同時,也可以很方便的提供給后端,讓后端同學可以輕松上手前端開發,從而達到合作共贏的狀態。對于前端人手緊缺的公司可以考慮這個方案的落地,也許會起到意想不到的效果。
同時,為了可擴展性,我們做了一些額外的處理。除了可以給Lesx DSL轉成的Component傳遞屬性然后可以在Lesx文件使用之外,當我們確實需要第三方或者自己之前基于React原生模式開發的組件需要拿過來直接使用的時候,我們提供了components
屬性,將任意的第三方組件放在conponents屬性對象中,既可以直接在DSL中使用,如下:
import React, { Component } from 'react';
import { render } from 'react-dom';
import My from './My';
import App from './index.lesx';console.log('App:', App);render(<Appcomponents={{My,}}
/>, document.querySelector('#root'));
在上面我們引入了自己開發的My
組件,并放在了Lesx DSL轉成的App組件的components屬性里,于是可以在lesx文件中像下面這樣使用:
<style>{ /** style代碼 */ }
</style><template><div><a onClick={this.func}>點我</a><My /></div>
</template><script>{ /** 邏輯代碼 */ }
</script>
其實,基于這種開發范式針對不同的場景可以有不同的代碼組織模式。如果你的界面不是很復雜,或者是比較典型的中后臺應用場景(增刪改查這種),你可以完全基于一個.lesx
文件開發完你所有的頁面邏輯,更多的則是依賴于第三方的UI庫來為你的開發提供便利,說白了就是更多的依賴于組件搭積木式
的開發范式,這個時候template
就是開發的重點所在,而script
跟style
只是起到了添磚加瓦的便利性的開發,這個時候Lesx的職責就是頁面級別
的代碼組織方式;如果是比較復雜的應用,比如SPA應用,這時我們可以基于Lesx來開發自己的一個個的React組件,然后加入vanex、dva等數據流管理框架來方便對大量數據的操作,最后通過react-router
等router組件進行統一組織,然后進行渲染。這個時候Lesx的職責就不一樣了,變成了組件級別
的代碼組織。
怎么樣,有沒有那么一點點的打動你的心呢?^_^ 如果有的話,不妨去體驗下Lesx,相信會帶給你不一樣的開發體驗。
例子:lesx-example
webpack loader: lesx-loader