react使用redux時reducer的重用和去switch判斷

通常在redux中寫reducer時,根據action傳遞過來的type,進行判斷,數據處理也是在reducer中。當然不用if 就用switch,大多數還用的switch

export default (state = initState, action) => {
    switch(action.type) {
        case LOGIN:
            return Object.assign({}, state, {
                type: LOGIN,
                data: action.params
            })
        case LOGIN_SUCCESS:
            return Object.assign({}, state, {
                type: LOGIN_SUCCESS,
                data: action.data
            })
        case LOGIN_ERROR:
            return Object.assign({}, state, {
                type: LOGIN_ERROR,
                data: action.data
            })
        default:
            return state
    }
} 

標準的寫法,判斷多了,寫下去感覺也挺麻煩的,能脫離switch就好了,只是把

return Object.assign({}, state, {
     type: LOGIN_ERROR,
     data: action.data
 })

這一段集中在一起, 如這樣

const reducer = {
    [REQUEST]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_SUCCESS]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_SUCCESS,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_ERROR]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_ERROR,
            data: action.data,
            namespace: action.namespace
        })
    }
}

用json格式,就需要根據不同的action,取不同的值,那麼再創建一個createReducer.js

export default (initState, reducerTarget) => 
    (state = initState, action) =>  (reducerTarget[action.type]) && reducerTarget[action.type](state, action) || state

這樣上面的reducer就通過createReducer高階函數封裝一遍

const reducer = {
    [REQUEST]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_SUCCESS]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_SUCCESS,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_ERROR]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_ERROR,
            data: action.data,
            namespace: action.namespace
        })
    }
}

export default () => {
    return (state = initState, action) => {
        return createReducer(state, reducer)(state, action);
    }
};

如此,就不用寫switch語句了,在combineReducers中

const createReducer = (injectedReducers) => {
    return combineReducers({
        reducerState: reducer(),
        ...injectedReducers
    })
}

到這裏成功擺脫了一個跟一個的判斷,但還是有個問題,比如一些簡單的請求,都同樣的只取三個狀態,請求,請求成功,請求錯誤。那麼這個reducer應該可以重用的,不然我們就需對不同的請求寫不同的reducer感覺很麻煩。

還是將就上面的reducer,一共有三個狀態,當然對於其他請求可能還有不同的請求,對reducer能重用還能加入對應不同的,纔是目的, 如對象合併似的

let reducerNew = {...reducer, ...selfReducer}

比如現在我們有兩個請求一個請求用戶數據,一個請求用戶權限,共用同一個reducer

const createReducer = (injectedReducers) => {
    return combineReducers({
        userState: composeReducers(userReducers(), requestReducer()),
        permissionsState: composeReducers(permissionsReducers(), requestReducer()),
        ...injectedReducers
    })
}

這樣就比較符合想的結果composeReducers函數

const composeReducers = (...reducers) => {
    return (state, action) => {
        if (reducers.length === 0) {
            return state;
        }
        let last = reducers[reducers.length - 1],
            rest = reducers.slice(0, -1);
        
        return rest.reduceRight((enhanced, reducer) => reducer(enhanced, action),
            last(state, action)
        );
    };
}

userReducers 和 permissionsReducers

const permissionsState = {
    permissions: []
}

const userInfo = {
    birthday: ''
}


const permissionsReducer = {

}

const userInfoReducer = {

}


export const permissionsReducers = () => {
    return (state = permissionsState, action) => {
        return createReducer(state, permissionsReducer)(state, action);
    }
};

export const userReducers = (namespace) => {
    return (state = userInfo, action) => {
        return createReducer(state, userInfoReducer)(state, action);
    }
};

到此我們來請求一下但問題是需要調用this.props.action,也就是this.props.request 他們用的同一個action,這樣返回的type就都是一樣的
額,當然action 爲了簡單的寫已經被我寫成這樣,對於沒沒有太多數據處理的 action,這樣寫也能省一點代碼

