Redux (1)

이정수·2023년 9월 13일
0

author: tave web study - 이정수

Redux introduction & 적용하기

리덕스는 왜 쓰이는가?

요 문제를 해결하려고

리덕스 없이 만들어 보기

//without-redux.html 파일의 body
 <body>
    <style>
      .container {
        border: 5px solid black;
        padding: 10px;
      }
    </style>
    <div id="red"></div>
    <div id="green"></div>
    <div id="blue"></div>
    <script>
      function red() {
        document.querySelector('#red').innerHTML = `
        <div class='container' id='component_red'>
          <h1>red</h1>
          <input type = 'button' value='fire'/>
          </div>
          `;
      }
      red();
      function green() {
        document.querySelector('#green').innerHTML = `
        <div class='container' id='component_green'>
          <h1> green</h1>
          <input type = 'button' value='fire'/>
          </div>
          `;
      }
      green();
      function blue() {
        document.querySelector('#blue').innerHTML = `
        <div class='container' id='component_blue'>
          <h1> blue</h1>
          <input type = 'button' value='fire'/>
          </div>
          `;
      }
      blue();
    </script>
  </body>

red, green, blue 박스마다 각각 fire 버튼이 있고, 버튼을 누르면 해당 박스의 색상으로 container의 색상들이 모두 변경된다.
이를 위해서는 각각의 container들이 (querySelector를 사용하여) 서로의 요소에 접근할 수 있어야 한다. 만약 더이상 Red가 필요없어져서 해당 부분의 코드를 삭제한다면? blue, green에서는 에러가 발생한다. 때문에 상자가 하나하나 늘어날 수록 로직이 기하급수적으로 늘어나서 복잡하고 비효율적이라고 생각되었다.
Redux는 이러한 점에서 출발하였다. 즉, 각각의 모듈은 자신의 일에만 집중한다. 자신의 상태가 변하면 변한상태를 중앙에 알리고, 중앙에서 상태가 변할경우 변경된 상태를 받아와서 자기자신을 업데이트한다.


Redux 시작하기

redux CDN에서 script를 복사해서 붙여넣거나, npm install --save redux설치로 시작할수 있다.


Redux의 흐름 알아보기



1. store 생성하기

  function reducer(state, action) {
    //기존의 state값과 action값
    if (state === undefined) {
      //최초의 초기화단계
      return { color: 'yellow' }; //store의 초기 state값이 됨
    }
  }
  const store = Redux.createStore(reducer);
  console.log('store.getState()', store.getState());
  const currentState = store.getState();

  function red() {
    const state = store.getState(); //{color:"yellow"}
    document.querySelector('#red').innerHTML = `
        <div class='container' id='component_red' style="background-color:${state.color}">
          <h1>red</h1>
          <input type = 'button' value='fire' onclick="
            document.querySelector('#component_red').style.backgroundColor='red';
          "/>
          </div>
          `;
  }
  red();
  1. const store = Redux.createStore(reducer);
    createStore()함수를 통해 store를 만든다. createStore함수는 인자로 reducer함수를 받아야 한다.
    만들어진 store는 내부적으로 state값이 생기게 된다.

  2. function reducer(state, action)
    reducer함수는 두가지 인자를 받는다.

  • 기존의 state값
  • action 값
    만약 기존 state값이 undefined 라면 reducer가 최초로 실행되었다는 의미이므로,
    원하는 초깃값을 return 해주면, redux의 store에는 그 초기값이 state로 지정이 된다.
  1. const state = store.getState();
    getState()를 통해 state값을 가져올 수 있다.

2. 새로운 state값 업데이트하기

function reducer(state, action) {
  if (state === undefined) {
    return { color: 'yellow' };
  }
  if (action.type === 'CHANGE_COLOR') {
    console.log('이번의 action', action);
    const newState = Object.assign({}, state, { color: action.color });
    return newState;
  }
}

//RED
function red() {
  const state = store.getState(); //{color:"yellow"}
  document.querySelector('#red').innerHTML = `
      <div class='container' id='component_red' style="background-color:${state.color}">
        <h1>red</h1>
        <input type = 'button' value='fire' onclick="
          store.dispatch({type:'CHANGE_COLOR', color:'red'});
        "/>
        </div>
        `;
}

const store = Redux.createStore(reducer);
store.subscribe(red); //state값이 바뀔때 마다 red 함수가 호출됨.
red();
  1. red함수의 버튼에 onclick 이벤트가 발생할 경우 dispatch를 통해 액션을 전달한다.

store.dispatch({type:'CHANGE_COLOR', color:'red'});

이때 필수적으로 action에 type을 명시해 주어야 한다.

  1. reducer함수 내부에서는 action이 전달되면 type값에 따라 state를 업데이트한다.
