Javascript

Redux는 무엇인가 (코딩애플, 생활코딩, Udemy 노트필기)

selonjulie 2022. 12. 6. 09:22

참고

2022.11.18 - [React] - useReducer

2022.07.07 - [React] - UseContext(Context API)

 

Redux는 무엇인지?

  • cross-component 또는 app-wide state를 위한 state관리 시스템
  • A predictable state container for Javascript apps
  • 자바스크립트로 만든 어플리케이션을 위한 예측 가능한 상태의 저장소
    • 예측 가능
      • 1) 코드의 복잡성을 낮추어, 코드가 예측 가능하게 만들어 줌
        • Single Source of Truth
        • 상태는 객체임 state={}
        • 하나의 객체안에 app에서 필요한 모든 데이터를 다 넣는다
      • 2) 외부로 부터 철저히 차단시킨다
        • 인가된 담당자(함수)만 사용할 수 있음. dispatcher 혹은 reducer만 쓸 수 있게
        • 직접 state값을 가지고 오지 못함. 함수를 통해서만 가져갈 수 있음
        • 예기치 않게 state값이 바뀌는 문제를 사전에 차단한다
      • 3) state의 데이터를 사용하는 부품들에 연락하여 지령을 내림
        • 자기 할일만 하면 됨
    • 장점
      • 1.undo, redo가 쉬움
        • state값을 복제하고, 그 복제값만 사용하기 때문
      • 2.debugger를 통해 현재 상태 뿐만아니라 이전 상태도 확인 할 수 있음
      • 3.module reloading을 할 수 있음 

 

state의 종류

  • Local State:  하나의 컴포넌트에 속하는 state
    • eg. 사용자의 입력 값을 감지하는 input 창, '더보기'를 할 수 있는 토글
    •  => 이는 컴포넌트 내부에서 useState(), useReducer()로 관리
  • Cross-Component State: 여러 개의 컴포넌트에 영향을 미치는 state
    • eg. 모달
    • => 이는 "prop chains", "prop drilling"이 필요
  • App-wide state: 모든 앱(거의 모든 컴포넌트)에 영향을 미치는 state
    • eg. 사용자 인증 (로그인)
    • => 이는 "prop chains", "prop drilling"이 필요
  • cross-component state, app-wide state은 prop을 관리하기 번거로우므로 내장 hook인 useContext를 사용할 수 있음
  • redux도 동일한 문제를 해결해 줌

 

Redux vs. useContext

그렇다면 useContext를 사용하면 되지 왜 redux가 필요한가?

2022.07.07 - [React] - UseContext(Context API)

 

useContext의 잠재적인 단점

  • 1.큰 앱에서는 설정이 복잡해지고, 상태관리가 어려울 수 있음
    • 1.1.여러 state가 있으면 그걸 관리하기위한 여러 context가 생기고, 그럼 ContextProvider컴포넌트가 많아짐.
    • 결국 심하게 중첩된 JSX코드가 나올 수 있음

    • 1.2.큰 ContextProvider를 사용해서 그 안에서 관리할 수도 있지만 그럼 하나의 ContextProvider 많을 것을 관리하기 때문에 유지하기 어려움

  • 2. 성능
    • useContext는 테마를 변경하거나, 인증 같은 '저빈도 업데이트'에는 좋지만, 데이터가 자주 변경되는 경우에는 적합하지 않음
    • 따라서 유동적인 상태 관리 라이브러리인 redux가 적합

Redux를 왜 쓰는지?

1. props의 대체제

2. state관리하기 쉬움

 

1. props의 대체제

  • 컴포넌트로 만들어진 리액트에서, state(변수)를 쓰고싶을 때, 다른 컴포넌트들은 이 state를 못씀
  • 꼭 props로 전송해 주어야함 (props를 한번에 전송하는 문법은 없음)
  • 2022.08.18 - [React] - Props
    • =>이럴 때 redux사용
  • redux사용하면 state를 보관하는 파일을 만들 수 있음
    • =>모든 컴포넌트들이 이 state을 직접 빼다가 쓸 수 있음 props를 사용할 필요가 없어짐
//index.js 세팅 + state보관

import {Provider} from 'react-redux';
import {createStore} from 'redux';

const 체중 = 100; //state마음대로 보관 가능

