Redux를 정리해보자!

dwell·2022년 3월 14일

Redux란 ?


Redux is a pattern and library for managing and updating application state, using events called "actions".

→ 직역하자면, 리덕스는 action이라는 이벤트를 통해 state를 관리 및 업데이트하는 라이브러리이자 패턴이다.

Redux를 사용하는 이유?


The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.

  • 앱 전체에 걸쳐서 사용되는 global state을 관리하기 편함 → 전역상태라는 것에 유의하자!!
  • redux를 사용함으로써 state가 언제 (when), 어떻게 (how), 어디서 (where), 왜 (why) 업데이트 되었는지 확인이 가능하다.
  • 따라서, 코드가 어떤 결과를 불러일으킬지 예상이 가능하고 테스팅에 용이하다.

Redux는 언제 사용해야 하는가?


Redux is more useful when:

  • You have large amounts of application state that are needed in many places in the app
  • The app state is updated frequently over time
  • The logic to update that state may be complex
  • The app has a medium or large-sized codebase, and might be worked on by many people

Not all apps need Redux. Take some time to think about the kind of app you're building, and decide what tools would be best to help solve the problems you're working on.

  • 규모가 큰 프로젝트
  • 관리해야할 state가 많고 업데이트 로직이 복잡한 경우
  • state가 업데이트가 자주 일어나는 경우

→ 무조건적으로 redux를 사용해야하는 것은 아니다. redux를 사용하면 코드가 많아지고 (boilerplate code가 많음) redux에 관한 지식도 배워야하기 때문에 기회비용이 있다. 하지만 규모가 큰 프로젝트라면 redux를 사용한 state management를 고려해보는 것도 좋다.

Core Concepts


🔷 one way data flow (단방향 데이터 흐름)

state → view → action → state

  • State – app에서 사용되는 current state

  • View – UI. 사용자한테 보여지는 화면

  • Actions – state를 변경하는 event

  • 다음과 같은 과정이 계속 반복되며 이를 one-way-data-flow라고 한다.

    • state가 현재 앱에서 사용되는 data를 보관한다.
    • current state를 view에서 렌더링해서 보여준다.
    • action이 발생하면 이를 store로 dispatch(보내다) 한다.
    • store은 보내진 action (type,payload)을 토대로 기존의 state를 reducer 함수를 이용하여 newState를 만든다.
    • 그리고 새로 만들어진 newState는 다시 view에 보여진다.

🔷 Terminology

  • action : 발생한 이벤트에 대한 정보를 담고있는 자바스크립트 객체이다.
    • type : 발생한 이벤트 이름, 필수 → 주로 "domain(state)/eventName” 형식으로 작성

    • payload : 이벤트에 대한 추가 정보 (추가 / 수정 / 삭제 데이터 등..), 선택

      const addTodoAction = {
        type: 'todos/todoAdded',
        payload: 'Buy milk'
      }
  • action creator : 단순히 action 객체를 return 해주는 순수 함수이다.
    • action creator은 필수는 아니지만 효율성을 증대시켜준다.
      - 반복된 action객체를 손수 작성하는 것보다 함수(action creator)를 호출하는 것이 간단하다.
      - type에 들어가는 string을 잘못 작성하거나 type,payload property를 잘못 작성하는 실수를 줄일 수 있다.

      const addTodo = text => {
        return {
          type: 'todos/todoAdded',
          payload: text
        }
      }
  • reducer : state, action을 받아서 알맞은 로직을 실행 후 newState를 반환하는 순수 함수이다.
    • (state, action) ⇒ newState

    • 3가지 원칙을 반드시 준수해야 한다.

      1. state와 action을 통해서만 newState를 도출해야 한다.

      2. 현재 state를 직접 수정하는 것이 아닌 복사본을 만들어서 복사본을 수정해야 한다.

        → immutable update !! (array, object의 경우 spread operator를 사용한다)

      3. asynchronous (비동기), 랜덤 value 생성, 다른 side effect를 발생시키는 로직은 금지

    • 주로 switch 구문으로 작성.

      → 하지만 이것은 필수가 아니다. if/else, loop 등 원하는 것을 사용하여 구현 가능하다.
      const initialState = { value: 0 }
      
      function counterReducer(state = initialState, action) {
        // Check to see if the reducer cares about this action
        switch(action.type) {
      		case 'counter/increment' : 
      	    // If so, make a copy of `state`
      			return {
      	      ...state,
      	      // and update the copy with the new value
      		    value: state.value + 1
      	    }
      		default:
      			// otherwise return the existing state unchanged
      		  return state
      	}
      }
  • store : global state를 보관하고 관리하는 저장소. 앱에는 하나의 store만 존재해야 한다.

  • dispatch : 발생한 action 객체를 store로 보내는 역할을 하는 api.

    • state를 업데이트할 수 있는 유일한 방법이다.

    • reducer을 통해 반환된 newState는 다시 store에 보관된다.

    • action creator가 있는 경우 action을 대신해서 사용한다.

      // action creator가 없는 경우
      store.dispatch({ type: 'counter/increment' })
      // action creator가 없는 경우
      store.dispatch({ type: 'counter/increment' })
      
      // action creator가 있는 경우
      const increment = () => {
        return {
          type: 'counter/increment'
        }
      }
      
      store.dispatch(increment())
  • selectors : store에서 필요한 state만 추출하는 함수.
    • 앱의 규모가 커지고 관리하는 state가 많아졌을 때 유용.

      const selectCounterValue = state => state.value

