【发布时间】:2018-06-29 06:17:45
【问题描述】:
我正在使用 Redux、Saga 和 Typescript 开发一个 React 应用程序。
应用程序结构的设置使得每个主要顶级容器组件在 Store 目录中都有一个相应的文件,用于定义它的动作创建者、reducers 和 sagas。
当应用启动时,所有的 reducer 都从 Store 文件中组合起来,而 Sagas 组合成一个通用的 rootSaga 函数。
除了现在我尝试使用选择器将一些状态属性加载到我的 Sagas 中之外,一切都很好。我没有收到任何错误,但我的选择器函数没有返回我的状态值。
如果我尝试在我的 Store 文件中使用 getState() 函数,我会收到 Typescript 错误“找不到名称 getState”。
很明显,我没有在我的 Store 文件中包含正确的库,或者我没有通过正确的命名空间调用 state 函数,但我不知道出了什么问题。
我从 Thunk 中间件切换到使用 Saga。当 Thunk 连接到应用程序时,我可以在 Store 文件中使用 getState。
这是包含我的 action creators、reducers 和 sagas 的 Store 文件。
我的选择器函数也在文件中(export const getVersionQueueFilters):
import { fetch, addTask } from 'domain-task';
import { Action, Reducer, ActionCreator } from 'redux';
import { takeLatest, takeEvery } from "redux-saga"
import { call, put, take, race, select } from "redux-saga/effects"
import * as moment from 'moment';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface ASVersionQueueState {
queuedVersions: QueuedVersion[];
versionQueueFilter: VersionQueueFilter;
eventsFilterList: SelectListItem[];
employeesFilterList: SelectListItem[];
gridIsLoading: boolean;
versionQueueRefresh: boolean;
error: boolean;
}
export interface QueuedVersion {
VersionCode: string;
VersionQualifier: string;
VersionID: string;
ProductID: string;
PieceName: string;
PrintClass: string;
FirstInhomeDate: string;
AccountID: string;
AccountExecutive: string;
AccountManager: string;
ArtManager: string;
AdUID: string;
Status: string;
Queue: string;
DueDateOverride: string;
IsLocked: string;
}
export interface VersionQueueFilter {
StartDate: string;
EndDate: string;
PieceType: Array<string>;
EventType: Array<string>;
EventID: string;
Employee: string;
}
export interface SelectListItem {
OptionName: string;
OptionVal: string;
}
export let DefaultVersionQueueFilter = {
StartDate: moment().subtract(30, 'days').format('YYYY-MM-DD'),
EndDate: moment().format('YYYY-MM-DD'),
PieceType: ['impactpc'],
EventType: ['special'],
EventID: '',
Employee: '12345'
}
// Version Queue polling delay value
let versionQueuePollDelay: number = 10000; // Delay in milliseconds
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
interface PollVersionsAction {
type: 'POLL_VERSIONS';
versionQueueFilter: VersionQueueFilter;
versionQueueRefresh: boolean;
}
interface PollRequestVersionsAction {
type: 'POLL_REQUEST_VERSIONS';
versionQueueFilter: VersionQueueFilter;
versionQueueRefresh: boolean;
}
interface PollRequestVersionsSuccessAction {
type: 'POLL_REQUEST_VERSIONS_SUCCESS';
versionQueueFilter: VersionQueueFilter;
receivedVersions: QueuedVersion[];
versionQueueRefresh: boolean;
}
interface PollRequestVersionsErrorAction {
type: 'POLL_REQUEST_VERSIONS_ERROR';
}
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = PollVersionsAction | PollRequestVersionsSuccessAction | PollRequestVersionsErrorAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state
export const actionCreators = {
pollVersions: () => {
return { type: 'POLL_VERSIONS', versionQueueFilter: getVersionQueueFilters, versionQueueRefresh: true }
},
pollRequestVersions: (versionQueueFilter: VersionQueueFilter, versionQueueRefresh: boolean) => {
return { type: 'POLL_REQUEST_VERSIONS', versionQueueFilter: versionQueueFilter, versionQueueRefresh: versionQueueRefresh }
},
pollRequestVersionsSuccess: (versionQueueFilter: VersionQueueFilter, versionQueueRefresh: boolean, data: QueuedVersion[]) => {
return { type: 'POLL_REQUEST_VERSIONS_SUCCESS', versionQueueFilter: versionQueueFilter, receivedVersions: data, versionQueueRefresh: versionQueueRefresh }
},
pollRequestVersionsError: () => {
return { type: 'POLL_REQUEST_VERSIONS_ERROR' }
}
};
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: ASVersionQueueState = { gridIsLoading: false, versionQueueRefresh: false, queuedVersions: [], versionQueueFilter: DefaultVersionQueueFilter, eventsFilterList: [], employeesFilterList: [], error: false };
export const reducer: Reducer<ASVersionQueueState> = (state: ASVersionQueueState, incomingAction: Action) => {
const action = incomingAction as KnownAction;
switch (action.type) {
case 'POLL_VERSIONS':
return {
...state,
versionQueueFilter: action.versionQueueFilter,
versionQueueRefresh: action.versionQueueRefresh,
gridIsLoading: true
}
case 'POLL_REQUEST_VERSIONS_SUCCESS':
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
// handle out-of-order responses.
if (action.versionQueueFilter === state.versionQueueFilter && action.versionQueueRefresh === state.versionQueueRefresh) {
return {
...state,
queuedVersions: action.receivedVersions,
versionQueueRefresh: action.versionQueueRefresh,
gridIsLoading: false
}
}
break;
case 'POLL_REQUEST_VERSIONS_ERROR':
return {
...state,
error: true
}
default:
// The following line guarantees that every action in the KnownAction union has been covered by a case above
const exhaustiveCheck: never = action;
}
return state || unloadedState;
};
// Sagas
// Saga Watchers
export const sagas = [
takeEvery('POLL_VERSIONS', fetchPollVersionsAsync)
]
// Selector Function
export const getVersionQueueFilters = (store: ASVersionQueueState) => store.versionQueueFilter;
// Utility function to delay effects
export function delay(delayMS: number) {
const promise = new Promise(resolve => {
setTimeout(() => resolve(true), delayMS)
});
return promise;
}
export function* versionPoller() {
const versionQueueFilters = yield select(getVersionQueueFilters);
try {
yield call(delay, versionQueuePollDelay);
yield put(actionCreators.pollVersions() );
} catch (error) {
// cancellation error
return;
}
}
export function* watchVersionPoller() {
while (true) {
yield take('POLL_REQUEST_VERSIONS_SUCCESS');
yield call(versionPoller);
}
}
export function* fetchPollVersionsAsync(action: PollVersionsAction) {
try {
yield put(actionCreators.pollRequestVersions(action.versionQueueFilter, action.versionQueueRefresh));
const data = yield call(() => {
return fetch('api/Versions')
.then(res => res.json())
}
);
yield put(actionCreators.pollRequestVersionsSuccess(action.versionQueueFilter, action.versionQueueRefresh, data));
} catch (error) {
yield put(actionCreators.pollRequestVersionsError());
}
}
选择器用于saga函数“versionPoller()”。
基本上,我会轮询我的 API 以获取任何更新的数据,但它至少需要传递一组默认的过滤器值。我想为此使用当前处于状态的过滤器值。
我也尝试将我的选择器函数定义为:
export const getVersionQueueFilters = getState().ASVersionQueueState.versionQueueFilter;
当我这样做时,我收到错误“找不到名称 getState”。
知道我做错了什么吗?
【问题讨论】:
标签: typescript react-redux redux-saga