function reducer(state = 체중, action){
 return state
}

let store = createStore(reducer)

ReactDOM.render(
 <React.StrickMode>
  <Provider store = {store}>
   <App />
  </Provider>
 </React.StrickMode>
 document.getElementById('root')
);
//App.js 컴포넌트에서 사용하기

import './App.css';
import {useSelector} from 'react-redux'

function App(){

 const 꺼내온거 = useSelector((state)=>state); //100

 return(
  <div classname = 'App'>
  <p>님의 처참한 몸무게: {꺼내온거}</p>
  </div>
 );
}

export default App;

 

2. state관리하기 쉬움

  • state사용하다가 버그가 생겼을 때, 일일이 찾을 필요가 없이 
  • redux를 사용하면 state를 저장해논데에 수정 방법(reducer)을 정의해 놓을 수 있음 
  • 다른 컴포넌트들은 수정 요청만 할 수 있음(dispatch함수 사용, 요청명 적기)
    • 똑같은 기능을 제공하는 라이브러리: MobX, Overmind.js, Recoil
  • 장점
    • 버그가 일어났을 때 추적이 쉽다
    • 큰 프로젝트를 진행할 때 필수
//index.js 세팅 + state보관 + state 수정방법 정의

import {Provider} from 'react-redux';
import {createStore} from 'redux';

const 체중 = 100; //state마음대로 보관 가능

//if문으로 수정방법 정의: reducer
function reducer(state = 체중, action){
 if(action.type === '증가'){
  state++; //증가요청하면 몸무게 +1
  return state
 } else if(action.type === '감소'){
  state--; //감소요청하면 몸무게 -1
  return state
 } else{
  return state
 }
}

let store = createStore(reducer)

ReactDOM.render(
 <React.StrickMode>
  <Provider store = {store}>
   <App />
  </Provider>
 </React.StrickMode>
 document.getElementById('root')
);
//App.js 컴포넌트에서 사용하기

import './App.css';
import {useDispatch, useSelector} from 'react-redux'//dispatch사용

function App(){

 const 꺼내온거 = useSelector((state)=>state); //100
 const dispatch = useDispatch()

 return(
  <div classname = 'App'>
  <p>님의 처참한 몸무게: {꺼내온거}</p>
  <button onClick = {()=>{dispatch({type: '증가'})}}>더하기</button>//dispatch함수 쓰고, 요청명쓰기
  </div>
 );
}

export default App;

화면


Redux의 작동 방식

'한' 개의 데이터(상태)를 저장하는 store가 있음. 컴포넌트는 절대 저장된 데이터를 직접 조작하지 않음.

 

1. store: 컴포넌트는 그 store를 구독하고, 필요한 데이터를 store->컴포넌트 받음

절대로 컴포넌트 -> store로 데이터가 흐르지 않음

 

2. reducer function: 변경되는 데이터는 Reducer Fn을 사용하여 데이터의 변형, 업데이트를 담당함

 

*Reducer Fn은 useReducer 훅과는 다름. 일반적인 개념으로 input을 받아 그 input을 변화하고 줄이는 역할을 함

입력을 변환해서 새로운 출력, 새로운 결과를 뱉어냄

 

3. action: 'trigger'를 함. 컴포넌트가 action을 발송(dispatch)함. action은 reducer작동해야되는 작업을 설명하는 단순한 '자바스크립트 객체'임.

  • 리덕스는 그 액션을 reducer로 전달하고, 원하는 작업에 대한 설명을 읽음.
  • 그 후 reducer가 그 작업을 수행함.
  • 그 후 reducer가 새로운 상태를 뱉으면 그 상태가 store에서 기존의 상태를 대체함.
  • 그 후 구독하는 컴포넌트가 알게되고, 컴포넌트는 UI를 업데이트함   

 


Javascript에서의 Redux

state와 render의 관계

  • Redux의 핵심은 store
    • store를 은행에 비유해서 생각
      • 은행에서 직접 돈을 못만지고 창구 직원(dispatch, subscribe, getState)를 통해야함
    • 정보가 저장되는 곳
    • store안에는 state라고 하는 실제 정보가 저장
      • !절대로 state에 직접 조작하는게 불가능!
  • store를 만들때 reducer라는 함수를 만들어 (인자로) 공급해주어야함
