📚 서론

더 나은 강점을 갖기 위해 스터디를 기획했다. 시작으로 React 가 눈에 띄었다. 어쩌다 보니 사용하게 되었으나 자세히 알지 못했던 이 React, 같은 크루원들과 동기부여를 나누며 React 공부를 시작하게 되었다.

📘 create-react-app 설치

  • npx 사용시
npx create-react-app my-app
cd my-app
npm start
  • npm 사용시
npm init react-app my-app
  • build
npm run build
  • deploy
npm i -g serve
serve -s build

📘 기본 개념

📗 npm vs npx


출처 : 모던 자바스크립트로 배우는 리액트 입문

리엑트를 시작하는데 npm 을 통한 방법과 npx 를 통한 방법이 있었다. 이 둘은 어떤 차이가 있을까?

npm

node.js 의 자동화된 의존성과 패키지 관리를 위한 패키지 매니저다. npm install 을 통해 원하는 패키지를 로컬 node_modules 에 설치할 수 있고 버전을 선택할 수 있다.

npm i <모듈명> -g

위의 명령어를 통해 글로벌 공간에 모듈을 설치하여 프로젝트마다 같은 모듈을 공유해서 사용할 수 있다. 그러나 이 방법은 모듈이 업데이트 되었는지 확인할 수 없고 업데이트 진행시 다른 프로젝트에 side effect 가 발생할 수 있다. 또한 create-react-app 같은 보일러 플레이트에 치명적이다. (최신버전 설치를 매번 해줘야 해서 번거롭다)

보일러 플레이트 : 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말한다.

npx

그래서 npm 5.2 버전 이후로 npx 가 기본 패키지로 제공되기 시작했는데 npx 는 모듈을 로컬에 저장하지 않고 매번 최신 버전의 파일만을 임시로 불러와 실행 시킨 후, 다시 그 파일은 없어지는 방식으로 모듈이 돌아가게 되었다.

npx 는 npm 을 더욱 편리하게 사용하기 위해 나온 도구다.

📗 Babel

모듈 핸들러가 여러 파일을 하나로 모아준다면 트랜스 파일러자바스크립트 표기법을 브라우저에서 실행할 수 있는 형태로 변환해준다.

리액트는 js 파일에 JSX 표기법이라 부르는 특수한 규칙을 따르는 작성 방법으로 코드를 기술한다. 이런 코드도 브라우저가 인식할 수 있는 형태로 자동 변환된다. 마치 Link 컴포넌트를 js 파일에서 사용했는데 개발자모드를 보면 html 에 a 태그가 사용되어 있는 것처럼 말이다. 트랜스 파일러바벨 에 대해 알아보자.

node.js 에서 require 과 import 구문의 차이를 기억할 것이다.

기본적으로는

// ES5
const react = require('react'); 

방식을 사용하나 이제는 pacakge.json 에

"type": "module",

를 추가한다면 아래와 같은 방식으로 모듈을 가져와 사용할 수 있었다.

// ES6
import react from 'react';

react 도 creat-react-app 을 통해 코드를 실행하는 것을 보면

이렇게 package.json 에 type: module 을 추가한 적도 없는데 import 문법을 사용하고 있는 것을 볼 수 있다. 이걸 가능하게 해주는 것이 Babel 이라고 한다.

Babel : JavaScript 의 컴파일러로 React 의 JSX 문법을 Vanilla Javascript 로 변경하거나 최신버전의 문법을 구 버전 문법으로 변환해준다.

React에서는 JSX 라는 React만의 특별한 문법을 사용한다. 하지만 JSX 는 브라우저가 잘 모르는 문법이기 때문에 브라우저가 이해할 수 있는 Vanilla JS로 변경해 주어야 하며 이것을 babel이 해준다.

최신 문법의 JS를 낮은 버전의 JS로 변환해주는 역할도 한다.

