reactjs - TypeScript ชนิดที่ถูกต้องสำหรับการส่ง thunk?
ฉันมีแอคชัน Redux แบบอะซิงโครนัส ดังนั้นจึงใช้ middleware แบบ thunk อยู่
ฉันมีฟังก์ชัน mapStateToProps, mapDispatchToProps และ connect สำหรับคอมโพเนนต์ดังนี้:
const mapStateToProps = (store: IApplicationState) => {
return {
loading: store.products.loading,
products: store.products.products
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
getAllProducts: () => dispatch(getAllProducts())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductsPage);
ทั้งหมดนี้ใช้งานได้ แต่ฉันสงสัยว่ามันเป็นไปได้ไหมที่จะเปลี่ยนประเภท any ของพารามิเตอร์ dispatch ใน mapDispatchToProps?
ฉันลองใช้ ThunkDispatch<IApplicationState, void, Action> แต่ได้รับข้อผิดพลาด TypeScript ดังต่อไปนี้ในฟังก์ชัน connect:
Argument of type 'typeof ProductsPage' is not assignable to parameter of type 'ComponentType<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>>'.
Type 'typeof ProductsPage' is not assignable to type 'ComponentClass<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>, any>'.
Types of property 'getDerivedStateFromProps' are incompatible.
Type '(props: IProps, state: IState) => { products: IProduct[]; search: string; }' is not assignable to type 'GetDerivedStateFromProps<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>, any>'.
Types of parameters 'props' and 'nextProps' are incompatible.
Type 'Readonly<Matching<{ loading: boolean; products: IProduct[]; } & { getAllProducts: () => Promise<void>; }, IProps>>' is not assignable to type 'IProps'.
Types of property 'getAllProducts' are incompatible.
Type '() => Promise<void>' is not assignable to type '() => (dispatch: Dispatch<AnyAction>) => Promise<void>'.
Type 'Promise<void>' is not assignable to type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.
Type 'Promise<void>' provides no match for the signature '(dispatch: Dispatch<AnyAction>): Promise<void>'.
เป็นไปได้หรือไม่ที่จะแทนที่ประเภท any บนพารามิเตอร์ dispatch ใน mapDispatchToProps?
คำตอบ #1
การตั้งค่านี้ใช้ได้ผลดีกับฉันมาก:
// store.ts
//...
export type TAppState = ReturnType<typeof rootReducer>;
export type TDispatch = ThunkDispatch<TAppState, void, AnyAction>;
export type TStore = Store<TAppState, AnyAction> & { dispatch: TDispatch };
export type TGetState = () => TAppState;
//...
const store: TStore = createStore(
rootReducer,
composeEnhancers(applyMiddleware(...middleware), ...enhancers)
);
export default store;
ที่ rootReducer ในการตั้งค่าของฉันมีลักษณะดังนี้ const rootReducer=createRootReducer(history);
// createRootReducer.ts
import { combineReducers, Reducer, AnyAction } from 'redux';
import { History } from 'history';
import {
connectRouter,
RouterState,
LocationChangeAction,
} from 'connected-react-router';
// ... here imports of reducers
type TAction = AnyAction & LocationChangeAction<any>;
export type TRootReducer = Reducer<
{
// ... here types of the reducer data slices
router: RouterState;
},
TAction
>;
const createRootReducer = (history: History): TRootReducer =>
combineReducers({
// ... here actual inserting imported reducers
router: connectRouter(history),
});
export default createRootReducer;
จากนั้นในส่วนประกอบที่เชื่อมต่อ
import { connect } from 'react-redux';
import Add, { IProps } from './Add'; // Component
import { TDispatch, TAppState } from '../../store';
type TStateProps = Pick<
IProps,
'title' | 'data' | 'loading'
>;
const mapStateToProps = (
state: TAppState,
): TStateProps => {
// ... here you have typed state :)
// and return the TStateProps object as required
return {
loading: state.someReducer.loading,
//...
}
}
type TDispatchProps = Pick<IProps, 'onSave'>;
const mapDispatchToProps = (
dispatch: TDispatch,
): TDispatchProps => {
// here you have typed dispatch now
// return the TDispatchProps object as required
return {
onSave: (): void => {
dispatch(saveEntry()).then(() => {
backButton();
});
},
}
}
สำหรับการกระทำแบบ thunk ฉันทำดังนี้
// this is a return type of the thunk
type TPromise = Promise<ISaveTaskResponse | Error>;
export const saveEntry = (): ThunkAction<
TPromise, // thunk return type
TAppState, // state type
any, // extra argument, (not used)
ISaveEntryAction // action type
> => (dispatch: TDispatch, getState: TGetState): TPromise => {
// use getState
// dispatch start saveEntry action
// make async call
return Axios.post('/some/endpoint', {}, {})
.then((results: { data: ISaveTaskResponse; }): Promise<ISaveTaskResponse> => {
// get results
// you can dispatch finish saveEntry action
return Promise.resolve(data);
})
.catch((error: Error): Promise<Error> => {
// you can dispatch an error saveEntry action
return Promise.reject(error);
});
};
คำตอบ #2
Redux สามารถส่ง actions ที่เป็นวัตถุธรรมดาได้ สมมติว่าเรามี action แบบนี้ {type: 'ACTION2'} เราสามารถสร้าง action creator และห่อมันภายใน dispatch แบบนี้
// This is our action
interface Action2 extends Action { type: "ACTION2"; }
// And this is action crator
const action2 = (): Action2 => ({ type: "ACTION2" });
ด้วย thunk middleware Redux สามารถ dispatch ฟังก์ชัน และเราสามารถสร้าง action แบบ async ได้แบบนี้
// This action will be dispatched from async action creator
interface Action1 extends Action { type: "ACTION1"; payload: string; }
// And async action creator
const thunkAction = (arg: string): ThunkAction<Promise<void>, {}, AppState, KnownActions> =>
async dispatch => {
const res = await asyncFunction(arg);
dispatch({ type: "ACTION1", payload: res });
},
ที่นี่ใช้ประเภท ThunkAction<R, S, E, A extends Action> ซึ่งรับอาร์กิวเมนต์ประเภทดังต่อไปนี้:
Rย่อมาจากชนิดการส่งค่ากลับของฟังก์ชันภายใน ในตัวอย่างข้างต้น ฟังก์ชันภายในเป็นฟังก์ชันแบบ async ดังนั้นมันจึงส่งค่ากลับเป็นPromise<void>Sย่อมาจากสถานะของแอปEเป็นประเภทคุณลักษณะขยายที่ไม่ได้ใช้Aเป็นประเภทของการกระทำKnownActionsในตัวอย่างข้างต้นคือการรวมกันของประเภทการกระทำทั้งหมดที่เป็นไปได้ (type KnownActions=Action1 | Action2;)
ตอนนี้มาดูกันว่าส่วนประกอบสามารถพิมพ์ได้อย่างไร
interface Component1DispatchProps {
action2: () => Action2;
thunkAction: (arg: string) => Promise<void>;
}
interface Component1StateProps {
stateprop: string;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch<AppState, {}, KnownActions>
) => {
return {
action2: () => dispatch(action2()),
thunkAction: (arg: string) =>
dispatch(thunkAction(arg))
};
};
เมธอด thunkAction คืนค่าเป็น dispatch(thunkAction(arg)) ซึ่ง thunkAction(arg) คืนค่าเป็น ThunkAction<Promise<void>, {}, AppState, KnownActions> ดังนั้น dispatch จะถูกเรียกด้วยอาร์กิวเมนต์ชนิด ThunkAction และจะถูกแก้ไขเป็น
<TReturnType>(
thunkAction: ThunkAction<TReturnType, TState, TExtraThunkArg, TBasicAction>,
): TReturnType;
ผลลัพธ์คือ dispatch(thunkAction(arg)) จะคืนค่า TReturnType หรือ Promise<void> ในตัวอย่างของเรา ลายเซ็นดังกล่าวถูกกำหนดสำหรับ thunkAction ใน Component1DispatchProps
ตัวอย่างที่สมบูรณ์อยู่ที่นี่
