我們在寫React應用時,通常用到redux做狀態管理,然後會用到一些中介軟體來支援非同步action,比如redux-thunk.通常我們的程式碼類似下面這樣的:
//ActionType R.js
export ActionType{
TYPE1:'type1',
TYPE2:'type2',
TYPE3:'type3',
// others
}
//reducer reducer.js
import {ActionType as AT} from 'R'
export default function(state={},action){
switch(action.type){
case AT.TYPE1:
return {
...state,
...action.data,
// or other
}
case AT.TYPE2:
//...
default:
return state
}
}
//Action Action.js
import {ActionType as AT} from 'R'
const act = {
action1:data=>dispatch=>{
// some action such as ajax
dispatch({
type:AT.TYPE1,
data:{
// data
}
})
}
action2:()=>(dispatch,getState)=>{
// call another action
dispatch(act.action3())
}
action3:()=>()=>{
// even you never use the dispath or getState,you still have to code like this.
}
}
export default act
// Component
@connect(
state=>state.reducer1,
dispatch=>bindActionCreators(Action,dispatch)
)
export default class View extends React.Component{
doAction1(){
this.props.action1({})
}
render(){
return(
<div>
anything else
</div>
)
}
}
複製程式碼
功能很簡單,卻需要些這麼多actionType, reducer裡面還有那麼多case分支。而最煩的是action,直覺上我們是希望能夠寫普通的function,而不用強行寫上dispatch和getState, 一個action呼叫另一個action竟然也不能直接呼叫。雖然我們知道是需要這樣做,需要用actionType來區分action,reducer裡面也需要根據type來做相應的更新,action中也需要dispatch來發出action。
但如果我們就是不想寫的這麼麻煩,能有什麼辦法嗎?
答案之一是用mobx之類的工具來管理狀態。當然,本文要介紹的是在redux中的方法,ev-redux. 先來看看使用後的效果:
首先安裝:npm i --save ev-redux
然後你的程式碼差不多是這樣:
// init evStore in you app.js when create store.
import {evStore} from 'ev-redux'
const store = createStore(...)
evStore.init(store)
// redux/reducer.js
import {ActionType as TYPE} from './index.js'
const initState={}
export default function(state=initState,action){
if(action.type === TYPE){
return {
...state,
...action.data
}
}
return state
}
// redux/Action.js
import {evRedux} from 'ev-redux'
@evRedux
export default class Action{
constructor(actionType){
this.actionType = actionType
}
action1(x){
this.update({
x
})
}
action2(){
this.dispatch({
type:this.actionType,
data:{y:this.getState().reducer2.y+1}
})
// to call another action, just call no need dispath!
this.action3({z:3})
// or just update
// this.update({y:this.getState().reducer2.y+1})
}
action3(z){
setTimeout(()=>{
this.update({z})
},1000)
}
}
// redux/index.js
import Action from './Action'
import reducer from './reducer'
import {connect as conn} from 'react-redux'
const connect = view => conn(state=>state.reducer2)(view)
const ActionType = Symbol()
const action = new Action(ActionType)
export {reducer,ActionType,action,connect}
// Component
import action from './action'
@connect
export default class View extends React.Component{
doAction1(){
// just call an action as we call a function.
action.action1()
}
render(){
return(
<div>
anything else
</div>
)
}
}
複製程式碼
不要只看著程式碼並沒有減少。仔細看看 reducer和action部分,這樣的action,這樣的reducer你還不願意用嘛?
- 不需要中介軟體來支援非同步任務。
- 不需要去寫那麼多actionTypes,一個reducer對應一個action就行,使用symbol保證不重複。
- reducer中不需要那麼多case分支,只需要合併一下action傳過來的state和原來的state就可以。
- 重點是action,不需要手動在每個方法上加上dispatch和getState.你只需要在需要更新store時,構造好需要更新的那部分state,,然後呼叫update就行,type都不需要帶。update哪兒來的?你直接用就行,後面會說。如果你想要用dispath和getState,直接從上下文取就行,this.dispatch,this.getState隨便用。呼叫其他action,直接用this呼叫就ok,就像呼叫普通方法那樣呼叫。
ok了,用法介紹完了。具體的原理簡單說明一下:
首先我們要觸發reducer來更新store,就要用到dispatch來分發action。我們的update方法裡面就是這麼做的。之所以把構造要更新的state方法action, 而不是在reducer中通過不同的type來區分,是因為我覺得對於每個action,裡需要更新那部分state,在action裡面你是最清楚的。如果你在actino裡面只是dispatch一個action,然後去reducer中構造state結構並更新,你不僅要寫很多個多餘的actionType,而且可能還要回頭看這個type是哪個action觸發的,還要回想這個action裡面傳過來的data是什麼樣的,明顯比在action中構造要複雜。
至於為什麼action中我們明明沒寫update,卻可以直接用,其實在ev-redux中,我們對action做了代理。在初始或evStore時,我們能夠獲取到dispatch和getState,然後在action的get方法中,我們用反射來set update、dispatch、getState. 這樣在我們的action中就能夠直接在上下文中呼叫到它們了。
原理就是用到js裡面的代理和反射,程式碼也很簡單,這裡就不展開說明了,直接放上github地址:github.com/evolify/EvR…