웹 게임을 만들며 배우는 React (Webpack과 끝말잇기)

짜스의 하루 ·2024년 5월 18일

Webpack이란 ?

webpack은 여러 개의 모듈(javascript, css, html, image 등)을 하나의 javascript 파일로 묶어주는 모듈 번들러(bundler)이다.

React를 통해 개발을 한다면 기능을 여러 컴포넌트로 분리할 것이다(페이스북의 컴포넌트가 2만개가 넘는다고 한다..).
webpack은 이 분리된 자원들을 하나의 javascript로 변환해 준다. 또 webpack은 JSX를 해석해 주는 babel을 적용할 수 있고, 코드 최적화 수행, console.log()와 같이 실제 서비스에서는 필요 없는 코드를 자동으로 제거하는 등 여러 기능을 사용할 수 있다.

일반적으로 웹팩을 사용하여 여러 개의 JavaScript 파일을 번들링하고,그 결과물인 하나의 JavaScript 파일을 HTML 파일에서 로드한다. 이를 통해 HTML 파일은 하나의 자바스크립트 파일만을 로드하여 페이지를 렌더링할 수 있다.

  • 웹팩 설정 수정: 웹팩 설정 파일을 수정하여 여러 개의 JavaScript 파일을 번들링하고, 단일 JavaScript 파일로 출력하도록 설정합니다. 주로 entry 포인트와 output 설정을 수정하게 된다. (entry는 어떤 js, jsx파일들, output는 하나의 js파일로 합칠래!를 의미한다고 보면 된다)

  • HTML 파일 수정: 번들링된 JavaScript 파일을 로드할 HTML 파일에는 단일 <script>태그를 추가하고, 해당 태그의 src 속성을 번들링된 JavaScript 파일의 경로로 설정한다. 이 파일은 주로 app.js와 같은 이름으로 생성된다.

이후에는 웹팩을 실행하여 번들링된 JavaScript 파일을 생성하고, HTML 파일을 브라우저에서 열어서 정상적으로 작동하는지 확인하면 된다. 이렇게 하면 여러 개의 JavaScript 파일을 웹팩으로 합친 후, HTML 파일에서 단일 파일을 불러와서 사용할 수 있다!

webpack 속성

webpack에서 기본적으로 설정해 주어야 할 중요한 속성은 다음 4가지 정도이다.

  • Entry : webpack을 통해 변환하려는 자원의 최초 진입점
  • Output : Webpack이 변환한 파일의 경로(이름)
  • Loader : webpack이 javascript 외 다른 자원(html, css, image 등)을 변환할 수 있도록 해줌
  • Plugin : 추가적인 기능, 주로 결과물의 형태를 바꾸는 역할에 사용

Webpack을 이용한 React 개발

npm init을 통해 Node.js 프로젝트를 초기화하여 package.json 파일을 생성
npm i react react-dom을 통해 react 환경 설치
npm i -D webpack webpack-cli을 통해 webpack 설치!

를 하고 나면, package.json 파일에
모두 설치가 된 것을 확인할 수 있다.

webpack.config.js 작성

entry & output

  • mode -> development/production 모드를 설정할 수 있다.
  • entry (입력) -> entry는 변환할 대상을 지정하는 것으로 시작점인 ./client로 설정하자
    (client 파일 안에 다른 js파일들을 지정해 두었기 때문이다)
  • output (출력) -> output은 변환된 결과물의 파일 이름을 설정해 주면 된다.
    path도 설정해 줄 수 있지만 default는 dist 디렉터리이다.

그럼 webpack을 실행해 보자.
실행을 해보면 에러가 발생하는데,

ERROR in ./src/index.js 5:16
Module parse failed: Unexpected token (5:16)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import App from './App';
|

ReactDOM.render(, document.getElementById('root'));
에러를 보면 JSX 코드 부분을 webpack에서 처리하려면 적절한 loader가 필요하다고 한다.

loader (with babel)
loader는 webpack이 javascript만 변환할 수 있기 때문에 다른 자원을 load 할 수 있도록 도와주는 기능이다.
JSX 문법을 변환하기 위해서는 babel과 특정 preset들이 필요하다.

babel-loader
@babel/preset-env
@babel/preset-react

npm install --save-dev babel-loader @babel/preset-env @babel/preset-react를 실행하고,
다시 webpack을 실행해보면,

