为什么要使用redux-saga
redux的优势接口与实现分离,这一模式虽然很有用,但也存在着副作用。
当屏蔽某一个action操作的时候,它实际上是先将整个state恢复为最近确定的state,然后一遍一遍的播放后续的action,生成新的state。这当中如果有任何副作用(异步操作,外部数据源读取),都会导致生成不可预测的state。
Redux-Saga充分利用了ES6的Generator特性,一切异步调用都可以通过yield交给Redux-Saga去处理,并在异步代码执行完成后返回yield处继续执行,从此和回调形式的异步调用说再见,同时支持try/catch语法,可以更方便的对代码块进行异常捕获。
使用方法
1. 在全局定义store(一般是在 main 文件中)
import { Provider } from 'react-redux';
import { store } from '@/state/index';
const container = document.getElementById('root')!;
const rootElement = (
<ConfigProvider>
<Provider store={store}>
<AppWrapper />
</Provider>
</ConfigProvider>
);
const root = createRoot(container);
root.render(rootElement);
// state/index.ts
export * as actionCreators from './action-creators';
export * from './store';
export * from './reducers/index';
export * from './saga/index';
// state/store.ts
import { createStore, applyMiddleware } from 'redux';
import rootReducers from './reducers/index';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './saga';
const sagaMiddleware = createSagaMiddleware();
// 这里将saga和reducer联动起来
export const store = createStore(
rootReducers,
{},
applyMiddleware(sagaMiddleware),
);
sagaMiddleware.run(rootSaga);
2. 创建action
// state/action-creators/index.ts
// 通过type关联saga中的getOrderDetail方法
export const getDetailAction = (payload: orderNo) => ({
type: 'GET_ORDER_DETAIL_ACTION',
payload,
});
3. 定义saga
// state/saga/a.saga.ts
import { takeEvery, put, call } from 'redux-saga/effects';
import { getOrderNoApi } from '@/api';
const getOrderNoApi = (data: orderNo) => {
return api({
url: 'xxxxxxx',
method: 'POST',
data,
});
};
// payload的参数从action中传递过来
const getOrderDetail = function* (action: {
type: String;
payload: { orderNo: string };
}): Generator {
try {
// 绑定实际接口
let res = yield call(getOrderNoApi, action.payload);
// 更新数据,在reducer中可以得到这里传递出去的数据
yield put({
type: 'GET_ORDER_DETAIL_ACTION',
payload: res.data,
});
} catch (error) {}
};
export default function* () {
yield takeEvery('GET_ORDER_DETAIL_ACTION', getOrderDetail);
}
// state/saga/index.ts
import { fork, all, ForkEffect } from 'redux-saga/effects';
// 找到所有的saga文件
const context = require.context('@/state/saga', false, /\.saga.ts$/);
const allSaga: ForkEffect[] = [];
context.keys().forEach((filename) => {
const file = context(filename);
allSaga.push(fork(file.default));
});
// 合并saga
export default function* rootSaga() {
yield all([...allSaga]);
}
4. 定义reducer
// state/reducers/a.ts
interface OrderPayloadProps {
type: String;
payload: Detail;
}
const searchParamsReducer = (
state: String,
action: OrderPayloadProps,
) => {
switch (action.type) {
case 'GET_ORDER_DETAIL_ACTION':
return {
...state,
// 这里的detail主要在业务里使用,可自定义
detail: action.payload,
};
default:
return state;
}
};
export default searchParamsReducer;
// state/reducers/index.ts
import { combineReducers } from 'redux';
import aReducer from './a';
import bReducer from './b';
// 合并reducer,
const reducers = combineReducers({
a: aReducer,
b: bReducer,
});
export default reducers;
export type RootState = ReturnType<typeof reducers>;
5. 在业务中调用
import { useDispatch, useSelector } from 'react-redux';
import { actionCreators, RootState } from '@/state';
const Page = () => {
const dispatch = useDispatch();
// request
const {
getDetailAction,
} = bindActionCreators(actionCreators, dispatch);
// response
const {
detail,
} = useSelector<RootState, 返回值类型>(state => state.a);
useEffect(() => {
getDetailAction({ orderNo: params.id || '' }); // 调用接口
}, []);
return (
<div>
<span>名称: {detail?.name}</span>
<span>地址: {detail?.address}</span>
</div>
);
}
export default Page;