Skip to main content

[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)