function reducer(oldState, action){
 //...
}
var store = Redux.createStore(reducer);
  • render : store안쪽에 있지 않음. UI를 만들어주는 역할을 하는 '내'가 짤 코드
function render(){
 var state = store.getState();
 //...
 document.querySelector('#app').innerHTML =
  <h1>WEB</h1>
  ...
}

store의 state값이 바뀔 때 마다 render함수를 불러오면 얼마나 좋을까? - subscribe

store.subscribe(render);

 

action과 reducer

  • action은 객체
    • type: create
  • action이 -> dispatch에게 전달
  • dispatch의 역할: 1. reducer를 호출해서 state값을 바꿈 2.subscribe를 이용하여 render함수를 호출하여 화면을 갱신 
    • 1. reducer를 호출해서 state의 값을 바꿈
      • Reducer함수 호출시 2개의 값을 전달함: 1) 현재의 state값 2)action데이터 
      • 이때 reducer의 return 객체는 state의 새로운 값
      • state를 입력값으로 받고, action을 참조해서 새로운 state값을 return해주는 'state 가공자'
function reducer(state, action){
 if(action.type === 'create'){
  var newContents = oldState.contents.concat();
  var newMaxId = oldState.maxId+1;
  newContents.push({id: newMaxId, title: action.payload})
  return Object.assign({}, state, {
   contents: newContents,
   maxId: newMaxId,
   mode: 'read',
   selectId: newMaxId
  })
 }
}

  • dispatch역할
    • 2. subscribe를 이용하여 render함수를 호출하여 화면을 갱신
      • 새로운 state값이 return되면 render가 다시 호출되야함
      • dispatch가 subscribe에 등록되어 있는 구독자들을 다 호출해줌 → render 호출 → 이후 일 반복 (render→getState→state→getState→render→UI그려줌)

 

요약

  • 핵심은 state
  • state를 기반으로 화면을 그려준다
  • store에 있는 state에 직접 접근하는게 금지되어 있기 때문에 중간에  getState를 통해 값을 가져온다
  • dispatch를 통해 값을 변경시킨다
  • subscribe를 이용해서 값이 변경됬을때 구동될 함수들을 등록해준다
  • reducer를 통해서 state값을 변경한다

 

실습 1

Without Redux

  • 부품(component)끼리 강력하게 coupling이 되어 있음. 서로 의존하고 있음.
더보기
<html>
  <body>
    <style>
      .container {
        border: 5px solid black;
        padding: 10px;
      }
    </style>
    <div id="red"></div>
    <div id="green"></div>
    <script>
      function red() {
        document.querySelector("#red").innerHTML = `
        <div class="container" id="component_red">
          <h1>red</h1>
          <input type="button" value="fire" onclick="
          document.querySelector('#component_red').style.backgroundColor='red';
          document.querySelector('#component_green').style.backgroundColor='red';
          ">
        </div>
          `;
      }
      red();

      function green() {
        document.querySelector("#green").innerHTML = `
        <div class="container" id="component_green">
          <h1>green</h1>
          <input type="button" value="fire" onclick="
          document.querySelector('#component_red').style.backgroundColor='green';
          document.querySelector('#component_green').style.backgroundColor='green';
          ">
        </div>
          `;
      }
      green();
    </script>
  </body>
</html>

 

With Redux

  • 중앙 집중적 관리
  • 부품(component)끼리 완전히 독립적(decoupling)

CDN | Redux

1. store생성

