ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JavaScript] 25 Beginner JavaScript Project Ideas 첫번째 만들기
    JavaScript 2022. 1. 25. 22:41

    첫 번째 프로젝트 살펴보기

    눌러서 확인해보세요!

    클릭을 할 때마다 랜덤으로 배경색이 변하는 페이지입니다.



    프로젝트 설정하기

    저는 린트를 한번 맛본 이후로 빠져나오지 못하는 삶을 살게 되었습니다...
    따라서 eslint와 prettier를 설치하기로 했습니다.
    또한 scss를 적극적으로 활용하고 싶었습니다. 하지만 scss를 사용하기 위해서는 트랜스파일링이 필요합니다.
    때문에 webpack이나 parcel을 사용해 번들링을 할 필요가 있었습니다.
    parcel이 가볍게 쓰기에는 훨씬 편하기 때문에 parcel를 사용해서 번들링을 진행하였습니다.



    25 Beginner JavaScript Project 에서 계속 사용할 lint 설정하기

    .pretterrc

    {
      "printWidth": 100,
      "tabWidth": 2,
      "singleQuote": true,
      "trailingComma": "all",
      "bracketSpacing": true,
      "semi": true,
      "useTabs": false,
      "endOfLine": "auto"
    }

    .eslint.json

    {
        "env": {
            "browser": true,
            "es2021": true
        },
        "extends": [
            "airbnb-base"
        ],
        "parserOptions": {
            "ecmaVersion": "latest",
            "sourceType": "module"
        },
        "rules": {
          "import/extensions": 0,
          "import/no-unresolved": 0,
          "import/prefer-default-export": 0,
          "no-underscore-dangle": 0,
          "implicit-arrow-linebreak": 0
        }
    }

    진입점 만들어주기

    spa와 비슷한 형태로 프로젝트를 진행합니다. 따라서 index.html에 #app 이라는 진입점을 만들어 줍니다.

    index.js

    다음으로 index.js입니다.
    #app을 찾은 다음 App컴포넌트에 전달합니다.
    이 때 엘리먼트가 존재하지 않을 경우를 위한 방어 코드를 작성합니다.
    다음으로 즉시 실행 함수로 감싸주었습니다.
    이것은 처음 알게 된 부분인데 new App()과 같은 형태로 바로 작성한다면 린트 에러를 만나게 됩니다.
    해결하기 위한 방법으로는 인스턴스를 변수에 할당하는 방법이 있겠지만 이 또한 사용하지 않는 변수를 생성하기 때문에 린트 에러가 납니다.
    따라서 즉시 실행 함수를 사용해주었습니다.

    import App from './components/App.js';
    
    const NO_ENTER_POINT = '진입점이 존재하지 않습니다!';
    const $app = document.querySelector('#app');
    
    if (!$app) {
      throw Error(NO_ENTER_POINT);
    }
    (() => new App({ $target: $app }))();



    방어 로직 설계하기

    function 키워드의 경우 일반 함수 선언문처럼 동작할 수도 있지만 생성자 함수로써 동작할 수도 있습니다.
    우리가 원하는 결과는 생성자 함수로써 동작하는 function입니다. 따라서 이를 위한 방어 로직을 만들어 줍니다.

    import { NO_CONSTRUCTOR } from './constants';
    
    export const isCalledWithNew = (constructor) => {
      if (!constructor) {
        throw Error(NO_CONSTRUCTOR);
      }
    };



    왜 상태에 중점을 둘까?

    현대의 웹에 들어서 가장 큰 차이점이라 한다면 상태에 따라 렌더링을 한다는 점 일 것입니다. 이로 인해 MV* 패턴이 등장하게 되었으니까요.
    점점 웹의 생태계가 커져감에 따라 뷰와 모델의 제어를 컨트롤러가 하게 되는데, 때문에 컨트롤러의 역할이 너무 커지게 되었습니다. 또한 프론트엔드 생태계에서 또한 뷰와 사용자와 직접 인터렉션이 맞닿아 있기 때문에 뷰와 컨트롤러의 경계가 모호할 수도 있다는 생각을 합니다.
    마지막으로 jQuery입니다.
    왜 각광받던 jQuery가 점점 사용되지 않을까요? 바로 어디서 값이 변경되었는지 디버깅하기 어렵기 때문이라 생각합니다. 즉 DOM과 너무 밀접하게 닿아있고, 사용자의 행위에 직접 DOM의 상태가 바뀌기 때문에 서로 다른 엘리먼트 간의 상태변화를 잘 파악하기가 어렵습니다.
    따라서 모던 웹의 경우에는 데이터에 초점을 맞추어 웹이 동작하며 데이터가 바뀌는 행위에 집중을 합니다. 즉, 상태에 따라 DOM이 알아서 변화하는 것이죠.



    App.js 만들기

    이제 그렇다면 이 웹에서는 어떤 상태가 필요할까요?
    바로 배경색입니다. 따라서 인스턴스의 state에 colors라는 프로퍼티를 만들어준 후 r, g, b값을 0으로 초기화했습니다.
    다음으로 상태에 따라 화면이 변화하기 때문에 setState가 호출이 될 때 _render()가 실행되도록 했습니다.
    또한 버튼의 행위를 상위 컴포넌트에서 제어할 수 있도록 콜백 함수를 전달해 주었습니다.

    App.js

    import '../styles/index.scss';
    import { isCalledWithNew } from '../utils';
    import Button from './Button';
    
    function App({ $target }) {
      this.$target = null;
      this.state = {
        colors: { r: 0, g: 0, b: 0 },
      };
      this._setState = (nextState) => {
        this.state = nextState;
        this._render();
      };
    
      isCalledWithNew(new.target);
      this.$target = $target;
    
      (() =>
        new Button({
          $target,
          text: 'click me!',
          onBtnClick: () => {
            const createRandomRgb = () => Math.round(Math.random() * 255);
            const r = createRandomRgb();
            const g = createRandomRgb();
            const b = createRandomRgb();
            this._setState({ ...this.state, colors: { r, g, b } });
          },
        }))();
    
      this._render = () => {
        const { r, g, b } = this.state.colors;
        this.$target.style.backgroundColor = `rgba(${r},${g},${b})`;
      };
    }



    Button컴포넌트 및 타입 체크

    다음으로 버튼 컴포넌트입니다.
    버튼 컴포넌트에서는 상태를 주지 않았고, 버튼에 들어갈 텍스트, addEventListener의 콜백함수를 상위 컴포넌트에서 props로 받아 온다는 것이 특징입니다.
    또한 자바스크립트는 아무래도 동적 타입이다 보니 어떠한 타입으로 값이 들어올지 생각하기 어렵습니다. 또한 잘못된 타입이 들어왔을 경우 예상치 못한 에러를 만들 수 있습니다. 따라서 최소한의 타입 방어를 해주기 위한 함수를 만들어 주었습니다.

    function Button({ $target, text, onBtnClick }) {
      this.$target = null;
      this.text = null;
      this.onBtnClick = null;
    
      isCalledWithNew(new.target);
      this.$target = $target;
      this.text = text;
      this.onBtnClick = onBtnClick;
    
      const $button = document.createElement('button');
      $button.setAttribute('type', 'button');
      $target.appendChild($button);
    
      this.render = () => {
        this._verifyParams();
        $button.innerText = this.text;
      };
    
      this._verifyParams = () => {
        if (typeof this.text !== 'string') {
          return console.warn(`버튼안의 ${text}는 문자여야 합니다.`);
        }
        if (typeof this.onBtnClick !== 'function') {
          return console.warn('onBtnClick은 함수여야 합니다.');
        }
        return true;
      };
      $button.addEventListener('click', this.onBtnClick);
      this.render();
    }



    회고

    처음 시작하는 프로젝트다 보니 MV*패턴을 사용하는 간략한 이유와 기타 설정들까지 작성하느라 글이 길어진 것 같습니다.
    하지만 어떻게 등장하게 되었는지 이해하다 보면 React와 같은 프레임워크나 라이브러리들이 어떤 방향을 가지며, 사람들이 왜 좋아하는지 알게 되는 것 같습니다.
    vanillaJs는 프론트엔드의 기본이기 때문에 앞으로 계속 웹을 만들어가면서 JavaScript역량을 키워나기야할것 같습니다.

    댓글

Designed by Tistory.