如何在前端編碼時(shí)實(shí)現(xiàn)人肉雙向編譯
如何在前端編碼時(shí)實(shí)現(xiàn)人肉雙向編譯
React+flux是目前最火的前端解決方案之一,但flux槽點(diǎn)頗多,例如store比較混亂,使用比較繁瑣等,于是出現(xiàn)了很多第三方的基于flux優(yōu)化的架構(gòu)。
有人統(tǒng)計(jì)了目前主流的flux實(shí)現(xiàn)方案,感興趣的可以看這里:Which Flux implementation should I use?
其中redux
是目前github
上star
最多的一個(gè)方案,該方案完全獨(dú)立于react
,意味著這套理念可以作為架構(gòu)層應(yīng)用于其他的組件化方案。同時(shí)官方也提供了react-redux
庫,幫助開發(fā)者直接使用react+redux
快速開發(fā)。
個(gè)人理解它的主要特性體現(xiàn)在以下幾點(diǎn):
-
強(qiáng)制使用一個(gè)全局的
store
,store
只提供了幾個(gè)簡(jiǎn)單的api(實(shí)際上應(yīng)該是4個(gè)),如subscribe
/dispatch
(訂閱、發(fā)布),getState
,replaceReducer
。 -
store
負(fù)責(zé)維護(hù)一個(gè)唯一的叫做state
樹的對(duì)象,其中state
存儲(chǔ)了應(yīng)用需要用到的所有數(shù)據(jù)。 -
store
和頂層組件使用connect
方法綁定,并賦給props
一個(gè)dispatch
方法,可以直接在組件內(nèi)部this.props.dispatch(action)
。 簡(jiǎn)單一點(diǎn)說,就是去掉了flux
中組件和store
的unbind/bind
環(huán)節(jié)。當(dāng)state
變化時(shí),自動(dòng)更新components
,不需要手動(dòng)操作。 -
提供了
applyMiddleware
方法用于異步的action
,并且提供了加入中間件的能力,例如打印日志追蹤應(yīng)用的所有狀態(tài)變化。 -
對(duì)全局的數(shù)據(jù)
state
的操作,由多個(gè)reducer
完成。每個(gè)reducer
都是一個(gè)純函數(shù),接收兩個(gè)參數(shù)state
和action
,返回處理后的state
。這點(diǎn)類似管道的操作。
接下來我們可以回答標(biāo)題的問題了,即:如何在前端編碼時(shí)實(shí)現(xiàn)人肉雙向編(zi)譯(can)。
其實(shí)就是使用coffee來編寫react+redux應(yīng)用。
我們來寫個(gè)簡(jiǎn)單的hello world玩玩。
view部分
這部分和redux/flux無關(guān),純粹react的實(shí)現(xiàn),使用jsx的話,render部分的代碼大概長(zhǎng)這樣:
- render:function(){
- return (
- <div>
- <div class="timer">定時(shí)器:{interval}</div>
- <div>{title}</div>
- <input ref="input"><button>click it.</button>
- </div>
- )
- }
那如何使用coffee寫這段代碼呢? 我們需要先將jsx編譯這類似這樣的js代碼,請(qǐng)注意是用大腦編譯:
- render:function(){
- return React.createElement('div',null,
- React.createElement('div',{className:'timer'},'定時(shí)器'+this.props.interval),
- React.createElement('div',null,this.props.title),
- React.createElement('input',{ref:'input'}),
- React.createElement('button',null,'click it.')
- );
- }
然后將js代碼逆向編譯為coffee。
這里我們可以用$
代替React.createElement
簡(jiǎn)化代碼(終于可以用jQuery的坑位了),得益于coffee
的語法,借助React.DOM
可以用一種更簡(jiǎn)單的方式實(shí)現(xiàn):
{div,input,button,span,h1,h2,h3} = React.DOM
這里就不單獨(dú)放render部分,直接看完整代碼:
- {Component,PropTypes} = React = require 'react'
- $ = React.createElement
- {div,input,button} = React.DOM
- class App extends Component
- clickHandle:->
- dom = this.refs.input.getDOMNode()
- this.props.actions.change(dom.value)
- dom.value = ''
- render:->
- {title,interval} = this.props
- div className:'timer',
- div null,'定時(shí)器:' + interval
- div null,title
- input ref:'input'
- button onClick:@clickHandle.bind(this),'click it.'
- App.propTypes =
- title: PropTypes.string
- actions: PropTypes.object
- interval: PropTypes.number
- module.exports = App
如果你能看到并看懂這段coffee,并在大腦里自動(dòng)編譯成js代碼再到j(luò)sx代碼,恭喜你。
連接store
這個(gè)環(huán)節(jié)的作用,主要是實(shí)現(xiàn)view層和store層的綁定,當(dāng)store數(shù)據(jù)變化時(shí),可自動(dòng)更新view。
這里需要使用redux提供的createStore方法創(chuàng)建一個(gè)store,該方法接受2個(gè)參數(shù),reducer和初始的state(應(yīng)用初始數(shù)據(jù))。
store.coffee的代碼如下:
- {createStore} = require 'redux'
- reducers = require './reducers' # reducer
- state = require './state' # 應(yīng)用初始數(shù)據(jù)
- module.exports = createStore reducers,state
然后我們?cè)趹?yīng)用的入口將store和App綁定,這里使用了redux官方提供的react-redux庫。
- {Provider,connect} = require 'react-redux'
- store = require './store'
- $ = React.createElement
- mapState = (state)->
- state
- rootComponent = $ Provider,store:store,->
- $ connect(mapState)(App)
- React.render rootComponent,document.body
可能有人會(huì)問,mapState和Provider是什么鬼?
mapState提供了一個(gè)類似選擇器的效果,當(dāng)一個(gè)應(yīng)用很龐大時(shí),可以選擇將state
的某一部分?jǐn)?shù)據(jù)連接到該組件。我們這里用不著,直接返回state
自身。
Provider是一個(gè)特殊處理過的react component,官方文檔是這樣描述的:
- This makes our store instance available to the components below.
- (Internally, this is done via React undocumented “context” feature,
- but it’s not exposed directly in the API so don’t worry about it.)
所以,放心的用就好了。
connect方法用于連接state和App,之后即可在App組件內(nèi)部使用this.props.dispatch()方法了。
添加action和reducer
***我們添加一個(gè)按鈕點(diǎn)擊的事件和定時(shí)器,用于觸發(fā)action,并編寫對(duì)應(yīng)的reducer處理數(shù)據(jù)。
在前面的App
內(nèi)部已經(jīng)添加了this.props.actions.change(dom.value)
,這里看下action.coffee的代碼:
- module.exports =
- change:(title)->
- type:'change'
- title: title
- timer:(interval)->
- type:'timer'
- interval:interval
再看reducer.coffee
- module.exports = (state,action)->
- switch action.type
- when 'change'
- Object.assign {},state,title:'hello ' + action.title
- when 'timer'
- Object.assign {},state,interval:action.interval
- else
- state
至此,代碼寫完了。
一些其他的東西
這里只介紹一個(gè)中間件的思想,其他的特性例如異步action,或者dispatch一個(gè)promise等原理基本類似:
- dispatch = store.dispatch
- store.dispatch = (action)->
- console.log action # 打印每一次action
- dispatch.apply store,arguments
由于時(shí)間關(guān)系,redux的一些特性和設(shè)計(jì)原理沒有展現(xiàn)出來,以后有時(shí)間再單獨(dú)講,完整的項(xiàng)目代碼,感興趣的同學(xué)可以看這里:請(qǐng)點(diǎn)我,或者直接找我聊。
項(xiàng)目用到了fis3作為構(gòu)建工具,使用fis3 release
即可在本地查看效果。