数据/ 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,让它工作,然后将其分解.这样你就不会立刻处理太多未知数.