# 2-1-1-advance-action

## 비동기 액션 생성자

미들웨어는 모든 액션을 보내기 전에 임의의 로직을 삽입할 수 있게 해준다. 비동기 액션은 미들웨어의 대표적인 예시다.

미들웨어 없이 `dispatch`가 평범한 객체만을 받을 수 있다. 그래서 Ajax 호출을 컴포넌트 안에서 해야만 한다.

`actionCreator.js`

```javascript
export function loadPostsSuccess(userId, response) {
    return {
        type: 'LOAD_POSTS_SUCCESS',
        userId,
        response
    };
}

export function loadPostsFailure(userId, error) {
    return {
        type: 'LOAD_POSTS_FAILURE',
        userId,
        error
    };
}

export function loadPostsRequest(userId) {
    return {
        type: 'LOAD_POSTS_REQUEST',
        userId
    }
}
```

`UserInfo.js`

```javascript
import { Component } from 'react';
import { connect } from 'react-redux';
import { loadPostsRequest, loadPostsSuccess, loadPostsFailure } from './actionCreators';

class Posts extends Component {
  loadData(userId) {
    // Injected into props by React Redux `connect()` call:
    let { dispatch, posts } = this.props;

    if (posts[userId]) {
      // There is cached data! Don't do anything.
      return;
    }

    // Reducer can react to this action by setting
    // `isFetching` and thus letting us show a spinner.
    dispatch(loadPostsRequest(userId));

    // Reducer can react to these actions by filling the `users`.
    fetch(`http://myapi.com/users/${userId}/posts`).then(
      response => dispatch(loadPostsSuccess(userId, response)),
      error => dispatch(loadPostsFailure(userId, error))
    );
  }

  componentDidMount() {
    this.loadData(this.props.userId);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.userId !== this.props.userId) {
      this.loadData(nextProps.userId);
    }
  }

  render() {
    if (this.props.isFetching) {
      return <p>Loading...</p>;
    }

    let posts = this.props.posts.map(post =>
      <Post post={post} key={post.id} />
    );

    return <div>{posts}</div>;
  }
}

export default connect(state => ({
  posts: state.posts
}))(Posts);
```

서로 다른 컴포넌트들이 같은 API 엔드포인트에서 데이터를 요청하기 때문에 *반복*이 생기게 된다. 또한 로직의 일부(캐시된 데이터가 있으면 일찍 종료하기)를 여러 컴포넌트에서 재사용하기를 원한다.

**미들웨어는 좀 더 표현력 있는 비동기 액션 생성자를 작성하게 해준다.** 미들웨어는 평범한 객체 외의 다른 것들을 보낼 수 있게 해주고 그 값을 변환해준다. **예를 들어 미들웨어는 약속이 보내지면 "잡아서" 요청과 성공/실패 액션으로 바꿔줄 수 있다.**

미들웨어의 간단한 예는 `redux-thunk`다. **"썽크" 미들웨어는 액션 생성자를 함수를 반환하는 함수인 "썽크"로 작성할 수 있게 해준다.** (이는 제어를 역전한다.) 즉, `dispatch`를 인자로 받기 떄문에 액션을 여러번 보내는 액션 생산자를 작성할 수 있다.

위 코드를 redux-thunk를 리용하여 재작성해보자.

`actionCreators.js`

```javascript
export function loadPosts(userId) {
  // Interpreted by the thunk middleware:
  return function (dispatch, getState) {
    let { posts } = getState();
    if (posts[userId]) {
      // There is cached data! Don't do anything.
      return;
    }

    dispatch({
      type: 'LOAD_POSTS_REQUEST',
      userId
    });

    // Dispatch vanilla actions asynchronously
    fetch(`http://myapi.com/users/${userId}/posts`).then(
      response => dispatch({
        type: 'LOAD_POSTS_SUCCESS',
        userId,
        response
      }),
      error => dispatch({
        type: 'LOAD_POSTS_FAILURE',
        userId,
        error
      })
    );
  }
}
```

`UserInfo.js`

```javascript
import { Component } from 'react';
import { connect } from 'react-redux';
import { loadPosts } from './actionCreators';

class Posts extends Component {
  componentDidMount() {
    this.props.dispatch(loadPosts(this.props.userId));
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.userId !== this.props.userId) {
      this.props.dispatch(loadPosts(nextProps.userId));
    }
  }

  render() {
    if (this.props.isFetching) {
      return <p>Loading...</p>;
    }

    let posts = this.props.posts.map(post =>
      <Post post={post} key={post.id} />
    );

    return <div>{posts}</div>;
  }
}

export default connect(state => ({
  posts: state.posts
}))(Posts);
```

코드가 훨씬 줄었다. "Smart" `loadPosts` 액션 생성자 대신 `loadPostsSuccess`와 같은 "평범한" 액션 생성자를 사용할 수 있다.

미들웨어 직접 작성할 수 있다. 비동기 액션 생산자를 아래 처럼 하는 대신에:

```javascript
export function loadPosts(userId) {
    return {
        types: ['LOAD_POSTS_REQUEST', 'LOAD_POSTS_SUCCESS', 'LOAD_POSTS_FAILURE'],
        shouldCallAPI: (state) => !state.users[userId],
        callAPI: () => fetch(`http://myapi.com/users/${userId}/posts`),
        payload: {userId}
    }
}
```

이런 액션들을 변환해주는 미들웨어를 만들 수 있다.

```javascript
function callAPIMiddleware({ dispatch, getState }) {
  return function (next) {
    return function (action) {
      const {
          types,
          callAPI,
          shouldCallAPI = () => true,
          payload = {}
      }
    }
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hoilzz-til.gitbook.io/docs/frontend/react/redux/redux-doc/2-1-1-advance-action.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
