---
# System prepended metadata

title: 組合 Redux actions

---

# 組合 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">&gt; 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>&mdash; Some(@cλβαι) (@_cybai) <a href="https://twitter.com/_cybai/status/1230081117786525696?ref_src=twsrc%5Etfw">February 19, 2020</a></blockquote>

還是有人注意到 reducer 和 monoid 的關聯的。
