그럴듯한 개발 블로그
반응형

완성본 코드
https://github.com/donghyun1998/vanilla_js_SPA_starter_kit/blob/main/src/utils/useReducer.js
 
useState로도 상태관리 가능하지만, 더 짧고 가독성 좋게 관리할 수 있는 리액트 훅인 useReducer에 대해 알게 되어 비슷하게 구현해봤다.
이전에 구현했던 useState와 비슷하게 클로저로 구현했다.
중복되는 개념이 많으니 먼저 보고 오길 바란다.
 
복잡한 state가 필요한 상황에서 useReducer를 사용하면 reducer함수 내부의 switch문으로 state변경을 가독성 좋게 할 수 있다.

function deepCopy(obj) {
  if (Array.isArray(obj)) {
    return obj.map((item) => deepCopy(item));
  } else if (isObject(obj)) {
    return Object.keys(obj).reduce((acc, key) => {
      acc[key] = deepCopy(obj[key]);
      return acc;
    }, {});
  } else {
    return obj;
  }
}
function isObject(obj) {
  return obj !== null && typeof obj === "object";
}

/**
 * useReducer
 * @param {function} reducer
 * @param {Array | Object | number | string | boolean} stateInput
 * @param {HTMLElement} component
 * @param {string} render
 * @returns
 */
export default function useReducer(reducer, stateInput, component, render) {
  let state = stateInput;
  const getState = () => {
    return state;
  };
  const dispatch = (action) => {
    let newState = deepCopy(state);
    reducer(newState, action);
    state = newState;
    component[render]();
  };
  return [getState, dispatch];
}

 

import useReducer from "../../utils/useReducer.js";
import { importCss } from "../../utils/importCss.js";
import {navigate} from "../../utils/navigate.js";

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      state.age += 1;
      return state;
    case "DECREMENT":
      state.age -= 1;
      return state;
    case "MEMBER":
      state.grade = 'member';
      return state;
    case "BACKEND":
      state.development = 'backend';
      return state;
    default:
      return state;
  }
}

export default function useReducerPrac($container) {
  const input = {name: '김동현', age: 25, organization: '42seoul', grade: 'learner', development: 'frontend'};
  const [getProfile, dispatch] = useReducer(reducer, input, this, "renderProfile");

  this.init = () => {
    this.render();
    this.renderProfile();
    $container.querySelector(".increase").addEventListener("click", () => {
      dispatch({ type: "INCREMENT" });
    });
    $container.querySelector(".decrease").addEventListener("click", () => {
      dispatch({ type: "DECREMENT" });
    });
    $container.querySelector(".change-grade").addEventListener("click", () => {
      dispatch({ type: "MEMBER" });
    });
    $container.querySelector(".change-end").addEventListener("click", () => {
      dispatch({ type: "BACKEND" });
    });
    $container.querySelector(".navigate-to-root").addEventListener("click", () => {
      navigate("/");
    });
  };

  this.render = () => {
    importCss("../style/card-page.css");
    $container.innerHTML = `
    	<div>
    		<div class="title">useReducer를 사용해 봅시다</div>
    	</div>
    	<div class="card" style="width: 18rem;"></div>
    	<button class="increase btn btn-primary">나이 증가</button>
      <button class="decrease btn btn-primary">나이 감소</button>
      <button class="change-grade btn btn-primary">멤버 승급</button>
      <button class="change-end btn btn-primary">백엔드 개발자로 전향</button>
      <button type="button" class="btn btn-secondary navigate-to-root">Go to root</button>
    `;
  };
  this.renderProfile = () => {
    const profile = getProfile();
    $container.querySelector(".card").innerHTML = `
      <div class="card-body">
        <h5 class="card-title">${profile.name}</h5>
        <p class="card-text">나이: ${profile.age}세</p>
        <p class="card-text">단체: ${profile.organization} (${profile.grade})</p>
        <p class="card-text">관심 분야: ${profile.development}</p>
      </div>
    `;
  }

  this.init();
}

 
복잡한 object state를 관리할 때 사용하도록 하자~

반응형

'<frontend> > vanilla-js-SPA-starter-kit' 카테고리의 다른 글

useEffect 구현  (0) 2024.02.08
바닐라 js SPA 스타터킷  (1) 2024.02.08
useState 구현  (0) 2024.02.01
profile

그럴듯한 개발 블로그

@donghyk2

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!