Redux

한샘·2021년 4월 5일
0

❓Redux란?

Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다.

Redux는 여러분이 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고,
테스트하기 쉬운 앱을 작성하도록 도와줍니다. 여기에 더해서 시간여행형 디버거와 결합된 실시간 코드 수정과 같은 훌륭한 개발자 경험을 제공합니다.

🖍기본예제

import { createStore } from 'redux'

/**
 * 이것이 (state, action) => state 형태의 순수 함수인 리듀서입니다.
 * 리듀서는 액션이 어떻게 상태를 다음 상태로 변경하는지 서술합니다.
 *
 * 상태의 모양은 당신 마음대로입니다: 기본형(primitive)일수도, 배열일수도, 객체일수도,
 * 심지어 Immutable.js 자료구조일수도 있습니다.  오직 중요한 점은 상태 객체를 변경해서는 안되며,
 * 상태가 바뀐다면 새로운 객체를 반환해야 한다는 것입니다.
 *
 * 이 예제에서 우리는 `switch` 구문과 문자열을 썼지만,
 * 여러분의 프로젝트에 맞게
 * (함수 맵 같은) 다른 컨벤션을 따르셔도 좋습니다.
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 앱의 상태를 보관하는 Redux 저장소를 만듭니다.
// API로는 { subscribe, dispatch, getState }가 있습니다.
let store = createStore(counter)

// subscribe()를 이용해 상태 변화에 따라 UI가 변경되게 할 수 있습니다.
// 보통은 subscribe()를 직접 사용하기보다는 뷰 바인딩 라이브러리(예를 들어 React Redux)를 사용합니다.
// 하지만 현재 상태를 localStorage에 영속적으로 저장할 때도 편리합니다.

store.subscribe(() => console.log(store.getState())))

// 내부 상태를 변경하는 유일한 방법은 액션을 보내는 것뿐입니다.
// 액션은 직렬화할수도, 로깅할수도, 저장할수도 있으며 나중에 재실행할수도 있습니다.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

상태를 바로 변경하는 대신, 액션이라 불리는 평범한 객체를 통해 일어날 변경을 명시합니다. 그리고 각각의 액션이 전체 애플리케이션의 상태를 어떻게 변경할지 결정하는 특별한 함수인 리듀서를 작성합니다.

보통의 Redux 앱에는 하나의 루트 리듀서 함수를 가진 단 하나의 저장소가 있습니다. 앱이 커짐에 따라 루트 리듀서를 상태 트리의 서로 다른 부분에서 개별적으로 동작하는 작은 리듀서들로 나눌 수 있습니다. React 앱을 하나의 루트 컴포넌트에서 시작해서 여러 작은 컴포넌트의 조합으로 바꾸는 것과 동일합니다.

Redux pattern

Actions : 액션은 애플리케이션에서 저장소로 보내는 데이터 묶음입니다. 이들이 저장소의 유일한 정보원이 됩니다. 여러분은 store.dispatch()를 통해 이들을 보낼 수 있습니다.
액션은 평범한 자바스크립트 객체입니다. 액션은 반드시 어떤 형태의 액션이 실행될지 나타내는 type 속성을 가져야 합니다. 타입은 일반적으로 문자열 상수로 정의됩니다. 여러분의 앱이 충분히 커지면 타입들을 별도의 모듈로 분리할수도 있습니다.

const ADD_TODO = 'ADD_TODO'

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

리듀서(Reducer) : 액션은 무언가 일어난다는 사실을 기술하지만, 그 결과 애플리케이션의 상태가 어떻게 바뀌는지는 특정하지 않습니다. 이것은 리듀서가 할 일이죠.

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
}
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

저장소(Store) : 이전 섹션에서 우리는 "무엇이 일어날지"를 나타내는 액션과 이 액션에 따라 상태를 수정하는 리듀서를 정의했습니다.

저장소는 이들을 함께 가져오는 객체입니다. 저장소는 아래와 같은 일들을 해야 합니다:

애플리케이션의 상태를 저장하고;
getState()를 통해 상태에 접근하게 하고;
dispatch(action)를 통해 상태를 수정할 수 있게 하고;
subscribe(listener)를 통해 리스너를 등록합니다.

리듀서를 만들었다면 저장소를 만드는건 쉽습니다. 이전 섹션에서 우리는 combineReducers()를 통해 여러 리듀서를 하나로 합쳤습니다. 우리는 이것을 가져와서 createStore()에 넘길겁니다.

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

let store = createStore(todoApp)

createStore()의 두번째 인수로 초기 상태를 지정해줄수도 있습니다. 이는 서버에서 실행중인 Redux 애플리케이션의 상태와 일치하도록 클라이언트의 상태를 채워줄때 유용합니다.

let store = createStore(todoApp, window.STATE_FROM_SERVER)
import { createStore } from 'redux'
import todoApp from './reducers'

let store = createStore(todoApp)

Redux 데이터 흐름

1.여러분이 store.dispatch(action)를 호출합니다.
```js
{ type: 'LIKE_ARTICLE', articleId: 42 };
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Megan' } };
{ type: 'ADD_TODO', text: 'Read the Redux docs.'};
2.Redux 저장소가 여러분이 지정한 리듀서 함수들을 호출합니다.
```js
// 애플리케이션의 현재 상태(할일 목록과 선택된 필터)
let previousState = {
  visibleTodoFilter: 'SHOW_ALL',
  todos: [{
    text: 'Read the docs.',
    complete: false
  }]
};

// 실행되는 액션(할일 추가)
let action = {
  type: 'ADD_TODO',
  text: 'Understand the flow.'
};

// 리듀서가 다음 상태를 반환함
let nextState = todoApp(previousState, action);


리듀서는 단지 다음 상태를 **계산**하는 순수 함수라는 점을 기억하세요. 리듀서는 완전히 예측 가능해야 합니다: 같은 입력을 가지고 몇번을 호출하든지 같은 출력이 나와야 합니다. API 호출이나 라우터 전환같은 사이드이펙트를 일으켜서는 안됩니다. 이런 일들은 액션이 전달되기 전에 행해져야 합니다.
3.루트 리듀서가 각 리듀서의 출력을 합쳐서 하나의 상태 트리로 만듭니다.

루트 리듀서를 어떻게 구성하는지는 완전히 여러분에게 달렸습니다. Redux는 루트 리듀서를 각각이 상태 트리의 가지 하나씩을 다루는 함수들로 나눌 수 있도록 combineReducers() 헬퍼 함수를 제공합니다.

combineReducers()의 작동 방식은 아래와 같습니다. 여러분이 두 개의 리듀서를 가지고 있다고 합시다. 하나는 할일 목록을 위한 것이고, 하나는 선택된 필터 설정을 위한 것입니다:

```js
function todos(state = [], action) {
  // Somehow calculate it...
  return nextState;
}

function visibleTodoFilter(state = 'SHOW_ALL', action) {
  // Somehow calculate it...
  return nextState;
}

let todoApp = combineReducers({
  todos,
  visibleTodoFilter
});

여러분이 액션을 보내면, combineReducers가 반환한 todoApp은 두 리듀서를 모두 호출합니다:

```js
let nextTodos = todos(state.todos, action);
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action);

그리고 두 결과를 합쳐서 하나의 상태 트리로 만듭니다:

```js
return {
  todos: nextTodos,
  visibleTodoFilter: nextVisibleTodoFilter
};
4.Redux 저장소가 루트 리듀서에 의해 반환된 상태 트리를 저장합니다.

이 새 트리가 여러분의 앱의 다음 상태입니다! store.subscribe(listener)를 통해 등록된 모든 리스너가 불러내지고 이들은 현재 상태를 얻기 위해 store.getState()를 호출할겁니다.

이제 새로운 상태를 반영하여 UI가 변경될겁니다. 여러분이 React Redux으로 바인딩을 했다면, 이 시점에 component.setState(newState)가 호출됩니다.

profile
developer

0개의 댓글