상태 관리

상태가 복잡해지는 앱에서 상태들을 관리하는 방법에 대해서 이야기합니다. 리액트 16.3부터 Context API가 발표되어 상태관리를 편리하게 할 수 있는 방법이 생겼습니다.

React-Redux

설치

reduxreact-redux 패키지를 설치합니다.

npm install --save redux react-redux

리액트의 state와 리덕스의 state를 구분해서 이해해야 합니다. 리액트의 state는 컴포넌트의 상태를 나타내는 것이고 리덕스의 state는 스토어(store)에 의해서 관리되는 상태를 의미합니다. 두 상태는 각각의 용법에 맞게 사용되어야 합니다. 리액트의 상태는 this.state를 이용할 수 있고 리덕스의 상태는 연결된 컴포넌트의 props 형태로 대응(mapStateToProps)을 시켜서 사용할 수 있습니다.

데이터 흐름 4

리덕스의 데이터 흐름에 대해서 알아봅니다.

  1. 먼저 store.dispatch(action) 함수에 의해서 action이 store로 전달됩니다. 리액트-리덕스의 경우에는 mapDispatchToProps에 정의되어 있는 함수가 연결된 컴포넌트 props.함수()를 이용해서 불릴 때 액션이 스토어로 전달됩니다.

  2. store가 리듀서를 호출합니다. 스토어는 리듀서에게 현재의 상태 트리와 액션 두 가지 인자를 넘깁니다.

  3. 루트 리듀서가 각 리듀서의 출력을 합쳐서 하나의 상태 트리를 만듭니다.

  4. 리덕스 스토어는 루트 리듀서에 의해서 반환된 상태 트리를 저장합니다.

  5. store.subscribe(listener)를 통해 등록된 모든 리스너가 불려지고 store.getState()가 호출됩니다. 이때 react redux의 setState()가 호출됩니다.

프로바이더(provider)

리액트에서 자식으로 전달하는 것으로 props 뿐만 아니라 context도 전달할 수 있습니다. context는 한번 제공되면 자손들 모두에게 노출되서 자손들이 언제든 접근할 수 있습니다. 1 react-redux 모듈은 context를 사용하고 있습니다. react-redux의 Provider 컴포넌트는 Provider 안에 있는 모든 컴포넌트들에게 store context를 사용할 수 있게 합니다. Provider를 사용할 때는 다음과 같이 store를 props으로 전달합니다.

import { Provider } from 'react-redux';
import store from './src/store';

<Provider store={store}>
  ...
</Provider>

스토어(store)

스토어는 액션이 전달되면 리듀서를 불러 새로운 상태를 만드는 역할을 합니다.

스토어는 redux의 createStore 함수를 이용해서 만듭니다.

import { createStore } from 'redux';
import reducers from './reducers';

const store = createStore(reducers);

스토어는 아래와 같은 일들을 해야 합니다.

  • 애플리케이션의 상태를 저장합니다

  • ​getState()를 통해 상태에 접근하게 합니다.

  • ​dispatch(action)를 통해 상태를 수정할 수 있게 합니다.

  • ​subscribe(listener)를 통해 리스너를 등록합니다.

스토어는 프로바이더 props를 이용해 모든 하위 컴포넌트들에게 전달됩니다.

리듀서(reducer)

리듀서는 이전 상태와 액션을 인자로 받아 다음 상태를 반환하는 순수 함수입니다. 스토어는 자기가 관리하는 상태와 액션을 리듀서에 인자로 넘겨서 상태를 변화시킵니다. 초기 상태도 리듀서에 정의합니다.

액션

action이란 평범한 오브젝트(plain object)입니다.

{ type: 'LIKE_ARTICLE', articleId: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }

액션은 반드시 타입(type) 프로퍼티(property)를 가지고 있어야 합니다. 일반적으로 액션의 타입은 문자열 상수를 사용하고 액션의 동작을 나타냅니다.

리액트 앱은 redux를 이용해서 presentational 컴포넌트와 container 컴포넌트로 나눌 수 있습니다. 3

container 컴포넌트 구현

기술적으로 container 컴포넌트는 redux 상태 트리를 읽고 props를 presentational 컴포넌트로 전달하는 역할을 담당합니다. 2

react-redux의 connect() 함수를 이용해서 container 컴포넌트를 만들 수 있습니다.

connect는 mapStateToProps라는 함수와 mapDispatchToProps 함수를 인자로 가질 수 있습니다.

mapStateToProps는 redux store의 상태(state)를 presentational 컴포넌트의 props로 어떻게 넘길 것인지를 정의합니다. store의 상태가 바뀔 때마다 mapStateToProps가 불립니다. mapStateToProps의 반환값은 평범한 오브젝트(Object)이어야 합니다.

다음은 스토어의 상태(state)가 변경되면 연결된 컴포넌트의 props.todos로 state.todos가 전달이 됩니다.

function mapStateToProps(state) {
  return { todos: state.todos } // 앞에 있는 todos는 연결된 컴포넌트의 props이고 state는 스토어의 state입니다.
}

mapDispatchToProps는 store에 action들을 전달하는 역할을 합니다. dispatch(action)는 redux store의 함수로 이것만을 이용해서 store의 상태를 변경할 수 있습니다.

connect()의 반환값으로 고차컴포넌트(high-order component)를 반환합니다. 고차컴포넌트란 컴포넌트를 인자로 받아 새로운 컴포넌트를 만들어내는 함수입니다.

참조 사이트

1

Mastering React Native, Eric Masiello and Jacob Friedmann, packt publishing, 2017: getting started with redux

2

Redux usage with react: https://redux.js.org/basics/usage-with-react

3

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

4

리덕스 데이터 흐름: https://lunit.gitbook.io/redux-in-korean/basics/dataflow