babel 은 단순히 문법만 변환해주는 역할만 하기 때문에 최신 함수들을 사용하기 위해서는 object의 prototype에 붙여주는 역할이 필요하며 이를 polyfill이 해준다. 그래서 babel은 컴파일-타임에 실행되고 babel-polyfill은 런-타임에 실행된다.

📗 WebPack

자바스크립트는 import / export 구문이 있기전 script 태그를 사용하여 외부의 스크립트 파일을 가져와 사용할 수 있으나 개별의 파일 스코프를 갖지 않고 하나의 전역 객체를 공유하기에 같은 이름의 변수 이름을 사용시 가장 뒤에 쓰인 스크립트를 기준으로 적용된다.

IIFE(Immediately Invoked Function Expression) 즉시 실행함수 는 함수 외부에서 접근이 불가능하여 스코프 문제를 해결하는 듯 싶었으나 즉시 실행되기에 모듈화를 해결했다고 볼 수 없다.

이 모듈화 문제를 해결하는 방법으로 CommonJS 와 AMD 가 등장했다.

📕 CommonJS

Javascript 를 브라우저에서뿐 아니라 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하려고 조직한 자발적 워킹그룹이다.
CommonJS 의 Common 은 JS 를 브라우저에서만 사용하는 언어가 아닌 일반적인 언어로 사용할 수 있게끔 하겠다는 의지를 보인다.

  • export 키워드로 모듈을 만들고 require() 함수로 임포트하는 방식이다.
  • 전역 변수와 지역변수를 분리하여 모듈이 독립적인 실행환경을 갖는다.
  • 브라우저에서 필요한 모듈을 모두 받기까지 아무것도 할 수 없다.

CommonJS 모듈화

  • 스코프 : 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다.
  • 정의 : 모듈 정의는 export 객체를 이용한다.
  • 사용 : 모듈 사용은 require 함수를 이용한다.

브라우저

  • 클라이언트 측 애플리케이션, 브라우저는 HTML, CSS, JS 를 해설하고 실행하여 웹 애플리케이션을 렌더링하고 사용자의 동적 입력에 반응하여 페이지에서 필요한 작업 수행, 웹 페이지를 보여주는 역할을 한다.

서버 사이드 애플리케이션

  • 클라이언트로부터 요청을 받아 처리하고 그 결과를 다시 클라이언트에게 전달하는 역할을 한다.
  • 웹 서버에서 동작하며 사용자 요청에 따라 데이터 처리, 비즈니스 로직 실행, DB 와 상호작용등 주로 웹사이트 기능과 백엔드 부분 담당

데스크탑 애플리케이션

  • 사용자의 개인 컴퓨터에서 실행되는 소프트웨어
  • 워드 프로세서, 그림판, 게임등

브라우저용

서버사이드용

📕 AMD(Asyncronous Module Definition)

  • 비동기로 로딩되는 환경에서 모듈을 사용하는 것이 목표다.
  • define 함수 내에 코드를 작성하여 스코프 분리가 가능하다.

브라우저용

서버사이드용

define() 함수

define(id?, dependencies?, factory);  
  • 첫 번째 인수인 id 는 모듈을 식별하는데 사용한다.
  • 두 번째 인수는 정의하려는 모듈의 의존성을 나타내는 배열로 반드시 먼저 로드되어야 하는 모듈을 나타낸다.
  • 세번째 인수는 팩토리 함수로 모듈이나 객체를 인스턴스화하는 실제 구현을 담당한다.
    만약 팩토리 인수가 함수라면 싱글톤으로 한 번만 실행되고, 반환되는 값이 있다면 그 값을 exports 객체의 속성값으로 할당한다. 반면에 팩토리 인수가 객체라면 exports 객체의 속성값으로 할당된다.

예시)

define(['./foo.js', './boo.js'], function(foo, boo){
//
})

