React

[Redux] Redux 기초

Alexim 2022. 7. 31. 18:06

dispatch, store, action.. 뭔가 많아 보이고 복잡해보여서 그렇지 정말 기초는 간단한 것 같다.

1. action은 이벤트를 설명하는 이름인 타입을 가지고 있는 "객체" 이고

2. dispatch(action) 을 호출하면 (dispatch는 이벤트 트리거라고 할 수 있다.)

3. reducer에 이전 state와 action이 담겨 실행되면서 action의 타입과 맞는 부분이 실행되고 새로운 state를 내뱉는다.

그리고 store는 dispatch, getState, subscribe 메서드가 담겨있는 객체일 뿐이다.

 

오늘 다른 사람의 코드를 봤을 때 내가 공부했던 것과 달라보여서 너무 혼란스러웠는데 알고보니 thunk 미들웨어 때문이었다. 

(action 생성자 함수가 type만을 가진 객체를 내뱉지 않고 여러가지 작업을 하고 있다면 thunk를 사용한 것..!  미들웨어를 사용하지 않으면 action의 역할이 한정적일 수 있다고 한다.)

Redux

Redux는 작은 독립형 JS 라이브러리이다. 일반적으로 여러 다른 패키지와 함께 사용된다.

단방향 데이터 흐름 앱 구조를 사용한다. 

 

 

Store

모든 Redux 애플리케이션의 중심

애플리케이션의 전역 상태를 보관하는 컨테이너. 

store는 reducer를 전달하며 생성된다. redux 애플리케이션 상태가 담겨 있고 앱의 전체 상태를 가지고 있는 객체이다.

이 객체의 상태를 바꾸는 유일한 방법은 액션을 보내는 것 뿐이다.

 

Actions

액션은 "type" 필드를 가지고 있는 plain JavaScript 객체이다.

애플리케이션에서 발생한 일을 설명하는 "EVENT"로 생각할 수 있다.

액션 객체는 필수적으로 "type"을 가져야 한다.

 

Reducer

현재 state 및 action 객체를 수신하고 필요한 경우 상태를 업데이트하는 방법을 결정하고 새 상태를 반환하는 함수이다.

즉 업데이트 로직을 정의하는 함수이다. 수신된 액션(이벤트) 유형에 따라 이벤트를 처리하는 이벤트 리스너로 생각할 수 있다.

2개의 파라미터를 받는다. (1. state(현재 상태), 2. action(액션 객체)) (state, action) => newState

- 특정 규칙

- state 및 action 인수를 기반으로 새 상태 값만 계산해야 한다.

- 기존 state를 수정할 수 없다. 대신 기존 값을 복사하고 복사된 값을 업데이트하여 늘 불변하게 변경하여야 한다.

- side effect가 있어서는 안된다. 즉 순수한 함수여야 한다. side effect가 있어야 한다면 미들웨어를 사용해야 한다.

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}

Store Methods

스토어는 몇 가지 메소드를 가지고 있는 객체일 뿐이다.

 

1. getState()

2. dispatch(action)

3. subscribe(listener)

4. replaceReducer(nextReducer)


getState()

애플리케이션의 현재 state를 반환한다.

store의 reducer가 반환한 마지막 값과 같다.

 

