바닐라JS로 상태 기반 렌더링 구현

현수·2023년 5월 25일
0
post-thumbnail

바닐라 JS는 리액트나 jQuery같은 프레임워크나 라이브러리를 사용하지 않는 순수 자바스크립트를 의미합니다.

서문

나는 리액트로 프론트엔드 개발을 처음 접했다. 그리고 나중에 바닐라JS 개발을 접해보니 리액트에서는 느낄 수 없었던 불편한점을 느꼈고 리액트가 정말 편리한 도구임을 알게되었다. 그리고 바닐라JS에 리액트 아키텍쳐 패턴을 적용하는 방법을 이해하면 리액트의 기능을 보다 잘 이해하고 추후 바닐라JS로 개발할 일이 있을 때 효율적으로 개발할 수 있을 것 같아 "바닐라JS로 리액트처럼 개발하기"를 시작하게되었다.

해당 주제는 개발자 황준일님께서 블로그에 체계적으로 정리를 하신 부분이 있고 해당 내용을 참고하였습니다.

상태 기반 렌더링, 선언형 프로그래밍

바닐라JS 개발에 있어서 가장 불편했던 점이다. 리액트는 상태(state) 기반 렌더링선언형 프로그래밍으로 사용할 수 있게 구현되어있다. 선언형 프로그래밍은 무엇(What) 이 중요하며 리액트에서는 상태가 해당한다. 상태을 변경시키면 상태를 사용하고 있는 요소들이 자동으로 리렌더링된다. 상태를 포함하고 있는 요소에 접근하고 변경하고 렌더링하는 과정은 추상화되어 리액트에서 관리하고 개발자는 상태 관리에만 집중할 수 있게된다.

반면에 바닐라JS는 아키텍처를 설계하지 않는 이상 어떻게(How) 위주의 선언형 프로그래밍으로 작성하게된다. 상태를 변경하고 싶다면 상태를 사용하고있는 요소들을 일일이 DOM API로 접근해 상태를 변경하고 렌더링을 시켜주는 과정을 개발자가 작성해야된다.

React 구조 파악하기

바닐라JS로 리액트 기능을 구현하기위해 리액트가 어떻게 동작하는지 먼저 파악한다. 그리고 함수 형태 보다 클래스형태가 구현에 있어 더 간단할 것으로 보여 클래스 형태를 기준으로 분석한다.

선언

클래스 기능을 사용하여 Component를 상속 받아 생성한뒤 render() 메소드를 통해 요소를 생성한다.

state

클래스 내부 constructor에서 super() 메서드에 props를 전달해 부모인 Component의 constructor에 인자를 전달한다. 현재 컴포넌트의 state는 객체 형태의 프로퍼티 state를 통해 관리한다. state 변경은 부모인 Component의 setState 메서드를 통해 변경한다.

props

상위 컴포넌트에서 하위 컴포넌트로 넘겨진 props는 부모 클래스인 Component의 props 프로퍼티로 접근 가능하다.

이벤트 핸들링

리액트는 특정 요소의 특정 이벤트가 발생했을때 수행하는 함수를 지정할 수 있다.

// App.js
import React  from 'react';
import Counter from './Counter';

const App = () => {
  return <Counter appName={"this is app name"} />;
}
export default App;
//Counter.js
import React, {Component} from 'react'

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {    // state
      increaseNum : 0,
      decreaseNum : 100
    };
  }

  render(){
    const { appName }  = this.props;    // props
    const {increaseNum, decreaseNum} = this.state;
    return(
      <div>
        <div>앱 이름: {appName}</div>
        <h1>증가하는 값 : {increaseNum}</h1>
        <h2>감소하는 값 : {decreaseNum}</h2>
        <button
          onClick={()=>{    // 이벤트 핸들링
            this.setState({
              increaseNum : increaseNum + 1,
              decreaseNum : decreaseNum - 1
            });
        }}
        >
          Increase / Decrease
        </button>
      </div>
    );
  }
}

export default Counter;

Vanilla JS로 구현하기

클래스와 모듈 분리 없이 오직 state와 state 변화에 따른 렌더링을 구현하면 아래와 같다. props 또한 구현에서 제외 되었다.

구현 설명

  • root 라는 div 요소에 원하는 요소를 생성하는 SPA(Sing Page Application) 방식
  • setState으로만 상태를 변경할 수 있게하였고 setState 가 실행되면 재렌더링

단점

  • 추상화되어 있지 않아 재사용하기 어렵고 역할에 따라 컴포넌트로 분리하기도 어려움
  • 렌더링마다 요소에 이벤트가 추가되며 최초 렌더링시에만 이벤트가 등록되도록 해야함
// HTML은 빈 div 하나만 생성하고 root id로 접근
const $root = document.getElementById("root")

// 초기 state
let state = {
  increaseNum: 0,
  decreaseNum: 100
}

// 렌더링 함수
const render = () => {
  const { increaseNum, decreaseNum } = state;
  // HTML 내부에 요소를 채움
  $root.innerHTML = `
    <div>
      <div>앱 이름: {appName}</div>
      <h1>증가하는 값 : ${increaseNum}</h1>
      <h2>감소하는 값 : ${decreaseNum}</h2>
      <button id="eventBtn">
        Increase / Decrease
      </button>
    </div>
  `;
  // 버튼에 클릭 이벤트 추가
  document.getElementById("eventBtn").addEventListener("click", () => {
    // setState로 state 변경과 렌더링 요청
    setState({
      increaseNum : increaseNum + 1,
      decreaseNum : decreaseNum - 1
    })
  })
}

// state 변경 및 렌더링
const setState = (newState) => {
  state = { ...state, ...newState }
  render();   // state가 변경되었으니 재렌더링
}

// 첫 렌더링
render();

레퍼런스

[React] 클래스형 컴포넌트에서 state 사용하기

Vanilla Javascript로 웹 컴포넌트 만들기

0개의 댓글