# 組合 Redux actions
如果不堅持一定得用 `combineReducers` 的話, Redux action 還可以做到些奇怪的事。
例如平常寫 action 時為了讓 action 可以被 dispatch 到正確的地方,會寫:
```javascript
const POST_REQUEST = 'POST_REQUEST';
const POST_SUCCESS = 'POST_SUCCESS';
const POST_FAILURE = 'POST_FAILURE';
const PRODUCT_LIST_REQUEST = 'PRODUCT_LIST_REQUEST';
const PRODUCT_LIST_SUCCESS = 'PRODUCT_LIST_SUCCESS';
const PRODUCT_LIST_FAILURE = 'PRODUCT_LIST_FAILURE';
const PostRequest = () => ({
type: POST_REQUEST,
});
const PostSuccess = (data) => ({
type: POST_SUCCESS,
data,
});
const PostFailure = (error) => ({
type: POST_FAILURE,
error,
});
const ProductListRequest = () => ({
type: PRODUCT_LIST_REQUEST,
});
const ProductListSuccess = (data) => ({
type: PRODUCT_LIST_SUCCESS,
data,
});
const ProductListFailure = (error) => ({
type: PRODUCT_LIST_FAILURE,
error,
});
```
然後在 reducer 裡面寫:
```javascript
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case POST_REQUEST: {
const { data } = action;
return {
...state,
{
...state.post,
isPending: true,
},
};
}
case POST_SUCCESS: {
const { data } = action;
return {
...state,
{
...state.post,
isPending: false,
data,
},
};
}
case POST_FAILURE: {
const { error } = action;
return {
...state,
{
...state.post,
isPending: false,
error,
data: undefined,
},
};
}
case PRODUCT_LIST_REQUEST: {
const { data } = action;
return {
...state,
{
...state.products,
isPending: true,
},
};
}
case POST_LIST_SUCCESS: {
const { data } = action;
return {
...state,
{
...state.products,
isPending: false,
data,
},
};
}
case POST_FAILURE: {
const { error } = action;
return {
...state,
{
...state.products,
isPending: false,
error,
data: [],
},
};
}
default:
return state;
};
}
```
可是這樣很煩啊,一樣的東西要寫好多遍。
---
如果加上兩個 action 來選不同的欄位,那可以簡化為:
```javascript
const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS';
const FAILURE = 'FAILURE';
const Request = (data) => ({ type: REQUEST, data });
const Success = (data) => ({ type: POST_SUCCESS, data });
const Failure = (error, data) => ({ type: POST_FAILURE, error, data });
const requestState = {
isPending: false,
data: undefined,
error: undefined,
};
const requestReducer = (state = requestState, action = {}) => {
switch (action.type) {
case REQUEST: {
const { data } = action;
return {
...state,
isPending: true,
data,
};
}
case SUCCESS: {
const { data } = action;
return {
...state,
isPending: false,
data,
};
}
case FAILURE: {
const { data, error } = action;
return {
...state,
isPending: false,
data,
error,
};
}
default:
return state;
}
}
const POST = 'POST';
const PRODUCT_LIST = 'PRODUCT_LIST';
const Post = (data) => ({ type: POST, data });
const ProductList = (data) => ({ type: PRODUCT_LIST, data });
const initialState = {
post: requestState,
productList: { ...requestState, data: [] },
};
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case POST: {
const { data: nextAction } = action;
const post = requestReducer(state.post, nextAction);
return {
...state,
post,
};
}
case PRODUCT_LIST: {
const { data: nextAction } = action;
const products = requestReducer(state.post, nextAction);
return {
...state,
products,
};
}
default:
return state;
};
```
然後在你的 saga 或是 thunk action 裡面寫:
```javascript
function* post() {
try {
yield put(Post(Request()));
const { data } = await getPost();
yield put(Post(Success(data)));
} catch (error) {
yield put(Post(Failure(error)));
}
}
function* products() {
try {
yield put(ProductList(Request([])));
const { data } = await getProducts();
yield put(ProductList(Success(data)));
} catch (error) {
yield put(ProductList(Failure(error, [])));
}
}
```
要是再把怎麼處理資料的 action 拆出來:
```javascript
const RESET = 'RESET';
const CONCAT = 'CONCAT';
const Reset = () => ({ type: RESET });
const Concat = (data) => ({ type: CONCAT, data });
const arrayReducer = (state = [], action = {}) {
switch (action.type) {
case RESET: {
return [];
}
case CONCAT: {
return [...state, ...action.data];
}
default:
return state;
};
}
```
甚至可以繼續組合下去:
```javascript
function* products(reset = false) {
try {
let next;
if (reset) next = Reset();
yield put(ProductList(Request(next)));
const { data } = await getProducts();
yield put(ProductList(Success(Concat(data))));
} catch (error) {
yield put(ProductList(Failure(error)));
}
}
```
完。
---
留個紀錄:
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">> Endofunctions are monoids, pointwise monoidal operations are monoids, and combining these two function-monoids give us reducers!<br><br>👀<a href="https://t.co/BQoCc1Rmdd">https://t.co/BQoCc1Rmdd</a></p>— Some(@cλβαι) (@_cybai) <a href="https://twitter.com/_cybai/status/1230081117786525696?ref_src=twsrc%5Etfw">February 19, 2020</a></blockquote>
還是有人注意到 reducer 和 monoid 的關聯的。