if (action.type === 'CHANGE_COLOR') {
    console.log('이번의 action', action);
    const newState = Object.assign({}, state, { color: action.color });
    return newState;
  }

state의 immutability를 지키기 위해선는 기존 state를 직접 변경하지 않고 복사본을 만들고 조작하여 리턴해야 한다.

  1. subscribe를 등록하여 상태값이 변경되면 red함수가 호출되어 UI가 바뀔 수 있도록 한다.

store.subscribe(red);

이렇게 되면 red말고도 blue, green 이 생겨도, red는 blue, green을 전혀 신경쓰지 않아도 된다. :+1:
(decoupling, 서로의 의존성을 낮출수 있다.)


궁금점 :grey_question: Object spread vs Object.assign

Redux에서 새로운 state을 반환할때,(state객체를 업데이트 할때)는 immutability를 지켜야 한다.
즉, 원본 객체가 아니라 복사본에서 업데이트를 하도록 한다.

const secState = Object.assign({}, state, { color: 'red' });

const newState = { ...state, color: 'red' };

두 방식의 차이점은 무었일까?
object assign은 첫번째 인자로 들어간 객체를 조작하고,
spread operator는 새 객체를 반환한다.

Object.assign(a, 조작) 이면 a 라는 객체 자체에 조작을 하게 된다.

{...a, 조작 } 이면 a는 원래 값을 유지한채, shallow copy로 변경된 객체를 반환한다.

Redux에서 Object.assign으로 state를 업데이트 할때는 첫번째 인자로 {} 새로운 객체를 넣어주어 원본 state 변경을 방지한다.

때문에

const secState = Object.assign({}, state, { color: 'red' });
console.log(state === secState); //false

const thirdState = Object.assign(state, { color: 'red' });
console.log(state === thirdState); //true

secState은 새로운 객체를 변경하였기 때문에 state === secState의 결과가 false이지만,
thirdState은 기존의 state 객체를 변경하였기 때문에 state === thirdState결과가 true로 나온다!

결론 > const newState = { ...state, color: 'red' }; , const secState = Object.assign({}, state, { color: 'red' });
요 두 코드는 동일한 역할 을 수행한다. 단, immutability를 지키기 위해서는 Object.assgin 활용시 첫 인자로 빈객체를 넣어주는 것을 잊지 말자!

그리고.. (아직 한발 남았다..)

Redux DEV TOOLS

yarn add @redux-devtools/extension 혹은
awindow.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 를 createStore()의 두번째 파라미터로 넘겨준후 크롬 재시동

이 Redux Dev Tool은 정말 놀랍다. 이전 상태를 replay 할수 있다니?? 세상에.....

복잡한 app이라면 어떤 맥락속에서 문제가 발생했는지 파악하는 것이 중요하다. 이 dev 툴은 그런 작업을 간편하게 만들어 준다.

redux를 사용하면 store로 상태를 관리하고 app을 더 단순한 코드로 복잡한 application을 만들 수 있다.
( 하지만 이런 작업은 contextAPI나 Recoil로도 할수 있는걸? 그러나 이렇게 dev tool을 활용하여 시간여행을 하는 기능은 디버깅의 난이도를 매우 낮춰준다.)

그리고 이맥락에서 다시 짚어보는 불변성

불변성을 지켜야 하는 이유!

Redux는 이전의 state와 새로 전달된 state를 참조값을 통해 구분하기 때문이다.
참조값이 바뀌면, state가 바뀌었다고 인식하여 해당 state를 구독하고 있는 컴포넌트에게 리랜더링을 요청한다.

  • state.test = action.test 처럼 직접 state값을 변경하게 되면 참조값은 변하지 않아 redux를 값이 바뀌었다고 인식하지 않고, 리랜더링 되지 않는다.
  • cf) immer 라이브러리를 사용해서 쉽게 불변성을 유지할 수 있다.

추가로 알아본 정보들

어떤 데이터를 redux로 관리해야 할까?

어플리케이션의 모든 상태를 redux로 관리할 필요는 없지만,,

  • 애플리케이션의 여러곳에서 공유되는 데이터
  • 다른 페이지를 갔다가 돌아왔을 때 그상태를 유지할 필요가 있는 데이터는 redux로 관리한는 편이 좋다.
    ex) 결제페이지 까지 갔다가 뒤로 가기를 클릭한 경우, 이전에 입력했던 사용자 정보를 유지하는 것이 좋으므로 redux로 관리한다.
Redux 더 알아보기

강의를 듣고 난 후 redux가 탄생한 동기를 알아볼 필요성을 느꼈다. 왜냐하면 모든 기술은 기존 기술의 단점을 보완하거나 새로운 기능을 제공하려고 탄생하기 때문이다 :+1:

