Puffin's DevLog

TIL: Redux 4

Building React Applications with Idiomatic Redux

15. 페치된 데이터를 가지고 액션 디스패치 하기

데이터를 가져온 후에 Redux 액션을 전달하는 방법을 배우고, 라우터가 변경될 때는 어떻게 하면 좋은지를 다시 설명합니다.

라이프사이클 훅 사이에 공통된 코드를 별도의 메서드로 추출 할 수 있습니다. 이를 fetchData 라고 부릅시다. 여기서 가져올 데이터는 filter 에만 의존합니다.

초기 데이터를 가져 오기 위해, componentDidMount() 훅에서 이 메소드를 호출합니다. 또한 filter 가 componentDidUpdate() 훅 내에서 변경 될 때마다 호출합니다.

VisibleTodoList.js

class VisibleTodoList extends Component {
  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps) {
    if (this.props.filter !== prevProps.filter) {
      this.fetchData();
    }
  }

  fetchData() {
    fetchTodos(this.props.filter).then(todos =>
      console.log(this.props.filter, todos)
    );
  }
  .
  .
  .

fetchData() 업데이트 하기

가져온 todos 가 Redux store 의 상태가 되기를 원합니다. 어떤 것을 상태로 가져 오는 유일한 방법은 액션을 전달하는 것입니다.

우리는 방금 가져온 todos 를 콜백 prop 인 receiveTodos 라고 부릅니다.

fetchData() {
  fetchTodos(this.props.filter).then(todos =>
    this.props.receiveTodos(todos)
  );
}

컴포넌트 내에서 사용할 수 있게 하려면, receiveTodos 라는 함수를 전달해야 합니다. 이 함수는 connect의 두 번째 인수 안에서 액션 크리에이터가 됩니다. 함수의 이름이 콜백 prop 의 이름과 일치하기 때문에, ES6 Object 속성 표기법을 사용해서 더 짧게 작성할 수 있습니다.

다른 모든 액션 크리에이터가 정의된 파일에서 receiveTodos 를 import 해옵니다.

// At the top of `VisibleTodoList.js`
import { toggleTodo, receiveTodos } from '../actions'

...

// At the bottom of `VisibleTodoList.js`
VisibleTodoList = withRouter(connect(
  mapStateToProps,
  { onTodoClick: toggleTodo, receiveTodos }
)(VisibleTodoList))

receiveTodos 구현하기

이제 실제로 receiveTodos 를 구현해야 합니다.

액션 크리에이터 파일 (src/actions/index.js) 안에는 서버 response를 인수로 하고, 'RECEIVE_TODOS'의 type과 및 response를 필드로 가지는 객체를 반환하는 receiveTodos 함수의 새 export 를 만듭니다.

src/actions/index.js 내부

export const receiveTodos = response => ({
  type: 'RECEIVE_TODOS',
  response,
});

이 액션을 처리하는 리듀서는 응답과 같은 것이 어떤 filter 인지를 알아야 하므로, receiveTodos 액션 크리에이터에 인수로 filter를 추가하고 이를 액션 객체의 일부로 전달합니다.

export const receiveTodos = (filter, response) => ({
  type: 'RECEIVE_TODOS',
  filter,
  response,
});

filter를 사용하여 VisibleTodoList 컴포넌트 업데이트 하기

VisibleTodoList로 돌아가서, fetchData를 업데이트하여 액션 크리에이터를 통해 필터를 전달합니다.

ES6 분해대입 구문을 사용하면 prop으로부터 filterreceiveTodos를 바로 가져올 수 있습니다. filter를 바로 분해대입할 수 있는 것은 매우 중요한데, 콜백이 실행되었을 때 사용자가 페이지를 닫아서 this.props.filter가 아마도 변경되었을 수 있기 때문입니다.

Inside VisibleTodoList

fetchData() {
  const { filter, receiveTodos } = this.props;
  fetchTodos(filter).then(todos =>
    receiveTodos(filter, todos)
  );
}

보일러플레이트 덜 사용하기

앱을 사용하면서, 컴포넌트는 데이터가 준비되면 라이프사이클 훅에서 데이터를 가져 와서 Redux 액션을 디스패치합니다. 이제 보일러플레이트를 더 적게 작성해 보겠습니다.

named import 를 namespace import 로 바꿀 수 있습니다. 즉, 액션이 있는 파일에서 내보낸 모든 함수는 actions 라는 객체에 있으며, 이 객체는 connect의 두 번째 인수로 전달됩니다.

// At the top of `VisibleTodoList`

// Before: import { toggleTodo, receiveTodos } from '../actions'
import * as actions from '../actions' // After
.
.
.
// At the bottom of `VisibleTodoList`
VisibleTodoList = withRouter(connect(
  mapStateToProps,
  // Before: { onTodoClick: toggleTodo, receiveTodos}
  actions // After
)(VisibleTodoList))

VisibleTodoList 의 render() 함수 내부에서 prop 을 분해대입합니다. toggleTodo 액션 크리에이터가 onTodoClick prop 이름을 전달하되, 나머지 prop 은 그대로 전달 될 수 있기 때문입니다.

...rest 객체는 이제 toggleTodo 이외의 모든 prop 이 들어 있으므로, 그대로 내려보낼 것입니다. TodoList 컴포넌트가 기대하는 바에 따라 toggleTodo 를 onTodoClick prop 으로 전달합니다.

// Inside of `VisibleTodoList`
  render() {
    const { toggleTodo, ...rest } = this.props;
    return (
      <TodoList
        {...rest}
        onTodoClick={toggleTodo}
      />
    );
  }
}