export const requestStatus = {
    REQUEST: 'REQUEST',
    REQUEST_SUCCESS: 'REQUEST_SUCCESS',
    REQUEST_ERROR: 'REQUEST_ERROR'
}
// actionsCreater
export default (actionTypes) => {
	let actionKeys = Object.keys(actionTypes),
        actions = {};
	for (let i = 0, item; item = actionKeys[i++];) {
        let funName = actionName(item.toLowerCase());
		actions[funName] = (data) => {
			return ({
				type: item,
				...data
			})
		}
	}
	return actions;
}
// 返回actions
export default actionsCreater({...requestStatus, ...othersStatus})

那麼就需要對不同請求進行區分了,redux官網有介紹用prefix,還有就是給reducer取個別名,在this.props.request裏進行傳遞參數,比如這樣

this.props.request({namespace: 'user', data: {access_token: access_token}})
this.props.request({namespace: 'permissions', data: {access_token: access_token}})

如果不區分,當然reducer裏也同樣存在兩數據,但是後面一個的數據會修改前面一個的, 所以createReducer修改一下

const createReducer = (injectedReducers) => {
    return combineReducers({
        userState: composeReducers(userReducers(), requestReducer('user')),
        permissionsState: composeReducers(permissionsReducers(), requestReducer('permissions')),
        ...injectedReducers
    })
}

同樣修改createReducer

export default (initState, reducerTarget, reducerNamespace) => {
    return (state = initState, action) => {
        let { namespace, type } = action;
        if (state === undefined || reducerNamespace !== namespace) {
            return state;
        }
        return (reducerTarget[type]) && reducerTarget[type](state, action) || state
    }
}

那麼requestReducers也做同樣修改

import createReducer from '../createReducer';
const { REQUEST, REQUEST_SUCCESS, REQUEST_ERROR } = requestStatus;

const initState = {
    type: REQUEST,
    data: {}
}
const reducer = {
    [REQUEST]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_SUCCESS]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_SUCCESS,
            data: action.data,
            namespace: action.namespace
        })
    },
    [REQUEST_ERROR]: (state, action) => {
        return Object.assign({}, state, {
            type: REQUEST_ERROR,
            data: action.data,
            namespace: action.namespace
        })
    }
}

export default (namespace) => {
    return (state = initState, action) => {
        return createReducer(state, reducer, namespace)(state, action);
    }
};

是不是完了,沒有這樣寫但是在saga裏還是不能給值,因爲多了一個 namespase

import { effects } from 'redux-saga';
import { requestStatus } from '../../actions/typeCom';
const {call, put} = effects;
const { REQUEST_SUCCESS, REQUEST_ERROR } = requestStatus;

export function* requestSaga(data) {
    try {
    	// userInfoRequest 請求用戶信息 
        let res = yield call(userInfoRequest, data.data);
        yield put({type: REQUEST_SUCCESS, data: res.data, namespace: data.namespace});
        return res;
    } catch (error) {
        yield put({type: REQUEST_ERROR, namespace: data.namespace});
    }
}

那麼注意一下userInfoRequest,這樣像也不對,我需要用同一個saga呢,在複製一個或者在寫一個方法把這個requestSaga包裝一次,每次需要都包裝感覺也不是個事
最好把userInfoRequest當參數傳遞過去,目前這麼想的就在調用請求時

this.props.request(userInfoRequest, {namespace: 'permissions', data: {access_token: access_token}})

但是這樣傳遞給action時,action中return 返回數據是方法是不被saga接收的只能和data一起返回那修改一下actionsCreater

const actionName = str => {
    return str.replace(/_(\w)/g, function(all, letter){
        return letter.toUpperCase();
    })
}

export default (actionTypes) => {
	let actionKeys = Object.keys(actionTypes),
        actions = {};
	for (let i = 0, item; item = actionKeys[i++];) {
        let funName = actionName(item.toLowerCase());
		actions[funName] = (obj, data) => {
			if (typeof obj === 'function') {
				return ({
					fn: obj,
					type: item,
					...data
				})
			} else {
				return ({
					type: item,
					...obj
				})
			}
		}
	}
	return actions;
}

這樣調用request時

this.props.request(adminUserInfo, {namespace: 'user', data: {access_token: access_token}})
this.props.request(operationPermissions, {namespace: 'permissions', data: {access_token: access_token}})

這樣多個請求就可以同用部分類似的,報錯action, reducer,saga

請求一下,看看localStorage裏面的數據,利用PersistGate存下來的
在這裏插入圖片描述

大概如上,有待優化···

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章