1. 리덕스의 탄생

리액트는 SPA라는 Single Page Application을 개발하기 위한 라이브러리다. SPA가 등장하고 다양한 요구사항 들을 적용하면서 웹사이트의 규모는 더욱 커지게 되었다. 웹사이트의 규모가 커졌다 -> 즉, 관리해햐 하는 상태들이 많아져 상태관리의 복잡도가 크게 증가하게 되었다는 말이다. 이런 상태에서 서버에서 받아온 데이터 뿐 아니라, 로컬에서 사용하는 데이터들, 페이지에서 사용하는 UI컴포넌트의 상태도 포함된다.

이렇게 규모가 커지고 관리해야 하는 상태들이 많아지면서 개발자들은 언제! 어디서! 어떻게! 상태가 업데이트 되는지 파악하기 힘들게 되었다. 이 과정을 파악하지 못하면 버그를 찾고 수정하는데도 시간이 오래 걸리게 된다.. 버그를 제대로 수정하기 위한 첫 단추는 버그를 재현하는 것이기 때문이다.

이런 문제를 마주한 개발자들은 '수많은 상태를 어떻게 효과적으로 관리할까?' 에 대한 생각이 들게 된다. 문제를 해결하고 상태들을 명확히 관리하기 위해 상태관리만을 위한 기술이 등장하게 되고 그렇게 탄생한 것이 *Redux이다.

2. FLUX 아키텍쳐

Flux 아키텍쳐는 Redux의 기반이 되는 아키텍쳐 이다. 공식 레포에서는 "단방향 데이터 흐름을 활용한 리액트용 어플리케이션 아키텍쳐"로 Flux 를 정의한다. Flux는 메타(구 페이스북 현 메타)에서 리액트에서 발생하는 문제를 해결하기 위해 만든 것이다.

여기서의 핵심은 단방향 데이터 흐름과 아키텍쳐 라는 부분이 FLUX의 정의의 핵심이라고 할 수 있는데. Flux는 곧바로 사용가능한 프레임워크라기 보다는 패턴에 가깝다.( 패턴이라는 단어 대신 비슷한 뜻으로 아키텍쳐 라는 단어를 사용함)

Action : 데이터 흐름에 변화를 주기위해 어떤 동작이 발생함

이렇게 발생한 action을 dispatcher가 받게 된다.

Dispatcher : Action을 발송함

Dispatcher를 통해 변화된 데이터가 store에 저장됨

Store : 데이터를 저장하는 공간

사용자가 보게되는 View에 Store에서 가져온 데이터를 보여주게 된다. 그리고 view에서 또 action이 발생하게 되면 Dispatcher로 전달되는 동일한 흐름을 따라서 단방향으로 처리된다.

Redux impliments Flux

리덕스는 Flux라는 아키텍쳐를 실제로 구현한 구현체이다. Flux는 데이터의 흐름을 정의하는 패턴이고, 이런 패턴을 실제 개발시 코드에 적용해서 사용하게 되는데, Redux는 Flux라는 아키텍쳐를 곧바로 사용할 수 있게 구현한 라이브러리 이다.

3. Redux의 세가지 원칙

Single Source of Truth

Redux에서는 어플리케이션의 모든 상태를 Store라고 불리는 곳에 저장한다. 이때의 Store가 바로 single source of truth가 된다. source of truth가 단 한개가 될 경우, 많은 문제들이 간단히 해결된다. 어떤 상태의 값이 내가 원한는 대로 변경이 되는지 확인이 필요할 때 이 store 내부에서 해당 상태의 값이 어떻게 변하는지 확인하면 된다. 또 상태변화를 한줄로 직렬화 시켜서 상태변화를 일목요연하게 살펴 볼 수 잇고, 이전 값으로 되돌리거나 상태변경을 각각 해보며 디버깅을 쉽게 할수 있다.(Time Travel)

State is READ-ONLY

상태값은 읽기 전용이어야 한다. 외부에서 상태값을 마구 변경할 수 있다면, single source of truth는 별 의미없게 된것이다.

Changes are made with pure function

변화는 순수함수들을 통해 이루어 져야 한다.

순수함수 : 입력값을 변경하지 않고, 동일한 입력값에 대해서는 동일한 출력값을 리턴하는 함수

즉, Redux에서의 상태변화는 모두 pure Function을 통해 이루어져야 한다. 상태의 변화를 일으키는 함수를 Redux에서는 Reducer라고 부르는데, 모든 Reducer는 순수함수여야 한다라는 말이다.

Redux 맛보기를 마치며 ..

다음번에는 React 에 Redux를 적용하면서 왜 Redux가 React와 궁합이 좋은 상태관리 라이브러리인지 공부해 보도록 하겠다.

profile
keep on pushing

0개의 댓글