티스토리 뷰

이번 장에서는 리덕스를 사용하기 위해 알아야하는 용어와 개념에 대해 알아보겠습니다

 

state 관리

간단한 react counter 컴포넌트를 살벼보겠습니다. 버튼을 누르면 컴포넌트의 state가 증가합니다

 

function Counter() {
  // State: a counter value
  const [counter, setCounter] = useState(0)

  // Action: code that causes an update to the state when something happens
  const increment = () => {
    setCounter(prevCounter => prevCounter + 1)
  }

  // View: the UI definition
  return (
    <div>
      Value: {counter} <button onClick={increment}>Increment</button>
    </div>
  )
}

코드에 state view action이 있습니다.

 - state는 앱을 작동 시키는 핵심입니다.

 - view는 UI를 정의하고 현재 state를 보여줍니다.

 - action은 state를 사용자의 입력을 기반으로 발생하는 이벤트 및 트리거로 업데이트 합니다.

 

다음은 단방향 데이터 흐름에 대한 설명입니다.

 - state는 특정 시점에 앱의 상태를 나타냅니다

 - UI는 state를 렌더합니다

 - 사용자가 버튼을 클릭하는 등 어떠한 일이 발생하면 state가 업데이트 됩니다

 - UI는 새로운 state(업데이트 된 state)로 리렌더링 됩니다.

출처: https://ko.redux.js.org/tutorials/essentials/part-1-overview-concepts/

그러나 여러 컴포넌트가 같은 state를 공유해야 할 필요가 있을 때 이런 단방향 데이터 흐름을 관리하는 것은 힘들 수 있습니다. 특히 여러 컴포넌트가 애플리케이션의 다른 부분에 있을 경우에 더욱 힘들어 집니다. "lifting state up" 을 사용해서 해결할 수도 있지만 항상 도움이 되는 것은 아닙니다.

 

이를 해결하기 위한 한가지 방법은 컴포넌트에서 state를 추출하여 컴포넌트 트리 외부에 있는 중심 위치에 넣으면 됩니다. 이렇게 하면 콤포넌트 트리는 큰 view가 되고, 모든 컴포넌트는 트리의 어디에 위치하든 state를 가지고 오거나 변경할 수 있게 됩니다.

 

state를 분리하고 view와 state의 독립성을 유지하는 규칙을 적용함으로써 코드에 더 많은 구조를 사용해도 유지관리를 할 수 있습니다

 

이것이 redux의 기본 아이디어입니다. 애플리케이션 안에 있는 하나의 중앙 장소에 어디에서든 사용할 수 있는 state가 있고, state가 업데이트 될 때 어떻게 업데이트가 되는지 알 수 있는 코드를 만들기 위한 특정 패턴입니다.

 

불변성

자바스크립트 오브젝트와 배열은 기본적으로 모두 변경이 가능합니다. 배열과 오브젝트를 생성하면 안에 내용을 바꿀 수 있습니다.

const obj = { a: 1, b: 2 }
// 외부는 여전히 동일한 객체지만 내부는 변경됩니다
obj.b = 3

const arr = ['a', 'b']
// 배열도 마찬가지 입니다
arr.push('c')
arr[1] = 'd'

여기서 중요한 것은 내부의 값이 변경되어도 같은 메모리 값을 참조하고 있기 때문에 내용은 바뀌었지만 외부에서 볼때는 내용이 바뀌기 전과 후가 같다고 인식을 합니다. 이를 오브젝트와 배열의 mutating이라고 합니다.

 

따라서 immutably(불변성)을 지키기 위해서 업데이트 할 때 기존의 배열/오브젝트를 복사한 후 복사된 사본을 수정해야합니다.

 

자바스크립트의 배열/오브젝트 spread 연산자를 사용하거나, 기존 배열은 변하지 않고, 복사된 새로운 배열을 리턴하는 자바스크립트 배열의 내장 메소드를 사용해서 불변성을 지킬 수 있습니다.

 

const obj = {
  a: {
    // 안전하게 obj.a.c를 업데이트 하려면 각각의 부분을 복사해야 합니다
    c: 3
  },
  b: 2
}

const obj2 = {
  // copy obj
  ...obj,
  // overwrite a
  a: {
    // copy obj.a
    ...obj.a,
    // overwrite c
    c: 42
  }
}

