[Redux] 리덕스를 활용한 CRUD 구현하기

Dodam·2024년 1월 14일
0

[Redux]

목록 보기
1/11
post-thumbnail

redux가 동작하는 전체적인 흐름

  • state : 리덕스에서 저장하고 있는 상태 값(데이터)
  • render : state를 기반으로 화면에 렌더링한다.
  • dispatch : dispatch가 subscribe에 등록된 구독자(함수)들을 모두 호출한다.

        store에 있는 state에 직접 접근할 수 없으므로

  • getState로 값을 가져오고,
  • dispatch를 통해 값을 변경시키고,
  • subscribe : 값이 변경됐을 때 구동될 함수들을 등록해준다.
  • reducer를 통해 state 값을 변경한다.

store 생성

  1. store를 만든다.
    → 내부적으로 state가 자동 생성된다.
  2. reducer 함수를 만들어서 store에 주입한다.
  3. reducer가 dispatch를 통해 들어온 action값과 기존의 state값을 참조해서 새로운 state값을 만든다.
  4. store에 저장된 getState 값을 가져온다.
function reducer(state, action) {
	console.log(state, action);
	  if (state === undefined) {   // 초기 state값 설정
	    return { color: "yellow" };
    }
}

var store = Redux.createStore(reducer);   // store 변수를 전역으로 선언
	console.log(store.getState());   // store 안에 있는 state의 값을 가져온다.

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

store의 dispatch를 호출하면 dispatch는 reducer 함수를 호출한다.
(이 때, reducer는 이전의 state 값과 action을 인자로 가진다.)

  • reducer는 state 값을 변경해준다.
    → 이전 state와 action을 받아서 다음 state값을 리턴하고, 리턴된 state값이 새로운 state값이 된다.
  • return 값은 원본을 바꾸지 않고, 이전의 값을 복제한 결과를 리턴한다. Object.assign({}, state, {color:'red'}); 빈 객체를 복사하고, state 값을 넣어준 후(복제한 후) 새로운 값을 넣어준다.
var newState;
        if(action.type === 'CHANGE_COLOR') {
          newState = Object.assign({}, state, {color:'red'}); // 완전히 독립된, 복제된 결과들이 return 된다.
        }
        return newState;

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

  1. state가 변화하면 action을 스토어에 dispatch한다.
  2. 자신의 변화에 대한 코드를 작성한 후,
    스토어에 구독(subscribe)시켜놓으면 state가 바뀔 때마다 통보를 받는다.
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">
          </div>
        `;
      }
      store.subscribe(red); // state 값이 바뀔 때마다 red 함수가 호출된다.
      red();

리덕스를 사용하는 이유

Redux가 없는 코드는 각각의 component(부품) 간의 의존성이 너무 높다.

예를 들어, 특정 component의 코드를 수정하거나 삭제하면(Red 삭제), 다른 component와(Blue, Green) 연결된 코드가 얽혀있어 오류가 발생한다. 그러므로 component 추가, 수정, 삭제 시, 기존의 component를 모두 수정해야 한다.

하지만, Redux를 통해 중앙 집중형 관리를 하면 각각의 component는 action(상태가 바뀌었다는 것)을 store에 dispatch(통보)하면 된다.
이에 따른 자신의 변화를 작성 후, store에 subscribe(구독)하면 state가 바뀔 때마다 통보받기 때문에 다른 component와의 연결 없이 자신의 모양을 자유롭게 바꿀 수 있다. 즉, 수정해도 다른 component들은 영향을 받지 않게 된다.

요약하자면, Redux를 통해 각 Module의 독립성을 보장받을 수 있다.

Redux의 장점

  • Redux devtools 도구의 활용

    Redux devtools를 사용하여 스토어에 전달한 action들을 리플레이할 수 있다.
    즉, time traveling이 가능하다.
    또한 사용자가 애플리케이션 사용 중에 문제가 발생할 경우, 해당 상태를 다운로드하여 문제를 파악할 수 있다.

  • 단일 스토어
    모든 애플리케이션에서 관리해야 하는 상태는 store에 모두 보관되어 있다.
    따라서 애플리케이션의 상태를 한 곳에서 관리할 수 있다.

Redux 실습

  1. store를 생성한다. (전역 변수로 설정)
var store = Redux.createStore();
  1. reducer 함수를 만든다.
    state와 action을 인자로 넘겨 주고, 초기값을 설정한다.
function reducer(state, action) {
	if (state === undefined) {
		return {
			content: [{...}, {...}]
		}
	}
}
  1. 생성한 reducer 함수를 store에 인자로 넣어준다.
var store = Redux.createStore(reducer);
  1. store.getState()를 통해 state 값을 가져올 수 있다.
function TOC() {
	var state = store.getSate();
	var i = 0;
	var liTags = "";
	while (i < state.contents.length) {
		liTags = liTags + `
			<li>
				<a href="${state.contents[i].id}">${state.contents[i].title}</a>
			</li>
		`;
		i = i + 1;
	}
	document.querySelector("#toc").innerHTML = `
		<nav>
			<ol>
				${liTags}
			</ol>
		</nav>
	`;
}
  1. 현재 상태(state)를 통해 새로운 상태(newState)를 만들어준다.
    이 때, newState는 기존 상태(state)를 복제한 후, 새로운 state를 추가해준다.
    Object.assign() 메서드를 이용해서 빈 객체를 만들고 기존 state를 추가한 후, 새로운 state를 반영한다.
var newState;
if (action.type === 'SELECT') {
	newState = Object.assign({}, state, {selected_id: action.id}
}
return newState;	
}
  1. action을 만들고, dispatch로 action값을 전달해준다. 그러면 reduer가 state값을 갱신한다.
<li>
	<a onClick="event.preventDefault();

		var action = { type: 'SELECT', id: ${state.content[i].id}}
		store.dispatch(action)

		" href="${state.contents[i].id}">
			${state.contents[i].title}
	</a>
</li>
  1. dispatch가 subscribe한 함수들을 호출하면,
    그에 따라 render가 동작하고 state값을 참조해 UI를 새로 그리게 된다.
    (subscribe를 통해서 UI를 자동으로 업데이트)

    즉, subscribe에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때마다 전달해준 함수가 호출된다.

    function artice() {
    	var state = store.getState();
    	var i = 0;
    	var atitle, aDesc;
    	whiel (i < state.contents.length) {
    		if (state.contents[i].id === state.selected_id) {
    			aTitle = state.contents[i].title;
    			aDesc = state.contents[i].desc;
    			break;
    		}
    	}
    	document.querySelector("#content").innerHTML = `
    	<article>
    		<h2>${aTitle}</h2>
    		${aDesc}
    	</article>
    	`;
    }
    
    ...
    
    // 
    store.subscribe(article);

글 작성 기능 구현

    // submit 버튼 제어
    function control() {
    	<ul>
    		<li><a onclick="
    			event.preventDefault();
    			store.dispatch({
    				type: 'CHANGE_MODE',
    				mode: 'create'
    			})
    		" href="/create">create</a>
    		</li>
    	</ul>
    }
    
    // dispatch 함수
    function article() {
    	var state = store.getState();
    	if (state.mode === "create") {
    		document.querySelector("#content").innerHTML = `
    	<article>
    		<form onsubmit="
    			var _title = this.title.value;
    			var _desc = this.desc.value;
    			store.dispatch({ type:'CREATE', title: _title, desc: _desc })
    		">
    		...
    		</form>
    	</article>
    }

	// reducer 함수
    function reducer(state, action) {
    	if (state === undefined) {
    		return {
    			max_id: 2,
    			mode: "create",
    			contents: [
    				{ id: 1, title: "HTML", desc: "HTML is ..." },
    			],
    		}
    	}
    	// 새로운 state 생성
    	var newState;
    	if (action.type) === "CREATE") {
    		var newMaxId = state.max_id + 1;
    		var newContents = state.contents.concat();
    		newContents.push({
    			id: newMaxId,
    			title: action.title,
    			desc: action.desc,
    		});
    	}
    	// console.log(action, state, newState);
    	return newState;
    }
    
    // store 생성
    var store = Redux.createStore(reducer);
    
    // subscribe를 통한 자동 UI 업데이트
    store.subscribe(article);

