数据/ duck.jsx
import { createSelector } from 'reselect';
import { createReduxApi } from './backendApi';
const getDataContext = state => state.default.dataContext;
const backendReduxApi = createBackendReduxApi(getDataContext);
// Action creators
export const makeRestApiRequest = endpointName => backendReduxApi .makeRequestActionCreator(endpointName);
export const resetRestApi = endpointName => backendReduxApi .makeResetActionCreator(endpointName);
// Reducers
export const dataReducer = backendReduxApi .createReducer();
// Selectors
const getRestApiState = endpointName => backendReduxApi .getEndpointState(endpointName);
export const getRestApiData = endpointName => createSelector([getRestApiState(endpointName)],apiState => apiState.data);
export const getRestApiMeta = endpointName => createSelector([getRestApiState(endpointName)],apiState => apiState.Meta);
export const getRestApiError = endpointName => createSelector([getRestApiState(endpointName)],apiState => apiState.error);
export const getRestApiStarted = endpointName => createSelector([getRestApiState(endpointName)],apiState => apiState.started);
export const getRestApiFinished = endpointName => createSelector([getRestApiState(endpointName)],apiState => apiState.finished);
backendApi.jsx文件如下所示:
数据/ backendApi.jsx
import ReduxRestApi from './rest/ReduxRestApi';
export const BackendApi = { // NOSONAR
LANGUAGE_FILE: 'languageFile',EMPLOYEE: 'employee',};
const backendReduxApiBuilder = ReduxRestApi.build()
/* /api */
/* /api/employee */
.withGet('/myproject/api/employee',BackendApi.EMPLOYEE)
/* /language*/
.withGet('/myproject/language/nb_NO.json',BackendApi.LANGUAGE_FILE)
export const createBackendReduxApi = restApiSelector => backendReduxApiBuilder
.withRestApiSelector(restApiSelector)
.create();
然后在data / rest文件夹中我有4个文件:ReduxRestApi,restConfig,RestDuck和restMethods.
数据/休息/ ReduxRestApi.jsx
import { combineReducers } from 'redux';
import { get,post,postAndOpenBlob } from './restMethods';
import RestDuck from './RestDuck';
class ReduxRestApi {
constructor(endpoints,getRestApiState) {
this.createReducer = this.createReducer.bind(this);
this.getEndpoint = this.getEndpoint.bind(this);
this.makeRequestActionCreator = this.makeRequestActionCreator.bind(this);
this.makeResetActionCreator = this.makeResetActionCreator.bind(this);
this.getEndpointState = this.getEndpointState.bind(this);
this.ducks = endpoints.map(({ name,path,restMethod }) => new RestDuck(name,restMethod,getRestApiState));
}
createReducer() {
const reducers = this.ducks
.map(duck => ({ [duck.name]: duck.reducer }))
.reduce((a,b) => ({ ...a,...b }),{});
return combineReducers(reducers);
}
getEndpoint(endpointName) {
return this.ducks.find(duck => duck.name === endpointName)
|| { actionCreators: {} };
}
makeRequestActionCreator(endpointName) {
return this.getEndpoint(endpointName).actionCreators.execRequest;
}
makeResetActionCreator(endpointName) {
return this.getEndpoint(endpointName).actionCreators.reset;
}
getEndpointState(endpointName) {
return this.getEndpoint(endpointName).stateSelector;
}
static build() {
class RestApiBuilder {
constructor() {
this.withGet = this.withGet.bind(this);
this.withPost = this.withPost.bind(this);
this.withPostAndOpenBlob = this.withPostAndOpenBlob.bind(this);
this.withRestApiSelector = this.withRestApiSelector.bind(this);
this.endpoints = [];
}
withGet(path,name) {
this.endpoints.push({ path,name,restMethod: get });
return this;
}
withPost(path,restMethod: post });
return this;
}
withPostAndOpenBlob(path,restMethod: postAndOpenBlob });
return this;
}
withRestApiSelector(restApiSelector) {
this.restApiSelector = restApiSelector;
return this;
}
create() {
return new ReduxRestApi(
this.endpoints,this.restApiSelector
);
}
}
return new RestApiBuilder();
}
}
export default ReduxRestApi;
restConfig.jsx
import axios from 'axios';
import { removeErrorMessage,showErrorMessage } from '../../app/duck';
import { is401Error,isHandledError } from '../../app/ErrorTypes';
const isDevelopment = process.env.NODE_ENV === 'development';
const configureRequestInterceptors = (store) => {
const onRequestAccepted = (config) => {
store.dispatch(removeErrorMessage());
return config;
};
const onRequestRejected = error => Promise.reject(error);
axios.interceptors.request.use(onRequestAccepted,onRequestRejected);
};
const configureResponseInterceptors = (store) => {
const onSuccessResponse = response => response;
const onErrorResponse = (error) => {
if (is401Error(error) && !isDevelopment) {
window.location.reload();
}
if (!isHandledError(error)) {
store.dispatch(showErrorMessage(error));
}
return Promise.reject(error);
};
axios.interceptors.response.use(onSuccessResponse,onErrorResponse);
};
const configureRestInterceptors = (store) => {
configureRequestInterceptors(store);
configureResponseInterceptors(store);
};
export default configureRestInterceptors;
数据/休息/ RestDuck.jsx
import { createSelector } from 'reselect';
import { get,getBlob,postAndOpenBlob,postBlob } from './restMethods';
/**
* getmethodName
* Helper function that maps given AJAX-method to a name
*
* Ex. getmethodName(getBlob) -> 'GET'
*/
const getmethodName = (restMethod) => {
switch (restMethod) {
case get:
case getBlob:
return 'GET';
case post:
case postBlob:
case postAndOpenBlob:
return 'POST';
default:
return '';
}
};
/**
* createRequestActionType
* Helper function to generate actionType for actions related to AJAX calls
*
* Ex: createRequestActionType('fetchEmployee','ERROR',get,'/myproject/api/employee') -> '@@REST/fetchEmployee GET /myproject/api/employeeERROR'
*/
const createRequestActionType = (name,qualifier,restMethod = '',path = '') => [`@@REST/${name}`,getmethodName(restMethod),qualifier]
.filter(s => s !== '')
.join(' ');
/**
* createRequestActionTypes
* Helper function to generate ActionTypes for a given AJAX method and resource.
*
* Ex. createRequestActionType(fetchEmployee,'/myproject/api/employee') -> {
* reset: '@@REST GET /myproject/api/employee RESET',* requestStarted: '@@REST GET /myproject/api/employee STARTED',* requestError: '@@REST GET /myproject/api/employee ERROR',* requestFinished: '@@REST GET /myproject/api/employee FINISHED',* }
*/
const createRequestActionTypes = (name,path) => ({
reset: createRequestActionType(name,'RESET'),requestStarted: createRequestActionType(name,'STARTED',path),requestError: createRequestActionType(name,requestFinished: createRequestActionType(name,'FINISHED',path)
});
/**
* createRequestThunk
* Helper function that generates a thunk that performs an AJAX call specified by 'restMethod' and 'restEndpoint'
*
* When the thunk is running,the action 'requestStarted' will be dispatched immediately.
* Then,it performs the AJAX call that returns a promise.
* If the call goes well,the action 'requestFinished' will be dispatched with data from the call.
* If the call fails,the action 'requestError' is dispatched with the contents of the error.
*/
const createRequestThunk = (restMethod,restEndpoint,requestStarted,requestFinished,requestError) => (
(params,options = {}) => (dispatch) => {
dispatch(requestStarted(params,options));
return restMethod(restEndpoint,params)
.catch((error) => {
const data = error.response && error.response.data ? error.response.data : error;
dispatch(requestError(data));
return Promise.reject(error);
})
.then((response) => {
dispatch(requestFinished(response.data));
return response;
});
}
);
/**
* createRequestActionCreators
* Helper function that creates action creators 'requestStarted','requestFinished' and 'requestError',* @see createRequestThunkCreator
*/
const createRequestActionCreators = (restMethod,actionTypes) => {
const reset = () => ({ type: actionTypes.reset });
const requestStarted = (params,options = {}) => ({ type: actionTypes.requestStarted,payload: { params,timestamp: Date.Now() },Meta: { options } });
const requestFinished = data => ({ type: actionTypes.requestFinished,payload: data });
const requestError = error => ({ type: actionTypes.requestError,payload: error });
const execRequest = createRequestThunk(restMethod,requestError);
return {
reset,requestError,execRequest
};
};
/**
* createRequestReducer
*
* Helper function that creates a reducer for an AJAX call.
* Reducer alters the state of the actions with the name defined by
* actionTypes.requestStarted
* actionTypes.requestFinished
* actionTypes.requestError
*/
const createRequestReducer = (restMethod,resourceName,actionTypes) => {
const initialState = {
data: undefined,Meta: undefined,error: undefined,started: false,finished: false
};
return (state = initialState,action = {}) => {
switch (action.type) {
case actionTypes.requestStarted:
return {
...initialState,data: action.Meta.options.keepData ? state.data : initialState.data,started: true,Meta: action.payload
};
case actionTypes.requestFinished:
return {
...state,finished: true,data: action.payload
};
case actionTypes.requestError:
return {
...state,error: action.payload
};
case actionTypes.reset:
return {
...initialState
};
default:
return state;
}
};
};
/**
* RestDuck
* Class that offers action types,action creators,reducers and selectors for an AJAX call.
* @see createRequestActionTypes
* @see createRequestActionCreators
* @see createRequestReducer
*
* Ex.
* const getEmployeeDuck = new RestDuck(execGetRequest,'employee',GET_EMPLOYEE_SERVER_URL);
* // Action creators
* export const fetchEmployee = getEmployeeDuck.actionCreators.execRequest;
* // Reducer
* export const dataReducer = combineReducers(
* ...,* getEmployeeDuck.reducer,* }
* // Selectors
* export const getDataContext = state => state.default.dataContext;
* export const getEmployeeData = getEmployeeDuck.selectors.getRequestData(getDataContext);
* export const getEmployeeStarted = getEmployeeDuck.selectors.getRequestStarted(getDataContext);
* ...
*/
class RestDuck {
constructor(name,getApiContext) {
this.restMethod = restMethod;
this.name = name;
this.path = path;
this.getApiContext = getApiContext;
this.$$duck = {}; // for class internal use
}
get actionTypes() {
if (!this.$$duck.actionTypes) {
this.$$duck.actionTypes = createRequestActionTypes(this.name,this.restMethod,this.path);
}
return this.$$duck.actionTypes;
}
get actionCreators() {
if (!this.$$duck.actionCreators) {
this.$$duck.actionCreators = createRequestActionCreators(this.restMethod,this.path,this.actionTypes);
}
return this.$$duck.actionCreators;
}
get reducer() {
if (!this.$$duck.reducer) {
this.$$duck.reducer = createRequestReducer(this.restMethod,this.name,this.actionTypes);
}
return this.$$duck.reducer;
}
get stateSelector() {
return createSelector([this.getApiContext],restApiContext => restApiContext[this.name]);
}
}
export default RestDuck;
数据/休息/ restMethods.jsx
import axios,{ CancelToken } from 'axios';
const openPreview = (data) => {
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(data);
} else {
window.open(URL.createObjectURL(data));
}
};
const cancellable = (config) => {
let cancel;
const request = axios({
...config,cancelToken: new CancelToken((c) => { cancel = c; })
});
request.cancel = cancel;
return request.catch(error => (axios.isCancel(error) ? Promise.reject(new Error(null)) : Promise.reject(error)));
};
const defaultHeaders = {
'Cache-Control': 'no-cache',Pragma: 'no-cache',Expires: 0
};
const defaultPostHeaders = {
'Content-Type': 'application/json'
};
export const get = (url,params,responseType = 'json') => cancellable({
url,responseType,method: 'get',headers: {
...defaultHeaders
}
});
export const post = (url,data,data: JSON.stringify(data),method: 'post',headers: {
...defaultHeaders,...defaultPostHeaders
},cache: false
});
export const getBlob = (url,params) => get(url,'blob');
export const postBlob = (url,data) => post(url,'blob');
export const postAndOpenBlob = (url,data) => postBlob(url,data)
.then((response) => {
openPreview(response.data);
return {
...response,data: 'blob opened as preview' // Don't waste memory by storing blob in state
};
});
我不知道在这个结构中如何放置以及如何模拟api调用.我正在考虑制作一个类似于这个one的模拟api,在那里我会模仿ajax调用并将它们存储在redux中,但是不知道如何在这种设置中执行此操作?
我尝试使用mockApi文件夹而不是使用restMethods来使用我将编写将解析mockData的promises的文件.这是我的尝试:
mockRestMethods
const employee = {
name: 'Joe Doe'
}
const data = {
employee
};
export const get = item => new Promise((resolve) => {
setTimeout(() => {
resolve({ data: data[item] });
},1000);
});
但是,如果我在RestDuck文件中的createRequestThunk函数内检查返回的内容作为response.data,我得到数据:undefined there.为什么,我做错了什么?
export const get =(url,responseType =’json’)=>撤销({
with export const get = item =>新的Promise((resolve)=> {具有不同的API.
无论如何,您是否尝试在mock get函数中记录item的值.我猜它不是“员工”,这是数据中唯一的属性.
Yes,that was my goal,to replace the call that was pointing to the backend API,with the call where I would return the mock data. I have tried to log the value of the item,but I get undefined
好的,所以那里有很多抽象.我建议首先使用返回promise的版本替换get in data / rest / restMethods.jsx,让它工作,然后将其分解.这样你就不会立刻处理太多未知数.