const arr = ['a', 'b']
// 끝에 'c'가 추가 된 새로운 배열을 만듭니다
const arr2 = arr.concat('c')

// 또는 기존의 배열 복사본을 만든 다음
const arr3 = arr.slice()
// 복사본을 변경합니다
arr3.push('c')

 

redux는 모든 state가 immutably(불변성, 변경되지 않을)할 것 이라고 예측합니다. 나중에 불변성을 지키는 것이 어디에서 어떻게 중요한지 알 수 있습니다.

 

더 자세히 알고 싶다면 아래 링크를 참조하세요 

용어

계속하기전에 리덕스에서는 알아야 할 몇가지 중요한 단어가 있습니다.

 

Actions

action은 type 이라는 키 값을 가지고 있는 자바스크립트 오브젝트입니다. 애플리케이션에서 어떠한 이벤트가 일어날지에 대한 설명이라고 볼 수 있습니다.

 

예를 들어 "todos/todoAdded"처럼 type은 action에 대한 설명을 하는 문자열이어야 합니다. "domain/eventName"처럼 보통 앞에는 기능 또는 카테고리가 들어가고 뒤에는 어떤 일이 발생 할 지에 대해 적습니다.

 

action은 발생하는 이벤트에 대해서 state를 업데이트 할 때 추가적으로 필요한 정보가 있을 수 있습니다. 이런 정보를 payload라고 합니다.

 

일반적인 action은 다음과 같습니다

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

Action Creators

action 생성자는 action 객체를 리턴하는 함수입니다. action 생성자를 사용하게 되면 코드를 작성할 때마다 일일이 action을 적지 않아도 됩니다.

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}

Reducers

reducer는 현재 state와 action 객체를 받는 함수입니다. state를 어떻게 업데이트할지 정하고, 정해진 방법대로 코드를 적용한 새로운 state를 반환하기 위해 사용합니다 : (state, action) => newState

 

reducer는 받은 action(이벤트) type을 기반으로 이벤트를 처리하는 이벤트 리스너라고 할 수 있습니다.

 

reducer는 항상 몇 가지 특정 규칙을 지켜야합니다

 - 기존 state 및 action을 사용해서 새로운 state값을 계산해서 만들어야 합니다.

 - state를 직접 수정 할 수 없습니다. 기존 state를 deep copy사하고 복사 된 값을 변경하는 방법으로 업데이트 해야 합니다.

 - 비동기 작업을 수행하거나 임의의 값을 생성하는 등 기타 side effect를 일으키지 않아야 합니다.

 

reducer의 규칙이 중요한 이유와 올바른 방법은 추후에 더 자세히 설명하겠습니다.

 

reducer 함수 내부는 일반적으로 동일한 단계를 수행합니다.

  • reducer 안에 action에 대한 처리를 하는 로직이 있는지 확인합니다.
    • 해당하는 action이 있으면 state의 복사본을 만들고, 복사본에 새로운 값을 업데이트 한 다음 리턴하면 됩니다.
  • action이 없다면 기존 state를 변경하지 않고 반환합니다.

 

reducer가 따라야하는 규칙을 보여주는 작은 reducer의 예시입니다

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // reducer가 action에 대한 처리를 하는지 확인
  if (action.type === 'counter/increment') {
    // 있다면 state의 복사본을 만듭니다
    return {
      ...state,
      // 새로운 값으로 사본을 업데이트 합니다
      value: state.value + 1
    }
  }
  // 없다면 기존 state를 변경하지 않고 반환합니다.
  return state
}

reducer안에 action에 대한 처리는 if/else, switch 등등 모든 종류를 사용할 수 있습니다.

Store

애플리케이션의 현재 state는 redux의 store라는 객체에 있습니다.

store는 redux toolkit의 configureStore 메소드를 사용해서 생성되며 인자로 reducer를 넣어줍니다. getState 매서드를 통해 현재 state 값을 가져올 수 있습니다.

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

Dispatch

redux store에는 dispatch라는 메소드가 있습니다. state를 업데이트하는 유일한 방법은 store.dispatch()를 사용해서 action객체를 전달하는 것 입니다. store는 reducer 함수를 통해서 전달하는 action에 따라 실행하고 state를 업데이트 합니다. 업데이트 된 후 getState()를 실행하면 업데이트 된 state를 확인 할 수 있습니다.

store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}