16. Promise 알아보기 위해 dispatch 감싸기

receiveTodos 액션 크리에이터 그 자체로는 그다지 유용하지 않습니다. 우리가 호출할 때마다, 원하는 것은 todos 를 먼저 가지고 오는 것이기 때문입니다. fetchTodosreceiveTodos는 동일한 인수를 받아들이므로, 이 코드를 하나의 액션 크리에이터로 그룹화 할 수 있다면 좋을 것입니다.

VisibleTodoList 내의 기존 fetchData()

fetchData() {
  const { filter, receiveTodos } = this.props;
  fetchTodos(filter).then(todos =>
    receiveTodos(filter, todos)
  );
}

액션 크리에이터 리팩토링 하기

가짜 API 를 액션 크리에이터 파일 (src/actions/index.js)에 가져 와서 시작하겠습니다.

import * as api from '../api'

이제 우리는 fetchTodos 라는 비동기 액션 크리에이터를 추가 할 것입니다. 인수로 filter 를 받은 다음, 그것으로 API 의 fetchTodos 메서드를 호출합니다.

Promise 의 then 메서드를 사용하여 response의 Promise 의 결과를 filterresponse가 주어진 receiveTodos 에 의해 생성된 액션 객체로 바꿉니다.

src / actions / index.js 내부

export const fetchTodos = filter =>
  api.fetchTodos(filter).then(response => receiveTodos(filter, response));

receiveTodos 는 동기적으로 액션 객체를 환하지만, fetchTodos 는 액션 객체를 통해 resolve 되는 Promise 를 반환합니다.

이제 우리는 액션 크리에이터로부터 receiveTodos 를 내보내는 것을 멈출 수 있습니다. 왜냐하면 우리는 컴포넌트를 변경하여 fetchTodos 를 직접 사용할 수 있기 때문입니다.

VisibleTodoList 업데이트 하기

컴포넌트 파일로 돌아가서, connect에 의해 삽입된 fetchTodos prop 을 사용할 수 있습니다. 이것은 방금 작성한 새로운 비동기 fetchTodos 액션 크리에이터에 해당합니다.

지금부터 우리는 fetchTodos 액션 크리에이터를 사용할 것이기 때문에, import {fetchTodos} from '../api'를 제거할 수 있습니다. 이 액션은 connect 에 의해 prop 으로 주입됩니다.

fetchData () {
  const {filter, fetchTodos} = this.props;
  fetchTodos (filter);
}

요약

fetchTodos 액션 크리에이터는 API 에서 fetchTodos 함수를 호출하지만, 그 결과를 receiveTodos 에 의해 생성된 Redux 액션으로 변환합니다.

그러나 기본적으로 Redux 는 Promises 가 아닌 일반 객체를 디스패치하는 것만 허용합니다. 우리는 configureStore.js 내부에서 addLoggingToDispatch ()와 동일한 트릭을 사용하여 Promises 를 인식하도록 알려줄 수 있습니다. (addLoggingToDispatch 함수는 스토어에서 dispatch를 ​​ 가져와서 모든 액션과 상태를 기록하는 새로운 버전의 dispatch를 ​​ 반환한다는 점을 상기하세요).

Promise 지원 추가

configureStore.js 내부에서 스토어를 가져와 Promise 을 지원하는 dispatch의 버전을 반환하는 addPromiseSupport() 함수를 생성합니다.

먼저 스토어에서 정의된 rawDispatch 함수를 가져옵니다. 디스패치 함수와 동일한 API 를 가진 함수를 반환합니다. 즉, 액션을 받습니다.

const addPromiseSupportToDispatch = store => {
  const rawDispatch = store.dispatch;
  return action => {
    if (typeof action.then === 'function') {
      return action.then(rawDispatch);
    }
    return rawDispatch(action);
  };
};

액션이 실제 액션인지 또는 promise 인지는 알 수 없기 때문에 함수인 then 메소드가 있는지 확인합니다. 그렇다면 우리는 그것이 promise 이라는 것을 압니다. 액션이 promise 인 경우 rawDispatch를 통해 내려온 액션 객체를 resolve 할 때까지 기다립니다.

그렇지 않으면, 우리는 우리가 받은 액션 객체로 바로 rawDispatch를 호출할 수 있습니다.

새로운 addPromiseSupportToDispatch 함수를 사용하면 액션과 액션으로 resolve 할 수 있는 promise 을 모두 전달할 수 있습니다.

끝내려면 새 기능을 한 번 더 호출해야만 스토어가 App 으로 반환됩니다.

//`configureStore.js`의 맨 아래에
const configureStore = () => {
  const store = createStore (todoApp);

if (process.env.NODE_ENV! == 'production') {
    store.dispatch = addLoggingToDispatch (store);
  }

store.dispatch = addPromiseSupportToDispatch (store);

return store;
};

지금 앱을 실행하면, 응답 준비가 되었을 때 'RECEIVE_TODOS' 액션이 디스패치되는 것을 확인할 수 있습니다. 그러나 컴포넌트는 비동기 액션 크리에이터에서 비동기 로직을 캡슐화하는 보다 편리한 API 를 사용합니다.

순서는 중요합니다

configureStore 내부의 디스패치 함수를 재정의하는 순서가 중요하다는 것을 기억하십시오.

addLoggingToDispatch 전에 addPromiseSupportToDispatch 를 호출하도록 변경하면 액션이 먼저 출력되고 promise 가 처리됩니다.

이렇게 하면 우리에게 undefined라는 액션 타입이 생기고 액션 대신 Promise 가 표시됩니다. 이는 그리 유용하지 않습니다.

Loading script...