Core Redux API


🔷 createStore

  • store을 생성한 후 이를 반환해주는 함수. 유일한 인자는 reducer 이다.
const store = createStore(countUpReducer);

🔷 getState

  • currentState를 반환하는 함수이다.
  • store안의 state에 접근할 수 있는 유일한 방법이다.
  • store안의 state를 직접 변경할 수 없다.
store.getState();

🔷 dispatch

  • 유일한 인자는 action이다. (action creator가 있는 경우 대신 사용 가능).
  • reducer이 실행될 때 필요한 state는 getState를 통해서 가져오게 됨. (첫번째 인자가 state임)
const actionObj = {type: 'count/increment'}
const actionCreatorFunc = () => {type: 'count/increment'}

store.dispatch(actionObj);    
store.dispatch(actionCreatorFunc());

 
reducer(store.getState(), actionObj)가 자동으로 실행됨.

🔷 subscribe

  • listener를 인자로 갖는다 → store.subscribe(listener) 형태
  • listener : state가 변경될 때마다 (dispatch가 실행될 때마다) 실행되는 함수이다. 주로 getState를 이용해서 변경한 뒤의 state를 확인하려고 사용.
  • state가 변경될 때마다 실행할 함수를 구독하는 api라고 생각하면 쉽다.
  • unsubscribe 함수를 반환한다. 이 함수를 사용해서 구독을 취소할 수 있다. 구독을 취소하면 dispatch 후에 listener가 더 이상 실행되지 않는다.
  • 여러개의 listener를 subscribe 할 수 있다. lisetener마다 subscribe를 해줘야 한다.
const printCurrentState = () => {
  const state = store.getState()
  console.log(`state: ${state}`);
}

const unSubscribe = store.subscribe(printCurrentState);
unSubscribe()

🔷 combineReducers

  • slice = 각각의 state

    • state가 많아지면 state를 관리하고 업데이트하는 것이 복잡해진다.
    • slice를 기준으로 파일을 쪼갤 수 있다.
    • 각 파일은 해당 slice에 관련된 initialState, action, action creator, reducer, selector를 선언한다.
  • sliceReducer

    • slice (각각의 state)에 해당하는 reducer을 의미한다.
    • sliceReducer에서는 전체 state가 아닌 해당 slice만을 관리하면 된다.
    • sliceReducer의 인자로 들어오는 state 또한 전체 state가 아닌 slice의 state이다.
  • combineReducer

    • createStore의 인자로 reducer을 넣어줘야 하는데 sliceReducer이 생기면서 여러개의 reducer로 쪼개졌다. 이를 하나로 합쳐주는 것이 combineReducer이다.

    • combineReducer의 인자에는 slice에 해당하는 sliceReducer이 정의된 객체이며 rootReducer을 반환한다.

      const rootReducer = combineReducers({
        todos: todosReducer, 
        filter: filterReducer
      })
      
      const store = createStore(rootReducer);

  • components : stateless component (state를 렌더하는 UI)
  • features : stateful component (state를 관리하고 업데이하는 로직이 들어있음)
    • slice : 각 feature에 해당하는 slice를 컴포넌트와 같은 폴더에 두기
src/
|-- index.js
|-- app/
    |-- App.js (+)
    |-- store.js
|-- components/
    |-- FavoriteButton.js (+)
    |-- Recipe.js (+)
|-- features/
    |-- allRecipes/
        |-- AllRecipes.js (+)
        |-- allRecipesSlice.js
    |-- favoriteRecipes/
        |-- FavoriteRecipes.js (+)
        |-- favoriteRecipesSlice.js
    |-- searchTerm/
        |-- SearchTerm.js (+)
        |-- searchTermSlice.js

Example Code


🔷 redux + vanilla js

  • render 함수를 subscribe 해야지 state가 업데이트될 때 UI도 같이 업데이트가 된다.

https://codesandbox.io/embed/redux-vanilla-js-q6flod?fontsize=14&hidenavigation=1&theme=dark

🔷 redux + react

  • ReactDOM.render 함수를 subscribe 해줘야 UI가 업데이트 된다.

https://codesandbox.io/embed/react-redux-counter-4f51t6?fontsize=14&hidenavigation=1&theme=dark

profile
code for a change

0개의 댓글