요런 성공적인 결과! 를 얻을 수 있다.

이제 webpack도 연결해 보았다! 끝말잇기를 만들어보자!


wordRelay.jsx 파일에서 만들면 된다.

우선, 각각의 파일에 대해서 간단하게 설명해보겠다

1 . webpack.config.js

webconfig.js는 일반적으로 웹 애플리케이션의 환경 설정을 담당하는 파일이다. 웹팩을 사용하여 React 애플리케이션을 개발할 때 사용할 수 있는 설정 파일을 나타낸다.

여기서 중요한 부분은
entry, module, output이다.
--> 어떤 어떤 파일들을 (js, jsx) 이러한 module을 거쳐서 app.js로 만들거야~ 를 나타낸다.

2 . node_modules
: 폴더는 Node.js 프로젝트에서 사용하는 외부 패키지들이 설치되는 디렉토리이다. 이 폴더는 주로 프로젝트에 필요한 외부 라이브러리나 모듈을 관리하고 저장하는 공간으로 사용된다.

일반적으로 프로젝트 루트 디렉토리에서 npm install 명령어를 실행하면, package.json 파일에 명시된 종속성(dependencies)들이 node_modules 폴더에 설치된다. 또한, 이러한 외부 패키지들의 버전 관리도 package.json 파일에 자동으로 기록되므로, 프로젝트를 다른 환경으로 옮겨가거나 공유할 때 의존성 관리가 용이해진다.

3 . client.jsx

  • React 애플리케이션의 진입점(entry point)을 설정하는 부분이다!
    (client.jsx에 wordRelay.jsx를 이미 불러놨기 때문에, webpack.config.js에는 따로 추가하지 않아도 된다!)
  • React와 createRoot 함수를 react 패키지에서 import 하고, react-dom/client 패키지에서 createRoot 함수를 import 한다. 이 함수는 React 애플리케이션의 루트(root)를 생성하고, 해당 루트에 애플리케이션을 렌더링하는 역할을 한다.
  • WordRelay 컴포넌트를 현재 디렉토리에 있는 wordRelay.js 파일에서 import 한다. 단어 릴레이 게임을 구현하는데 사용할 예정이다!
  • document.querySelector('#root')를 사용하여 HTML 문서 내에서 id가 'root'인 요소를 선택한다.
    --> 보통 React 애플리케이션은 HTML 문서 내에 하나의 루트 요소를 가지고 있는데, 이곳에 애플리케이션을 렌더링한다.

4 . package.json 은 Node.js 프로젝트의 메타 데이터와 프로젝트에 대한 구성 정보를 담고 있는 파일이다.
주로 프로젝트 루트 디렉토리에 위치하며, 프로젝트를 관리하고 실행하는 데 필요한 여러 설정과 의존성 패키지 정보를 포함한다.

  1. 이제 드디어 wordRelay.jsx 에 코드를 작성해보자!!!!!!!!!!!!!!!!

먼저 class로 끝말잇기를 만들어보자

const React = require('react');
const { Component } = React;

class WordRelay extends Component {
  state = {
    word: '이서연',
    value: '',
    result: '',
  };

  onChangeInput = (e) => {
    this.setState({ value: e.target.value });
  };

  inputRef = (c) => {
    this.input = c;
  };

  onSubmitForm = (e) => {
    e.preventDefault();
    const { word, value } = this.state;

    const hasInvalidChars = /[^가-힣]/.test(value);
    if (hasInvalidChars) {
      this.setState({
        value: '',
        result: '오답! (숫자나 기호가 포함되어 있습니다)',
      });
      this.input.focus();
      return;
    }

    if (word.charAt(word.length - 1) === value.charAt(0)) {
      this.setState({
        word: value,
        value: '',
        result: '정답!',
      });
      this.input.focus();
    } else {
      this.setState({
        value: '',
        result: '오답!',
      });
      this.input.focus();
    }
  };

  render() {
    return (
      <div>
        <div>{this.state.word}</div>
        <form onSubmit={this.onSubmitForm}>
          <input
            ref={this.inputRef}
            type="text"
            value={this.state.value}
            onChange={this.onChangeInput}
          />
          <button type="submit">입력</button>
        </form>
        <div>{this.state.result}</div>
      </div>
    );
  }
}

module.exports = WordRelay;

여기서 꼭 봐야할 부분은,