action을 전달하는 것은 애플리케이션에서 이벤트 트리거를 하는 것이라고 볼 수 있습니다. 어떠한 이벤트가 발생했을 때 store는 action을 전달받고, state를 업데이트합니다.

 

action을 전달할 때에는 action생성자를 호출하여 전달합니다

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}

Selectors

selector는 store state에서 특정 값을 추출하는 방식의 함수입니다. 애플리케이션이 커지면서 앱의 다른 컴포넌트가 같은 데이터를 사용할 때 반복되는 로직을 방지 할 수 있습니다.

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

Redux 애플리케이션 Data Flow

앞부분에 앱을 업데이트하는 단방향 데이터 흐름에 대해 설명했습니다. 간단하게 다시 알아보면

 - state는 특정 시점의 앱 상태를 가지고 있습니다.

 - UI는 해당 state에 따라 렌더링 됩니다

 - 사용자가 버튼을 클릭하는 등 어떤 이벤트가 발생하면 발생한 이벤트에 따라 state가 업데이트 됩니다.

 - 새로운 state에 따라 UI가 다시 렌더링 됩니다

 

Redux는 다음 단계로 더 자세히 나눌 수 있습니다.

  • 초기 설정
    • redux store는 root reducer를 사용하여 생성합니다.
    • store는 root reducer를 한번 호출해서 초기 state 값을 저장합니다.
    • UI가 처음 렌더링 될 때 UI 컴포넌트는 redux store의 현재 state에 접근하고 해당 데이터를 사용하여 렌더링 할 항목을 결정합니다. 또한 state가 변경되었는지 알 수 있도록 store 구독합니다.
  • 업데이트
    • 사용자가 버튼을 클릭하는 등 앱에서 이벤트가 발생합니다.
    • redux store에 action을 dispatch하는 코드는 dispatch({ type: 'counter/increment' }) 와 같이 쓸 수 있습니다.
    • store는 이전 state와 전달 받은 action으로 reducer 함수를 실행하고, 반환 값을 새로운 state로 저장합니다.
    • store를 구독하고 있는 UI 컴포넌트에 store가 업데이트 되었음을 알립니다.
    • store의 data를 사용해야하는 UI 컴포넌트는 사용하고 있는 state가 변경되었는지 확인합니다.
    • data가 변경된 컴포넌트는 새로운 data로 리렌더링 되어 화면에 표시되는 내용을 업데이트 할 수 있습니다.

시각적으로 보면 다음과 같습니다

출처: https://ko.redux.js.org/tutorials/essentials/part-1-overview-concepts/

지금까지의 내용 요약

redux는 애플리케이션의  state를 전역에서 관리하기 위한 라이브러리 입니다.

  • redux는 일반적으로 redux와 react를 함께 사용하기 위해 react-redux 라이브러리를 사용합니다
  • redux tookit은 redux로직을 작성하는데 권장되는 방법입니다

 

redux는 단방향 데이터 흐름 구조를 사용합니다

  • state는 특정 시점의 앱 상태를 나타내고 UI는 해당 상태를 기반으로 렌더링 됩니다.
  • 앱에서 이벤트가 발생할 때
    • UI가 액션을 전달합니다.
    • store는 reducer를 실행하고 발생한 이벤트에 따라 state가 업데이트 됩니다.
    • store는 state가 변경되었음을 UI에 알립니다.
  • 새로운 state에 따라 UI가 다시 렌더링 됩니다.

 

redux는 여러 유형의 코드를 사용합니다

  • action은 type이라는 값을 가지고 있는 일반 객체이며, 앱에서 어떤 이벤트가 발생하는지를 설명합니다.
  • reducer는 이전 state + action을 기반으로 새로운 state의 값을 계산하는 함수입니다.
  • redux store는 action이 dispatch될 때마다 root reducer를 실행합니다.

 

다음 파트는 redux 앱 구조에 대해 알아보겠습니다


3탄 - redux app 구조

 

redux 강의 기본 핵심 튜토리얼 3탄 - redux app 구조

이전 포스트에서 redux의 용어와 개념에 대해서 설명했습니다. 이번 포스트에서는 실제 코드 예제를 살펴보고 어떻게 동작이 되는지 살펴 보겠습니다. 카운터 앱 예제 살펴볼 샘플 프로젝트는 버

jcon.tistory.com

 

반응형
댓글