더보기
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
  </head>
  <body>
    <style>
      .container {
        border: 5px solid black;
        padding: 10px;
      }
    </style>
    <div id="red"></div>
    <div id="green"></div>
    <script>
    //(3)reducer를 통해 state값을 만들어 줄 때 reducer의 기존 state값이 undefined라면 원하는 초기값을 설정해주면 됨
      function reducer(state, action) {
        if (state === undefined) {
          return { color: "yellow" };
        }
      }
      var store = Redux.createStore(reducer);//(1)store를 만들면 내부적으로 state가 생김 (4)초기값이 지정됨
      console.log(store.getState());//{ color: "yellow" }

      function red() {
        var state = store.getState();//(2)state값을 가져오려면 getState를 써야함 (5)state값을 가져옴
        //(6)초기값을 지정할 수 있게됨
        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';
          document.querySelector('#component_green').style.backgroundColor='red';
          ">
        </div>
          `;
      }
      red();
    </script>
  </body>
</html>

2. reducer와 action을 이용해서 새로운 state값 만들기

더보기
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
  </head>
  <body>
    <style>
      .container {
        border: 5px solid black;
        padding: 10px;
      }
    </style>
    <div id="red"></div>
    <div id="green"></div>
    <script>
    //(2)dispatch는 reducer 함수를 호출함. 이때 oldState값과, 전달된 action의 값을 인자로줌. return값은 새로운 state값
    //최초 한번은 무조건 실행, button이 트리거되었을 때 state값이 교체됨
      function reducer(state, action) {
        // console.log("state:", state);
        // console.log("action:", action);
        // console.log("=========");
        if (state === undefined) {
          return { color: "yellow" };
        }
        var newState;
        if (action.type === "CHANGE_COLOR") {
        //항상 oldState'복제'해서 새로운 state를 반환하게함
          newState = Object.assign({}, state, { color: "red" });
        }
        return newState;
      }
      var store = Redux.createStore(reducer);
      // console.log("getState:", store.getState());

      function red() {
        var state = store.getState();
        //(1)store에 dispatch함수 호출, action(객체)를 줌. 이때 type은 반드시 넣기
        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>
          `;
      }
      red();
    </script>
  </body>
</html>
  • reducer의 역할: store의 state값을 변경해줌
  • action을 만들어 dispatch에게 제출하면, dispatch가 reducer를 호출
    • 그때 oldState, action을 함께 넘김
  • reducer함수가 그 둘을 분석해서 새로운 state값을 반환

  • !state값을 받아서 변경하지말고, state값을 복제하고 그 복제본을 받아서 변경해라! (생활코딩 | immutabililty)
  • 객체를 복제할때 Object.assign({}, )을 사용
    • 첫번째 인자: 빈 객체, 두번째 인자: 빈 객체에 복제할 속성 =>새로운 객체가 복사됨
    • Object.assign()의 return 값은 첫번째 인자

 

3. state의 변화에 따라서 UI에 반영하기

