본문 바로가기

React

[ React ] React Redux API (with 카운터 예제)

반응형

 

Redux는 상태 관리 라이브러리다. 리액트 빌트인 훅에서 useReducer와 useContext가 합쳐진 느낌이다. 따라서 useReducer와 useContext를 잘 이해했다면 React Redux도 어렵지 않게 이해할 수 있다. Redux의 핵심은 세 가지로 축약할 수 있다. 

 

1. 상태는 readOnly다.
2. reducer는 순수 함수여야 한다.
3. 하나의 애플리케이션에는 하나의 store가 있다.

 

상태가 readOnly라는 뜻은 상태 변경시 state에 직접적인 변화를 주는 것이 아니라, 기존의 state로 새로운 state를 생성하는 것을 의미한다. 이는 리액트에서 setState를 사용할때와 동일하다. 예를 들어 상태값이 배열 또는 객체라면, 상태를 업데이트 하려면 spread 연산자로 새로운 배열 또는 객체를 반환해줘야 한다. 이렇게 불변성을 유지해줘야 하는 이유는 재부적으로 데이터가 변경 되는 것을 쉽게 감지하기 위함이라고 한다. 

 

reducer는 이전 상태와, 액션 객체를 파라미터로 받는다. reducer가 순수 함수여야 한다는 뜻은 똑같은 파라미터로 호출된 reducer는 언제나 똑같은 결과값을 반환해야 한다는 뜻이다. Date 객체를 사용하거나 네트워크 요청을 하는 것과 같이 실행 할 때마다 다른 결과가 나타나는 로직들은 reducer 함수 바깥에서 처리해줘야 한다. 

 

리덕스의 store란 '상태 저장소'를 뜻한다. 리덕스에서는 하나의 상태 저장소로 모든 상태를 관리한다. 상태 저장소를 생성하기 위해서는 'createStore'라는 함수를 import 해야한다. 이제 createStore를 시작으로 Redux API를 파헤쳐보자. 

 

createStore(reducer, initialState)

  • 앱의 상태 트리 전체를 보관하는 Redux 저장소를 만든다.
  • 앱 내에는 단 하나의 저장소만 있어야 한다. 

[인수]

  • reducer (Function) : 현재 상태 트라와 action으로 다음 상태 트리를 반환하는 함수. 필수 인자다.
  • initialState (any) : 초기 상태. 선택 옵션.

[반환]

  • store : 앱의 전체 상태를 가지고 있는 객체. 오직 action을 통해서만 객체의 상태값을 바꿀 수 있다. 
import { createStore } from 'redux'

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text])
    default:
      return state
  }
}

let store = createStore(todos, ['Use Redux'])

store.dispatch({
  type: 'ADD_TODO',
  text: 'Read the docs'
})

console.log(store.getState())
// [ 'Use Redux', 'Read the docs' ]

 

Provider

<Provider>는 중첩된 하위 컴포넌트들이 Redux store에 접근할 수 있도록 해준다. <Provider> 하위 컴포넌트들은 React-Redux가 제공하는 Hooks API를 사용할 수 있다. 일반적 형태는 아래와 같다. 

 

import { Provider } from 'react-redux'

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

 

여기서 store는 createStore로 생성한 앱 내의 상태 트리다. 즉, App을 포함한 하위 컴포넌트들이 상태 트리에 접근할 수 있도록 한다.

 

useSelector(selector)

const result: any = useSelector(selector: Function)

- selector 함수를 통해 Redux store에서 상태를 추출해준다. 

- action이 dispatch 되면 useSelector()는 이전의 selector 결과와 비교해서 다른점이 있으면 re-render를 해준다. 

- useSelector는 독립적으로 Redux store를 구독한다. 

- selector 함수의 첫번째 인자는 상태 저장소 객체다.

- selector 함수의 반환값이 result에 저장된다. 

 

기본 형태

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector((state) => state.counter)
  return <div>{counter}</div>
}

 

props로 어떤 것을 추출할지 정할 수 있다.

import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = (props) => {
  const todo = useSelector((state) => state.todos[props.id])
  return <div>{todo.text}</div>
}

 

useDispatch()

const dispatch = useDispatch()

- Redux store 에서 dispatch에 대한 참조를 반환한다. 

- 컴포넌트 내부에서 dispatch를 쓸 일이 있을때 이 훅을 사용하면 된다. 

 

기본 형태

import React from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

 

useCallback으로 최적화

import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()
  const incrementCounter = useCallback(
    () => dispatch({ type: 'increment-counter' }),
    [dispatch]
  )

  return (
    <div>
      <span>{value}</span>
      <MyIncrementButton onIncrement={incrementCounter} />
    </div>
  )
}

 

정리

React Redux를 사용할 때, 일반적으로 store.jsx에 상태 저장소를 생성하고, reducer와 action creator도 함께 정의한다. 그리고 앱의 최상위 컴포넌트를 import 해서 Provider로 감싸준다. action creator는 코드의 가독성을 높이기 위해 action 객체를 함수화 하는 일종의 리팩토링 과정으로 생각하면 된다. 

 

store.jsx에서 store, reducer, action creator를 정의해주고 최상위 컴포넌트를 Provider로 감싼 다음에, 다른 하위 컴포넌트에서는 useDispatch와 useSelector로 store를 구독하기만 하면 된다. 

 

일반적인 플로우는 아래와 같다. 

// store.jsx
initialState 정의
reducer 정의
action creator 정의
Provider로 최상위 컴포넌트 감싸기

// Component.jsx
useSelector 호출
useDispatch 호출

 

카운터 예제

store.jsx와 Counter.jsx를 중점으로 보면 된다. 

 

 

 

 

 

<참조>

https://react-redux.js.org/api/hooks    

https://react.vlpt.us/redux/01-keywords.html   

 

 

반응형