전체 코드

Create, Read, Delete 기능 구현

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/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() {
        // TOC: Table Of Content (글 목록)
        var state = store.getState();
        var i = 0;
        var liTags = "";
        while (i < state.contents.length) {
          // <li> 태그 안에 action을 만들고, dispatch로 action값을 전달해준다.
          liTags =
            liTags +
            `
          <li>
            <atoken interpolation">${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 href="/create">create</a></li>

          <li><input onclick ="
              store.dispatch({
                type: 'DELETE',
              })
            " type="button" value="delete"></input></li>
        </ul>
        `;
      }

      function article() {
        var state = store.getState();
        if (state.mode === "create") {
          // 글 작성 기능
          document.querySelector("#content").innerHTML = `
        <article>
          <form>
            <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 = `
              <aritcle>
                <h2>${aTitle}</h2>
                ${aDesc}
              </article>
            `;
        } else if (state.mode === "welcome") {
          // 글 삭제 기능
          document.querySelector("#content").innerHTML = `
          <aritcle>
            <h2>Welcome</h2>
            Hello, Redux!
          </article>
        `;
        }
      }

      // reducer 함수
      function reducer(state, action) {
        if (state === undefined) {
          // state의 초기값 설정
          return {
            max_id: 2,
            mode: "welcome",
            selected_id: 1,
            contents: [
              { id: 1, title: "HTML", desc: "HTML is ..." },
              { id: 2, title: "CSS", desc: "CSS is ..." },
            ],
          };
        }
        // 새로운 state 생성
        var newState;
        if (action.type === "SELECT") {
          newState = Object.assign(
            // 기존 state를 복제 후, 새로운 state를 추가한다.
            {},
            state,
            { selected_id: action.id, mode: "read" }
          );
        } else if (action.type === "CREATE") {
          var newMaxId = state.max_id + 1;
          var newContents = state.contents.concat(); // 배열을 복사할 때는 assign 대신 concat을 사용한다.
          newContents.push({
            id: newMaxId,
            title: action.title,
            desc: action.desc,
          });
          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;
      }
      // store 생성 (전역)
      var store = Redux.createStore(reducer);

      // state 값이 바뀌면 해당 함수가 호출되며, 자동으로 UI가 업데이트 된다.
      store.subscribe(article);
      store.subscribe(TOC);

      subject();
      TOC();
      control();
      article();
    </script>
  </body>
</html>
profile
Good things take time

3개의 댓글

comment-user-thumbnail
2024년 1월 20일

여태까지 눈팅중이었는데 미니 프로젝트 때 보다 한층 더 발전된 모습을 보여주고 계시는 것 같아 뿌듯합니다. 잘 보고 가요 누나!

1개의 답글