더보기
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
  </head>
  <body>
    <style>
      .container {
        border: 5px solid black;
        padding: 10px;
      }
    </style>
    <div id="red"></div>
    <div id="green"></div>
    <script>
      function reducer(state, action) {
        // console.log("state:", state);
        // console.log("action:", action);
        // console.log("=========");
        if (state === undefined) {
          return { color: "yellow" };
        }
        var newState;
        if (action.type === "CHANGE_COLOR") {
          newState = Object.assign({}, state, { color: action.color }); //(2)하드코딩이 아닌 action.color로 변경
        }
        return newState;
      }
      var store = Redux.createStore(reducer);

      function red() {
        var state = store.getState();
        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>
          `;
      }
      store.subscribe(red); //(1)state값이 바뀔때마다 red함수가 호출됨
      red();

      function green() {
        var state = store.getState();
        document.querySelector("#green").innerHTML = `
        <div class="container" id="component_red" style="background-color:${state.color}">
          <h1>green</h1>
          <input type="button" value="fire" onclick="
          store.dispatch({type: 'CHANGE_COLOR', color: 'green'});
          ">
        </div>
          `;
      }
      store.subscribe(green); 
      green();
    </script>
  </body>
</html>
  • state값이 바뀌면 render를 호출해서 state값을 가져와서 화면에 그리기
  • subscribe에 render를 등록해 놓으면됨. 그럼 dispatch가 state값을 바꿈

 

실습 2

CRUD가 있는 앱 만들기

더보기
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.js"></script>
  </head>
  <body>
    <div id="subject"></div>
    <div id="toc"></div>
    <div id="control"></div>
    <div id="content"></div>
    <script>
      function subject() {
        document.querySelector("#subject").innerHTML = `
              <header>
            <h1>WEB</h1>
            Hello, WEB!
          </header>
        `;
      }

      function TOC() {
        //(5) store에 있는 변수값을 getState를 통해 가져오기
        var state = store.getState();
        // console.log(state);
        var i = 0;
        var liTags = "";
        //(6) state변화주기 위해서 action 정의하기 (7)store.dispatch에 action을 줌 =>store가 reducer를 호출함
        while (i < state.contents.length) {
          liTags =
            liTags +
            `
          <li>
            <a onclick="
            event.preventDefault();
            var action = {type:'SELECT', id:${state.contents[i].id}}
            store.dispatch(action);
            " href="${state.contents[i].id}">${state.contents[i].title}</a>
          
            </li>
          `;
          i = i + 1;
        }
        document.querySelector("#toc").innerHTML = `
            <nav>
              <ol>${liTags}</ol>
            </nav>
        `;
      }

      function control() {
        document.querySelector("#control").innerHTML = `
        <ul>
          <li><a onclick="
            event.preventDefault();
            store.dispatch({
              type:'CHANGE_MODE',
              mode:'create'
            })
            "
            href="/create">create</a></li>
          <li><input onclick="
            store.dispatch({type: 'DELETE'})
            "
            type="button" value="delete" /></li>
        </ul>
        `;
      }

      function article() {
        //(5),(6),(7)
        var state = store.getState();
        if (state.mode === "create") {
          document.querySelector("#content").innerHTML = `
        <article>
          <form onsubmit="
           event.preventDefault();
           var _title =this.title.value;
           var _desc =this.desc.value;
           store.dispatch({
            type:'CREATE',
            title: _title,
            desc: _desc
           })
          ">
            <p>
              <input type="text" name="title" placeholder="title">
            </p>
            <p>
              <textarea name="desc" placeholder="description"></textarea>
            </p>
            <p>
              <input type="submit">
            </p>
            
          </form>
        </article>`;
        } else if (state.mode === "read") {
          var i = 0;
          var aTitle, aDesc;
          while (i < state.contents.length) {
            if (state.contents[i].id === state.selected_id) {
              aTitle = state.contents[i].title;
              aDesc = state.contents[i].desc;
              break;
            }
            i = i + 1;
          }
          document.querySelector("#content").innerHTML = `
        <article>
          <h2>${aTitle}</h2> 
          ${aDesc}
        </article>`;
        } else if (state.mode === "welcome") {
          document.querySelector("#content").innerHTML = `
        <article>
          <h2>Welcome</h2> 
          Hellow, Redux!
        </article>`;
        }
      }

      //(2)꼭 reducer를 줘야한다, 2-1.이전 state값, 호출 이후 action값 입력
      function reducer(state, action) {
        // console.log(state, action);
        //(3)초기값 설정
        if (state === undefined) {
          return {
            max_id: 2,
            mode: "welcome",
            selected_id: 1,
            contents: [
              { id: 1, title: "HTML", desc: "HTML is ..." },
              { id: 2, title: "CSS", desc: "CSS is ..." },
            ],
          };
        }
        //(8) 정의된 action일 때, 기존 state복제하고
        var newState;
        if (action.type === "SELECT") {
          newState = Object.assign({}, state, {
            selected_id: action.id,
            mode: "read",
          });
        } else if (action.type === "CREATE") {
          var newMaxId = state.max_id + 1;
          //배열일때는 concat사용하여 복제
          var newContents = state.contents.concat();
          newContents.push({
            id: null,
            title: action.title,
            desc: action.desc,
          });
          //기존의 state복사
          newState = Object.assign({}, state, {
            max_id: newMaxId,
            contents: newContents,
            mode: "read",
          });
        } else if (action.type === "DELETE") {
          var newContents = [];
          var i = 0;
          while (i < state.contents.length) {
            if (state.selected_id !== state.contents[i].id) {
              newContents.push(state.contents[i]);
            }
            i = i + 1;
          }
          newState = Object.assign({}, state, {
            contents: newContents,
            mode: "welcome",
          });
        } else if (action.type === "CHANGE_MODE") {
          newState = Object.assign({}, state, {
            mode: action.mode,
          });
        }
        // console.log(action, state, newState);
        return newState; //2-2. return값은 새로운 state
      }
      //(1)store를 만든다 (4)reducer를 createStore의 입력값으로 준 후, 변수화
      var store = Redux.createStore(reducer);

      //(9) article이 state값이 바뀔때마다 자동으로 article함수가 불러지도록
      store.subscribe(article);
      store.subscribe(TOC);

      subject();
      TOC();
      control();
      article();
    </script>
  </body>
</html>

 

시간여행 방법

1. Redux DevTools 활용

chrome web store | Redux DevTools

README.md | Redux DevTools Extension

 const store = createStore(
   reducer, /* preloadedState, */
+  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

2. reducer 함수에서 console.log를 찍어보기

      function reducer(state, action) {
        if (state === undefined) {
          return { color: "yellow" };
        }
        var newState;
        if (action.type === "CHANGE_COLOR") {
          newState = Object.assign({}, state, { color: action.color });
        }
        console.log("action.type:", action.type);
        console.log("action:", action);
        console.log("state:", state);
        console.log("newState:", newState)
        console.log("==========");;
        return newState;
      }


Node.js에서 Redux

1. store생성: createStore()

2. reducer Fn생성: 2개의 매개변수를 받음(old state + dispatched action), new state object를 반환함. 하지만 redux가 처음 이 코드를 실행시켰을 때를 위해 기본 값을 정해놓아야함(그렇지 않으면 state는 undefined 상태)

store에 어떤 reducer함수를 연결해야되는지 넣어줌

*Reducer 함수는 순수함수. same input leads to same output. 중간에 side effect는 없어야함

3. 그 store를 구독할 누군가가 필요. 구독은 함수로 매개변수 받지 않고, 저장소에서 getState()로 상태를 불러옴. redux에 상태가 변경할 때마다 구독 함수를 실행하라고 말해줘야함. store.subscribe(counterSubscriber);

4. 발송할 Action(type속성이 있는 객체) 필요. 리덕스는 reducer 내부에서 '다른 일'을 하는게 목표.

5. reducer Fn내에서 각 action의 상황에 따라 변화하게 될 사항을 if문으로 정의함

 

더보기
//node.js
const redux=require('redux');

//2.reducer함수 정의-> 5번 후에 액션을 받고 그 후 어떻게 변경될지 if문을 통해 정의
const counterReducer = (state = {counter : 0}, action)=>{
 if(action.type === 'increament'){
   return{
  counter: state.counter+1
 };
 }
 if(action.type === 'decreament'){
   return{
  counter: state.counter-1
 };
 }
 
 return state;
};

//1.store생성
const store=redux.createStore(counterReducer);

//3.구독 함수
const counterSubscriber = ()=>{
 const latestState = store.getState()
 console.log(latestState)
};

//4.redux에 상태가 변경할 때마다 구독 함수를 실행하라고 말해줘야함(실행은 안하고 point만함, 실행은 redux가 함)
store.subscribe(counterSubscriber)

//5.액션을 만들고 발송
store.dispatch({type: 'increment'});
store.dispatch({type: 'decrement'})

React에서 Redux

Store

1. store생성: 일반적으로 새로운 폴더를 생성함, reducer함수 만들기 

*절대 기존의 state를 변형해서는 안됨. 대신 새로운 state객체를 반환하여 항상 재정의함

더보기
//store-index.js

import {createStore} from 'redux';

const initialState = {counter:0, showConter: true};

const counterReducer = (state=initialState, action) => {
 if(action.type === 'increment'){
 //state.counter++ 이렇게 기존의 state를 변경하는거 절대 불가
 return{
  counter: state.counter + 1,
  showConter: state.showCounter
 }
 }
 
  if(action.type === 'increase'){
 return{
  counter: state.counter + action.amount, //payload추가
  showConter: state.showCounter
 }
 }
 
  if(action.type === 'decrement'){
 return{
  counter: state.counter - 1
  showConter: state.showCounter
 }
 
 if(action.type === 'toogle'){
  return{
   showCounter: !state.showCounter,
   counter: state.counter
  }
 }
 }
 return state;
}

const store = createStore(counterReducer);

export default store;

2. store제공:  가장 최상위에서 파일에서(index.js) provider로 app을 감싸기. 이제 컴포넌트가 store를 사용할 수 있음

더보기
//index.js
import {Provider} from 'react-redux'
import store from './store/index'

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

 

Component

3. 컴포넌트에서 useSelector로 컴포넌트에서 가져올 수 있게함. 그 후 component에서 useSelector훅을 이용하여 사용

 

4. useDispatch로 액션을 dispatch함

더보기
//Counter.js

import {useSelector, useDispatch} from 'react-redux';

const Counter =()=>{
 const dispatch= useDispatch();
 const counter = useSelector(state => state.counter);
 const show = useSelector(state => state.showCounter);
 
 const incrementHandler=()=>{
  dispatch({type: 'increment'})
 }
 
 const increaseHandler=()=>{
  dispatch({type: 'increment', amount: 5 }) //페이로드 추가
 }
 
  const decrementHandler=()=>{
  dispatch({type: 'decrement'})
 }
 
  const toogleCounterHandler=()=>{
  dispatch({type: 'toogle'})
 }
 
 return {
  {show && <div>{counter}</div>}
  <div>
   <button onClick={incrementHandler}>Increment</button>
   <button onClick={decrementHandler}>Decrement</button>
  </div>
 }
}

Redux에서 문제점

1. 식별자(type의 value를)를 잘못 치기가 쉬움

2. reducer함수의 크기가 엄청 커짐

3. 항상 모든 state의 snapshot을 반환해야하기 때문에 번거로움

 


Redux toolkit

툴킷을 사용하면 위 문제를 해결할 수 있음

툴킷을 설치하면 Redux는 삭제(툴킷에 포함되어 있음)

 

Store

1. store생성, 객체를 인자로 생성하는 createSlice를 사용하면 조각으로 사용할 수 있음. 관련되어 있지 않은 state는 여러 slice를 생성해서 유지관리가 편함. reducer함수에서 if문을 적지 않아도됨. 변경사항도 바로 적을 수 있음

2. configureStore로 저장소에 연결

3. action 식별자 만들기 counterSlice.actions

알아서 액션 객체가 만들어짐 (이미 type 속성을 가지고 있음) 

더보기
//store-index.js

import {createStore} from 'redux';
import {createSlice, configureStore} from '@redux/js/toolkit';

const initialState = {counter:0, showConter: true};

const counterSlice = createSlice({
 name: 'counter',
 initialState,
 reducers: {
  increment(state) {
   state.counter++;
  },
  decrement(state) {
   state.counter--;
  },
  increase(state, action) {
   state.counter = state.counter + action.payload;
  },
  toggleCounter(state) {
   state.showCounter = !state.showCounter;
  },
 }
})

const store = configureStore({
 reducer: counterSlice.reducer
});

//alternative
const store = configureStore({
 reducer: { counter: counterSlice.reducer }
})

export const counterActions = counterSlice.actions;
export default store;

Component

4. 컴포넌트에서 action creators를 import해서 사용. 액션 객체를 바로 사용할 수 있음

더보기
//Counter.js

import {useSelector, useDispatch} from 'react-redux';
import {counterActions} from '../store/index'

const Counter =()=>{
 const dispatch= useDispatch();
 const counter = useSelector(state => state.counter);
 const show = useSelector(state => state.showCounter);
 
 const incrementHandler=()=>{
  dispatch(counterActions.increment())
 }
 
 const increaseHandler=()=>{
  dispatch(ounterActions.increase(10))//{type: SOME_UNIQUE_IDENTIFIER, payload: 10}
 }
 
  const decrementHandler=()=>{
  dispatch(counterActions.decrement())
 }
 
  const toggleCounterHandler=()=>{
  dispatch(ounterActions.toggleCounter())
 }
 
 return {
  {show && <div>{counter}</div>}
  <div>
   <button onClick={incrementHandler}>Increment</button>
   <button onClick={decrementHandler}>Decrement</button>
  </div>
 }
}

Redux Saga

  • reducer는 순수 함수이기 때문에 데이터 fetching을 위한 비동기 처리 같은 부수적인 효과(side effects) 처리를 할 수 없음
  • redux-saga는 부수적인 효과를 만들기 위한 라이브러리
  • redux의 미들웨어
  • redux saga가 나오기 전에는 이를 처리하기 위해 redux-thunk를 사용

 

출처

코딩애플 | React 입문자들이 알아야할 Redux 쉽게설명 (8분컷)

Redux-Saga 소개

Udemy | React 완벽 가이드

생활코딩 | Redux

 

 

 

 

'Javascript' 카테고리의 다른 글

함수형 프로그래밍  (0) 2023.01.03
ISO 8601 국제표준시간(2016-10-27T17:13:40Z) 날짜만 나오게 자르기  (0) 2022.12.19
class  (2) 2022.11.28
생성자 함수와 new 연산자  (2) 2022.11.26
This  (0) 2022.11.26