const React = require('react');
const { Component } = React;
module.exports = WordRelay;

이다.

  • require('react') : 이 코드는 Node.js의 require 함수를 사용하여 'react' 모듈을 불러온다. 이 모듈은 React 라이브러리를 제공한다.

  • { Component } = React: 이 코드는 구조 분해 할당을 사용하여 React 모듈에서 Component 클래스를 추출한다. 즉, React.Component를 Component라는 이름으로 사용할 수 있도록 한다.

  • module.exports = WordRelay; : 이 코드는 현재 파일에서 WordRelay 변수(또는 클래스)를 외부로 내보낸다. 이를 통해 다른 파일에서 이 파일을 import할 때 WordRelay를 사용할 수 있다.

즉, 이 코드는 React를 사용하는 파일에서 WordRelay 클래스를 불러와서 사용할 수 있도록 모듈을 정의하고 내보내는 역할을 한다.
--> 필수로 작성해야 한다!

실행을 해보면

요로코롬 나온다.

위에서 정의한 것처럼, 글자만 가능하도록(숫자, 기호를 작성하면 오답 !), word의 마지막 글자와, value의 첫번째 글자가 같은지 비교하고, 같다면, 정답!을 외치도록 했다!

그런데, 여기서 불편한 점이 있다
무엇이 불편하냐고 한다면, 코드를 일일이 수정할 때마다, npx webpack을 해야 한다는 것이다..

진짜 첨에 조금 수정하고 결과는 확인해야 하니까 계속 npx webpack를 눌렀었다.. 🥲

이걸 해결하기 위해서 webpack-dev-server를 사용하면 된다!

webpack-dev-server 사용!

코드 변경을 감지하고 자동으로 빌드를 다시 실행하여 변경 사항을 즉시 반영한다. 또한 빠른 속도와 간편한 설정을 제공하여 개발 프로세스를 향상시킨다

npm install webpack-dev-server --save-dev

를 설치하고

npm install @pmmmwh/react-refresh-webpack-plugin --save-dev

이것도 같이 설치한다

@pmmmwh/react-refresh-webpack-plugin은 React 애플리케이션의 개발 시 빠른 개발 속도와 개선된 개발 경험을 제공하는 webpack 플러그이다.
이 플러그인은 React의 Hot Module Replacement (HMR)을 지원하여 코드 변경이 발생했을 때 페이지를 새로고침하지 않고도 즉시 변경된 내용을 적용할 수 있다.

요약하면, @pmmmwh/react-refresh-webpack-plugin은 React 애플리케이션의 핫 모듈 리로딩을 지원하는 webpack 플러그인이고,
webpack-dev-server는 webpack으로 빌드된 애플리케이션을 로컬 개발 서버에서 실행하여 개발 중에 변경 사항을 실시간으로 확인할 수 있도록 도와주는 도구이다.

간단하게 설명하자면, @pmmmwh/react-refresh-webpack-plugin:만 실행하면 기본 live-server에서 확인이 가능한거고, webpack-dev-server:를 하면, localhost:8080에서 확인가능하다. 라고 생각하면 된다

설치가 끝났다면,

속성을 추가해주면 된다.

devServer: 이 부분은 webpack 개발 서버의 설정을 구성한다.

  • devMiddleware: 개발 서버의 미들웨어 설정이다. 여기서는 publicPath를 /dist로 설정하여 정적 자원이나 번들링된 파일이 해당 경로에서 제공되도록 한다. 이렇게 하면 웹 서버가 /dist 경로의 파일들을 제공하게 된다.

  • static: 정적 파일의 제공을 설정합니다. directory 속성을 통해 webpack이 정적 파일을 제공할 디렉토리를 지정한다. 이 예제에서는 __dirname으로 현재 디렉토리를 지정한다,

  • hot: 핫 모듈 리로딩을 활성화하는 옵션--> 이를 true로 설정하면 webpack 개발 서버가 HMR을 사용하여 수정된 모듈을 자동으로 업데이트한다.

plugins: 이 부분은 webpack 플러그인을 설정하는 부분이다.

  • ReactRefreshWebpackPlugin: 이 플러그인은 React 애플리케이션에서 핫 모듈 리로딩을 지원하기 위해 사용된다. 위에서 설정한 개발 서버의 HMR 옵션과 함께 사용되어, 수정된 React 컴포넌트를 실시간으로 반영할 수 있도록 돕는다.