CommonJS 와 AMD 차이
모듈 명세의 차이는 모듈 로드에 있다.

  • 필요한 파일이 모두 로컬 디스크에 있어 바로 불러 쓸 수 있는 서버 사이드에서는 CommonJS 명세가 AMD 방식보다 간결하다.
  • 필요한 파일을 네트워크를 통해 내려받아야 하는 브라우저와 같은 환경에서는 AMD 가 CommonJS 보다 더 유연한 방법을 제공한다.

📕 동적 로딩

script 태그는 페이지 렌더링을 방해한다. script 태그의 HTTP 요청, 다운로드, 파싱, 실행이 일어나는 동안 브라우저는 다른 동작을 하지 않기에 script 태그를 가능한 body 태그의 마지막에 배치하기도 하는데 여전히 사용자의 첫 인터렉션이 가능하기까지 걸리는 시간은 줄지 않는다. 이는 페이지에 필요한 모든 JS 를 로딩하기 때문이다.

이에 대한 최적화 방법으로 동적 로딩(Dynamic Loading, Lazy Loading) 이 제시되었는데 페이지 렌더링을 방해하지 않으면서 필요한 파일만 로딩하는 방식이다.

var scriptEl = document.createElement('script');  
scriptEl.type = 'text/javascript';  
scriptEl.src = 'example.js';  
document.getElementsByTagName('head')[0].appendChild(scriptEl);  

📕 모듈화

스크립트 내부에서만 사용하는 변수, 함수들은 전역공간에 둘 필요가 없다. 전역 변수 남발로 인한 충돌은 유지보수에 막대한 영향을 끼치는데 스크립트의 모듈화는 이러한 문제를 방지한다.

기본적으로는 return 으로 외부에서 접근할 변수와 함수만 골라서 노출할 수 있고 외부에 노출할 필요가 없는 변수와 함수는 클로저를 이용하여 전역공간에 위치하지 않고도 접근할 수 있다.

ES6 에서는 export 를 이용해 모듈로 만들고 import 로 가져온다.

math.js

export function sum(x, y) {
  return x + y;
}

app.js

import * as math from "./math.js";
// import {sum} from "./math.js"
// sum만 가져오고 싶다면 이렇게 사용할 수도 있다.

console.log(math.sum(1, 2));

초기 모듈 사용시

module.exports = {message: 'webpack'};  