dispatch(action

디스패치 작업은 애플리케이션에서 "이벤트 트리거"로 생각할 수 있다. state를 변경할 수 있는 유일한 방법이다.

action을 파라미터로 받고 dispatch를 호출하면 store는 reducer를 실행한다.

store.dispatch(action)

 

subscribe(listener)

변경사항에 대한 리스너를 추가함.

state 값이 변하는지 감시하는 역할이라고 생각하면 될듯..?

subscribe에 listener를 전달해주면 액션이 디스패치 되었을 때 마다 전달해준 리스너가 호출된다. 

(react-redux를 사용하면 subscribe를 직접 할 필요가 없다.)

 


Redux Counter 예제

전체코드 -> https://codesandbox.io/s/redux-fundamentals-core-example-lr7k1?from-embed 

 

<!DOCTYPE html>
<html>
  <head>
    <title>Redux basic example</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
  </head>
  <body>
    <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>
    <script>
      // Define an initial state value for the app
      const initialState = {
        value: 0
      };

      // Create a "reducer" function that determines what the new state
      // should be when something happens in the app
      function counterReducer(state = initialState, action) {
        // Reducers usually look at the type of action that happened
        // to decide how to update the state
        switch (action.type) {
          case "counter/incremented":
            return { ...state, value: state.value + 1 };
          case "counter/decremented":
            return { ...state, value: state.value - 1 };
          default:
            // If the reducer doesn't care about this action type,
            // return the existing state unchanged
            return state;
        }
      }

      // Create a new Redux store with the `createStore` function,
      // and use the `counterReducer` for the update logic
      const store = Redux.createStore(counterReducer);

      // Our "user interface" is some text in a single HTML element
      const valueEl = document.getElementById("value");

      // Whenever the store state changes, update the UI by
      // reading the latest store state and showing new data
      function render() {
        const state = store.getState();
        valueEl.innerHTML = state.value.toString();
      }

      // Update the UI with the initial data
      render();
      // And subscribe to redraw whenever the data changes in the future
      store.subscribe(render);

      // Handle user inputs by "dispatching" action objects,
      // which should describe "what happened" in the app
      document
        .getElementById("increment")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/incremented" });
        });

      document
        .getElementById("decrement")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/decremented" });
        });

      document
        .getElementById("incrementIfOdd")
        .addEventListener("click", function () {
          // We can write logic to decide what to do based on the state
          if (store.getState().value % 2 !== 0) {
            store.dispatch({ type: "counter/incremented" });
          }
        });

      document
        .getElementById("incrementAsync")
        .addEventListener("click", function () {
          // We can also write async logic that interacts with the store
          setTimeout(function () {
            store.dispatch({ type: "counter/incremented" });
          }, 1000);
        });
    </script>
  </body>
</html>

 

1. 초기 state값 정의하기

const initialState = {
  value: 0
}

 

2. reducer 함수 정의하기.

reducer는 state와 action라는 두 개의 인수를 받는다.  redux 앱이 시작될 때 아직 state가 없으므로 initialState를 기본값으로 제공한다.

function counterReducer(state = initialState, action) {
  // Reducers usually look at the type of action that happened
  // to decide how to update the state
  switch (action.type) {
    case 'counter/incremented':
      return { ...state, value: state.value + 1 }
    case 'counter/decremented':
      return { ...state, value: state.value - 1 }
    default:
      // If the reducer doesn't care about this action type,
      // return the existing state unchanged
      return state
  }
}

action 객체에는 항상 type 필드가 있어야만 한다. type은 보는 사람이 그 의미를 이해할 수 있는 이름이어야 한다.

작업 type에 따라 새로운 결과가 될 새로운 객체를 반환하거나 변경 사항이 없다면 기존 state를 반환해야 한다.

원본을 직접 수정하는 대신 기존 state를 복사하고 복사본을 업데이트 해야한다.

 

이제 reducer를 만들었으니 Store를 만들 수 있다.

 

3. store 만들기

const store = Redux.createStore(counterReducer)

초기 state를 생성하고 향후 업데이트를 계산하기 위해 reducer 함수를 createStore에 전달하여 store를 만든다.

 

4. UI 업데이트 하기 

// Our "user interface" is some text in a single HTML element
const valueEl = document.getElementById('value')

// Whenever the store state changes, update the UI by
// reading the latest store state and showing new data
function render() {
  const state = store.getState()
  valueEl.innerHTML = state.value.toString()
}

// Update the UI with the initial data
render()
// And subscribe to redraw whenever the data changes in the future
store.subscribe(render)

store.subscribe(render)를 사용하여 store가 업데이트될 때마다 render 함수를 호출하고 전달할 수 있다. store가 업데이트될 때마다 최신 값으로 UI를 업데이트할 수 있음을 알 수 있다.

 

5. 액션을 dispatch하기

마지막으로 무슨 일이 일어났는지 설명하는 액션 객체를 생성 하고 이를 store에 dispatch 하여 사용자 입력에 응답해야 한다. store.dispatch(action) 하면 store는 reducer를 실행하고 업데이트된 state를 계산하며 subscribe를 실행하여 UI를 업데이트한다.