class -> hooks로 다시 작성!

이제 class로 작성해 두었던 코드들을 hooks로 변경해 보겠다!

  • useState 훅 사용: useState 훅을 사용하여 각각 word, value, result 상태를 정의한다. useState 훅은 함수 컴포넌트에서 상태를 추가할 수 있도록 해준다.

  • onChangeInput 함수: input 요소의 값이 변경될 때마다 호출되는 함수이다. 입력값을 사용자가 입력한 값으로 설정한다(e.target.value)

  • onSubmitForm 함수: form 요소가 제출될 때 호출되는 함수이다. 먼저 입력값이 유효한지 검사하고, 유효하지 않으면 오류 메시지를 설정한다. 그렇지 않은 경우 입력값이 정답인지 확인하고 결과를 설정한다.
    --> 입력값이 정답이면 word 상태를 변경하고, 결과를 '통과!'로 설정합니다. 입력값이 오답이면 결과를 '다시 작성하세요!'로 설정합니다.

  • return 문: JSX를 반환한다. 현재 단어를 보여주고, form을 통해 사용자에게 입력을 받는다. 입력값이 제출되면 결과가 표시된다.

여기서 잠깐 ! 컨트롤드 인풋 언컨트롤드 인풋에 대해서 이해해보자

제어 컴포넌트(Controlled Components):

  • 입력 폼 요소의 값(value)을 React 컴포넌트의 state와 동기화하여 관리한다.
  • 폼 요소의 값이 변경될 때마다 state를 업데이트하여 React가 실시간으로 상태를 감지하고 관리한다.
  • onChange 이벤트 핸들러를 사용하여 입력 값이 변경될 때마다 state를 업데이트하고,
    React의 단방향 데이터 흐름을 따르기 때문에 예측 가능하고 제어 가능한 상태를 유지할 수 있다.

비제어 컴포넌트(Uncontrolled Components):

  • 입력 폼 요소의 값을 React 컴포넌트의 state로 관리하지 않는다.
  • Refs를 사용하여 폼 요소에 직접 접근하고 DOM으로부터 값을 가져온다.
  • React는 폼 요소의 상태를 추적하지 않으므로 값의 변경을 감지하거나 업데이트하지 않는다.
  • 대신에 DOM의 상태를 직접 조작하고 관리한다.
  • 일반적으로 React에서는 가능한한 제어 컴포넌트를 사용하여 상태를 관리하고 React의 장점을 최대한 활용하는 것을 더 선호한다.

내 코드에서 살펴보자면, 위의 코드들은 제어 컴포넌트이다. state를 사용해서 값을 변경하고, 관리하며, onchange, onsubmit 이벤트를 활용해서 값의 변경을 state를 이용해서 업데이트를 해주기 때문이다.

그럼 비에저 컴포넌트로 변경하면 어떻게 될까?

import React, { useRef } from 'react';

const WordRelay = () => {
  const wordRef = useRef(null);
  const resultRef = useRef(null);

  const onSubmitForm = (e) => {
    e.preventDefault();

    const word = wordRef.current.value;
    const value = e.target.value;

    const hasInvalidChars = /[^가-힣]/.test(value);
    if (hasInvalidChars) {
      value = '';
      resultRef.current.innerText = '오답! (숫자나 기호가 포함되어 있습니다).';
      return;
    }

    if (word.charAt(word.length - 1) === value.charAt(0)) {
      wordRef.current.value = value;
      value = '';
      resultRef.current.innerText = '통과!';
    } else {
      value = '';
      resultRef.current.innerText = '다시 작성하세요!';
    }
  };

  return (
    <>
      <div ref={wordRef}>이서연로 끝나는 말은? </div>
      <form onSubmit={onSubmitForm}>
        <input type="text" ref={resultRef} />
        <button type="submit">입력</button>
      </form>
      <div ref={resultRef}></div>
    </>
  );
};

export default WordRelay;

대충 이렇게 이루어진다고 생각하면 된다.
Refs를 사용하여 input 요소의 값을 직접 가져와서 처리하며, Refs를 통해 DOM 요소에 접근하여 값을 가져오고 변경한다. React는 입력 값의 변경을 추적하지 않으므로 값의 업데이트를 직접 수행해야 한다는 조금의 번거로움도 있다.

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글