react redux-thunk项目中的模拟api返回未定义
Posted
技术标签:
【中文标题】react redux-thunk项目中的模拟api返回未定义【英文标题】:Mock api in react redux-thunk project returning undefined 【发布时间】:2019-02-05 03:10:14 【问题描述】:我是 redux 世界的新手,还没有一个以ducks 方式构建的项目。我试图理解它并用它来制作一个模拟 api,因为我还没有准备好后端。我正在使用我试图弄清楚的遗留代码。有一个名为 data 的文件夹,其中有一个 duck
和一个 backendApi
文件。 Duck 文件长这样。
data/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
。
data/rest/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, path, 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, name)
this.endpoints.push( path, name, restMethod: post );
return this;
withPostAndOpenBlob(path, name)
this.endpoints.push( path, name, 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;
data/rest/RestDuck.jsx
import createSelector from 'reselect';
import get, getBlob, post, 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), path, qualifier]
.filter(s => s !== '')
.join(' ');
/**
* createRequestActionTypes
* Helper function to generate ActionTypes for a given AJAX method and resource.
*
* Ex. createRequestActionType(fetchEmployee, get, '/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, restMethod, path) => (
reset: createRequestActionType(name, 'RESET'),
requestStarted: createRequestActionType(name, 'STARTED', restMethod, path),
requestError: createRequestActionType(name, 'ERROR', restMethod, path),
requestFinished: createRequestActionType(name, 'FINISHED', restMethod, 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, restEndpoint, 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, restEndpoint, requestStarted, requestFinished, requestError);
return
reset, requestStarted, requestFinished, 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,
started: false,
finished: true,
data: action.payload
;
case actionTypes.requestError:
return
...state,
started: false,
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, path, restMethod, 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;
data/rest/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,
params,
responseType,
method: 'get',
headers:
...defaultHeaders
);
export const post = (url, data, responseType = 'json') => cancellable(
url,
responseType,
data: JSON.stringify(data),
method: 'post',
headers:
...defaultHeaders,
...defaultPostHeaders
,
cache: false
);
export const getBlob = (url, params) => get(url, params, 'blob');
export const postBlob = (url, data) => post(url, data, '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 的 promise 的文件。这是我的尝试:
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
返回的内容,我会在那里得到 data: undefined
。为什么会这样,我做错了什么?
【问题讨论】:
你能在codesandbox.io/s/new上创建一个***.com/help/mcve吗? 【参考方案1】:我可能有这个错误,但似乎你正在替换
export const get = (url, params, responseType = 'json') => cancellable(
export const get = item => new Promise((resolve) =>
具有不同的 API。
无论如何,您是否尝试过在模拟 get 函数中记录 item
的值。我猜这不是“员工”,这是data
中唯一的属性。
是的,这是我的目标,将指向后端 API 的调用替换为返回模拟数据的调用。我试图记录项目的值,但我得到未定义
好的,所以那里有很多抽象。我建议首先将data/rest/restMethods.jsx
中的get
直接替换为返回承诺的版本,让它工作,然后将其分解。这样你就不会一次处理太多的未知数。
【讨论】:
是的,这是我的目标,将指向后端 API 的调用替换为返回模拟数据的调用。我试图记录item
的值,但我得到undefined
。
我试过直接替换方法,但是我遇到了同样的问题,我无法让promise方法工作。【参考方案2】:
我使用 redux-saga 做过类似的事情。经过调试,我发现必须有data
属性作为根键。你应该这样做:
const employee =
data: // root key of employee
items: [
name: 'Bhojendra' ,
name: 'Rauniyar'
]
// and no need to use setTimeout, we're just resolving some constant data
export const getItems = item => new Promise(resolve => resolve(employee))
现在,我希望您知道为什么 data 是 undefined
与您的代码。
还是不清楚?
响应查找 data
属性。就是这样。
【讨论】:
以上是关于react redux-thunk项目中的模拟api返回未定义的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 redux-thunk 调度操作让 connected-react-router 重定向?
在 React-redux 和 Redux-Thunk 中从 API 获取数据的问题
如何使用 redux-thunk `bindActionCreators`