alert(require('./examplemodule).message);  

ES6 에서 클라이언트 사이드 자바스크립트에서도 동작하는 모듈기능이 추가되었는데 script 태그에 type=module 속성을 추가하면 모듈을 사용할 수 있다.

<script type="module" src="./src/app.js"></script>

그러나 여전히 IE 같은 경우 모듈이 지원되지 않아 브라우저와 상관없이 사용할 수 있는 모듈이 필요했고 그에 대한 해법이 WebPack 이다.

📕 WebPack

공식 DOCS 를 방문해보자. 심지어 한글도 지원해줘서 읽기 편하다.


출처 : webpack.js.org

개발할때 파일을 나누고 프로덕션용으로 빌드할때는 파일 하나에 모으기 위해 js 파일이나 css 파일 등을 하나로 합치는 모듈 핸들러가 만들어졌고 그중 WebPack 이라 불리는 모듈 핸들러 에 대해 알아보자.

WebPack 은 Node.js 가 설치된 환경에서 실행된다. WebPack 은 하나의 시작점인 Entry Point 로부터 의존적인 모듈을 전부 찾아내 하나로 파일(Output) 을 만든다.

엔트리 파일

  • 의존 관계에 있는 다양한 모듈을 사용하는 시작점이 되는 파일

번들 파일

  • 브라우저에서 실행할 수 있게 모듈을 컴파일한 파일

컴파일

  • WebPack 의 컴파일은 엔트리 파일을 시작으로 의존 관계에 있는 모듈을 엮어서 하나의 번들 파일을 만드는 작업이다. JS 를 사용하는 HTML 코드는 컴파일 결과로 만들어진 번들 파일만 생성하면 된다.


출처 : NAVER D2 - WebPack

Loader

  • WebPack 이 웹 애플리케이션을 해석할 때 JS 파일이 아닌 것들을 변환할 수 있게 도와준다. 파일을 다른 언어에서 JS 로 변환하거나 인라인 이미지를 데이터 URL 로 로드할 수 있고 JS 모듈에서 직접 CSS 파일을 import 할 수 있다.
  • 다양한 리소스를 JS 에서 바로 사용할 수 있는 형태로 로딩하는 기능이다.
  • 로더를 이용해 React 의 JSX 형식도 사용할 수 있고 ECMAScript 2015 를 사용할 수 있게 컴파일하는 Babel 도 사용할 수 있다.
  • 다양한 언어와 언어 프로세서를 위한 로더

WebPack 예시

  • ES2015 import
  • CommonJS require()
  • AMD definerequire
  • css/sass/less 파일 내의 @import
  • 스타일 시트 url(...) 의 이미지 URL 또는 HTML <img src= ...> 파일

참고로 ES2015 가 ES6 이다. 이렇게 혼란에 빠질 수 있으니 연도를 붙이는 방법을 사용하는 것을 권장하기도 한다.

📕 WebPack 정리

WebPack 은 커스터마이징을 위한 강력하고 풍부한 API 를 제공하여 어떤 환경에서도 webpack 을 사용할 수 있도록 하며 개발과 테스트 및 프로덕션 작업 흐름을 유연하게 유지하도록 한다.

WebPack 은 비동기 I/O 와 다중 캐시 레벨을 사용하기에 컴파일 속도가 매우 빠르다.

모듈 의존성 관리가 편하고 로더를 통해 다양한 리소스를 효율적으로 활용할 수 있다.

📗 가상 DOM

기존 자바스크립트에서는 화면 요소를 변경시 DOM 을 직접 지정해서 바꿔 쓰는 처리를 했다.

id = nushida 를 갖는 요소 아래에 Hello World!! 라고 설정한 p 태그 삽입


📕 순수한 자바스크립트

var textElement = document.createElement("p");
textElement.textContent = "HelloWorld!!";
document.getElementById("nushida").appendChild(textElement);

📕 jQuery

var textElement = $("<p>").text("HelloWorld!!");
$(#nushida").append(textElement);

이러한 코드들은 렌더링 비용(화면 표시 속도) 에 문제가 발생하기에 쉽게 코드가 비대해져 어디에서 무엇을 하고 있는지 파악하기 어려운 단점이 있었다. 이런 문제를 해결하기 위해 가상 DOM 이 만들어졌다.

가상 DOM 은 자바스크립트 객체로 만들어진 가상 DOM으로 이를 통해 실제 DOM 과 차이를 비교하고 변경된 부분만을 실제 DOM 에 반영하여 DOM 조작을 최소화한다.

리액트, Vue 등 모던 자바스크립트 프레임워크나 라이브러리에서는 가상 DOM 을 제공하여 페이지 이동은 자바스크립트의 화면 치환으로 구현하나 렌더링 비용을 최소한으로 억제하여 쾌적한 웹 시스템을 제공한다.

📗 JSX 표기법

함수의 반환값으로 HTML 태그를 기술할 수 있고 그것을 컴포넌트로 다뤄 화면을 구성한다.

return 이후 행이 여럿일때 () 로 감싼다. 단, return 이후로는 한 개의 태그로 둘러싸여 있어야 한다.

import { Fragment } from 'react';

const App = () => {
	return (
      <Fragment>
    	<h1>안녕하세요</h1>
      	<p>잘 지내시죠?</p>
      </Fragment>
    );
};

이때 감싸는 태그는 HTML 태그이거나 비어 있는 태그 <> </> 혹은 Fragment 를 react 에서 import 하여 사용할수 있다.

📗 비트 Vite

앞에서 WebPack 과 Babel 을 조사해봤지만 FE 빌드 도구로 비트가 점차 떠오르는 추세이다. 비트는 웹팩을 사용해서 개발하는 것보다 속도가 압도적으로 빠르다.

웹팩을 사용해 FE 개발을 하는 경우 코드에 변경이 발생할시 서버를 다시 기동하지 않아도 다시 번들이 실행되고 업데이트 내용이 브라우저에 반영되나 규모가 커짐에 따라 번들로 묶는 시간이 늘어나는 문제가 발생했다.

비트는 개발환경에 따라 소스 코드를 번들하지 않고 빠르게 실행한다.

📗 기존 웹 시스템과 SPA 비교

기존 웹 시스템은 페이지를 이동할 때마다 서버에 요청을 전송하고 서버 측에서 HTML 파일을 반환하기에 페이지 이동시 잠깐 하얗게 보인다.

SPA 의 경우 다른 내용을 보고자 할때 필요한 데이터를 요청하여 비동기적 실행을 통해 데이터를 얻는다. 따라서 화면이 하얗게 변하지 않고 쾌적하게 페이지를 열람할 수 있다.
SPA 는 HTML 파일이 하나이며 JS 를 이용해 DOM 을 바꿔써서 화면 이동을 구현한다. 이는 화면 표시 속도를 높여준다.

📗 화살표 함수 표기법 특징

  1. function 을 이용하지 않고 함수 선언
const func = (value) => {
	return value;
}

이때 default 값을 = 기호로 넣어줄 수 있다.

const func = (value = '이름') => {
	return value;
}

객체 분할 할당시 기본값도 마찬가지로 넣어줄 수 있다. 속성이 존재할때는 해당 속성을 우선하고 존재하지 않은 경우 디폴트 값을 지정한다.

const obj = {
	name: 'john',
}

const { age = 20 } = obj;
  1. 인수가 하나일때 소괄호 생략 가능
const func = value => {
	return value;
}
  1. 인수가 2개 이상일때 소괄호로 감싸야 한다.
const func = (value1, value2) => {
	return value1 + value2;
}
  1. 처리를 한 행으로 반환하는 경우 중괄호와 return 생략가능
const func = (num1, num2) => num1 + num2;

return 을 생략하려면 중괄호도 같이 생략해야 한다.

  1. 반환값이 여러 행일 경우 () 로 감싸서 단일 행과 같이 모아 반환한다.
const func = (val1, val2) => (
  {
  	name: val1,
    age: val2,
  }
)

📗 ES5 -> ES6 예시

  • 모듈 export 시 하단에 export 혹은 export default 를 할 수 있다. (export default 는 한 파일에서 하나만 가능하며 이름 수정을 할 수 없다.)
  • require 에서 import 로 전환시 package.json 에 type: module 을 추가해야 하고 from 에 extension 확장자명을 명시해야 한다.

📔 레퍼런스

docs
Create React App
webpack.js.org
course
생활코딩 - react 2022 개정판
book
모던 자바스크립트로 배우는 리엑트 입문
blog
seizemymoment - npm vs npx 비교
ingg.dev - webpack
naver D2 - CommonJS 와 AMD
naver D2 - WebPack
react gitbook

📓 결론

이전에 리액트를 배우기 시작했을때 props 는 뭐고 state 는 뭔지 새로운 개념들의 숨쉴틈 없는 범람에 정신이 없어 학습에 어려움이 있었다. react hook 에 대해서도 자세히 알아볼 예정이긴 한데 이번 포스트를 작성하며 그 당시 Babel, WebPack 등의 개념에 대해 완전히 이해가 되지 않았던 부분들을 어느정도 채우게 되는 계기가 되지 않았나 싶다.

React 학습의 첫걸음을 드디어 딛게 되었다. 힘내보자.

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글