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