[Redux] Redux Observable 筆記
整合 Redux Observable
Setting Up The Middleware @ redux-observable
建立 Slice
import { mergeMap, map, filter } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';
import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { AppEpic } from '../../store/types';
import { IGithubUser } from './types';
const initialState: IGithubUser = {
id: 0,
name: '',
login: '',
email: '',
}
/**
* Actions
*/
export const fetchUser = createAction<string>('user/FETCH_USER')
const fetchUserFulfilled = createAction<IGithubUser>('user/FETCH_USER_FULFILLED')
/**
* Epics
*/
export const fetchUserEpic: AppEpic = (action$) => action$.pipe(
filter(fetchUser.match),
mergeMap(action =>
ajax.getJSON(`https://api.github.com/users/${action.payload}`).pipe(
map(response => fetchUserFulfilled(response as IGithubUser)),
)
),
);
/**
* Slice
*/
export const githubUserSlice = createSlice({
name: 'githubUser',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchUserFulfilled, (state, action: PayloadAction<IGithubUser>) => {
const { id, name, login, email } = action.payload
return {
...state,
id,
name,
login,
email,
};
})
}
})
/**
* Reducer
*/
export default githubUserSlice.reducer;
combineReducers
// src/store/reducers.ts
import { combineReducers } from '@reduxjs/toolkit';
import githubUserReducer from '../features/githubUser/githubUserSlice';
const rootReducer = combineReducers({
githubUser: githubUserReducer
});
export default rootReducer;
Type Definition
import { Action, AnyAction, ThunkAction } from '@reduxjs/toolkit';
import { Epic } from 'redux-observable';
import { store } from '.';
import rootReducer from './reducers';
export type AppState = ReturnType<typeof rootReducer>; // for useSelector
export type AppDispatch = typeof store.dispatch; // for useDispatch
export type RootState = ReturnType<typeof store.getState>;
export type AppEpic = Epic<AnyAction, AnyAction, AppState>; // for redux-observable
export type AppThunk<ReturnType = void> = ThunkAction< // for redux-thunk
ReturnType,
RootState,
unknown,
Action<string>
>;
combineEpics
// ./src/store/epics
import { AnyAction } from '@reduxjs/toolkit';
import { combineEpics, createEpicMiddleware } from 'redux-observable';
import { fetchUserEpic } from '../features/user/userSlice';
import { AppState } from './types';
export const epicMiddleware = createEpicMiddleware<AnyAction, AnyAction, AppState>();
export const rootEpic = combineEpics(
fetchUserEpic,
)
configureStore
epicMiddleware.run
一定要在 redux middleware 建立後才能被呼叫
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { pokemonApi } from '../services/pokemon';
import rootReducer from './reducers';
import { epicMiddleware, rootEpic } from './epics';
// 透過 configureStore() 建立 Redux Store
export const store = configureStore({
reducer: rootReducer,
// 加入 api middleware 來啟用 caching、invalidation、polling 等其他方法
middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), pokemonApi.middleware, epicMiddleware],
});
// epicMiddleware.run(rootEpic) should not be called before the middleware has been setup by redux.
// Provide the epicMiddleware instance to createStore() first.
epicMiddleware.run(rootEpic)