loader(로더) 설정하기

카테고리 Settings

📌 Loader(로더)란?

webpack은 기본적으로 Javascript, JSON 파일만 해석할 수 있다.

webpack이 로더를 사용하여 번들링 할 때에 다른 타입의 파일을 처리하거나 유효한 모듈로 변환하여 애플리케이션에서 사용할 수 있도록 도와준다.

계속 읽기

webpack 설정하기

카테고리 Settings

📌 webpack 이란?

모듈로 관리하는 파일들을 번들링 해주는 툴이다.

🔨 사용방법

명령어 환경 구성

Webpack 모듈 번들러를 명령어 환경에서 사용하기 위해 webpack, webpack-cli를 설치합니다.

1
$npm i -D webpack webpack-cli

번들 명령어 등록

package.json 파일에 webpack 번들링을 수행하는 bundle 명령을 등록합니다.

1
2
3
4
5
6
7
8
9
{
...
"scripts": {
"webpack:config": "webpack --target=browserslist --entry=./src/main.js --output-path=public",
"webpack:dev": "npm run webpack:config -- --mode=development",
"webpack:prod": "npm run webpack:config -- --mode=production"
},
...
}
  • entry 폴더와 output 폴더 경로를 지정해주면 된다.
  • dev는 개발환경 번들링 작업을 위한 명령이고 prod는 배포환경을 위한 번들링 작업이다. prod의 결과물은 배포 최적화되어 있다.
  • target 속성은 어떤 브라우저 환경을 대상으로 번들링할 것인지를 명시해주는 것이다. 프로젝트 루트 위치에 .browserslistrc 파일을 생성한다.
1
2
3
4
> 0.5% in KR
last 2 versions
not dead
ie 11

명령 실행

1
2
3
$npm run webpack:dev

$npm run webpack:prod

🔨 Webpack 구성 파일

기본적인 구성으로도 번들링 훌륭하지만 복잡한 구성을 위해서 구성파일을 별도로 작성해 관리하는 것이 좋습니다.

공통 구성 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack/common.js
import { resolve } from "node:path";

const commonConfig = {
target: "browserslist",
entry: {
main: resolve("src/main.js"),
},
output: {
path: resolve("public"),
filename: "[name].bundle.js",
},
};

export default commonConfig;

개발 구성 파일

1
2
3
4
5
6
7
8
9
10
// webpack/dev.js
import { merge } from "webpack-merge";
import commonConfig from "./common.js";

const devConfig = merge(commonConfig, {
mode: "development",
devtool: "eval-cheap-source-map",
});

export default devConfig;

빌드 구성 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// webpack/prod.js
import { merge } from "webpack-merge";
import commonConfig from "./common.js";

const prodConfig = merge(commonConfig, {
mode: "production",
devtool: false,
output: {
...commonConfig.output,
filename: "[name].min.js",
},
});

export default prodConfig;

개발, 빌드 명령 등록

1
2
3
4
5
6
7
// package.json
{
"scripts": {
"bundle": "webpack bundle -c webpack/dev.js",
"build": "webpack build --progress -c webpack/prod.js"
}
}

이제 기본 webpack 구성이 아닌 직접 생성한 파일을 통해서 번들링 작업을 해줄 수 있다.

🔨 Webpack 개발 서버 구성

Webpack 전용 개발 서버를 구동하기위해 패키지를 설치한다.

1
$npm i -D webpack-dev-server

서버 구성 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack/server.js
const devServer = {
host: "localhost",
port: 5000,
hot: true,
open: false,
compress: true,
liveReload: true,
static: ["public"],
historyApiFallback: true,
client: {
logging: "info",
overlay: true,
reconnect: 3,
},
watchFiles: {
paths: ["src/**/*.*", "public/**/*.*"],
},
};

export default devServer;

서버 구성 파일 불러오기

개발 환경에서 서버 구성 파일을 불러온다.

1
2
3
4
5
6
7
8
9
10
11
12
// webpack/dev.js
import { merge } from "webpack-merge";
import commonConfig from "./common.js";
import devServer from "./server.js";

const devConfig = merge(commonConfig, {
mode: "development",
devtool: "eval-cheap-source-map",
devServer,
});

export default devConfig;

서버 구동 명령어 등록

1
2
3
4
5
6
7
8
9
10
// package.json
{
"scripts": {
"start": "npm run server -- --open",
"dev": "npm run server",
"server": "webpack server -c webpack/dev.js",
"bundle": "webpack bundle -c webpack/dev.js",
"build": "webpack build -c webpack/prod.js"
}
}

HTML Entry 수정

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html lang="ko-KR">
<head>
<!-- ... -->
<script type="module" src="main.bundle.js"></script>
</head>
<!-- ... -->
</html>

댓글 공유

🚀 기능 요구 사항

어느 연못에 엄마 말씀을 좀처럼 듣지 않는 청개구리가 살고 있었다. 청개구리는 엄마가 하는 말은 무엇이든 반대로 말하였다.

엄마 말씀 word가 매개변수로 주어질 때, 아래 청개구리 사전을 참고해 반대로 변환하여 return 하도록 solution 메서드를 완성하라.

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

제한사항

  • word는 길이가 1 이상 1,000 이하인 문자열이다.
  • 알파벳 외의 문자는 변환하지 않는다.
  • 알파벳 대문자는 알파벳 대문자로, 알파벳 소문자는 알파벳 소문자로 변환한다.

실행 결과 예시

word result
“I love you” “R olev blf”

내 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function problem4(word) {
if (typeof word !== 'string') throw new TypeError('매개변수는 문자열 타입이여야 합니다.');
if (!word.length || word.length > 1000) throw new RangeError('매개변수는 1 이상 1,000 이하의 문자열이여야 합니다.');

const ALPHABET_LENGTH = 26;
const ASCII_NUMBER_CODE_UPPER_A = 65;
const ASCII_NUMBER_CODE_UPPER__Z = 90 ;
const ASCII_NUMBER_CODE_LOWER_A = 97;
const ASCII_NUMBER_CODE_LOWER__Z = 122;

const splitWords = [...word];
const uppers = Array.from({length:ALPHABET_LENGTH}, (_,i) => String.fromCharCode(ASCII_NUMBER_CODE_UPPER_A+i));
const lowers = Array.from({length:ALPHABET_LENGTH}, (_,i) => String.fromCharCode(ASCII_NUMBER_CODE_LOWER_A+i));

const changeOppositeStr = str => {
const codePosition = str.charCodeAt();

return (codePosition >= ASCII_NUMBER_CODE_UPPER_A && codePosition <= ASCII_NUMBER_CODE_UPPER__Z
? [...uppers].reverse()[uppers.indexOf(str)]
: codePosition >= ASCII_NUMBER_CODE_LOWER_A && codePosition <= ASCII_NUMBER_CODE_LOWER__Z
? [...lowers].reverse()[lowers.indexOf(str)]
: str);
}

return splitWords.map(splitedWord => changeOppositeStr(splitedWord)).join('');
}
  • ASCII 코드를 활용하여 AZ, az 를 담은 배열을 생성하였다.
  • map 메서드를 사용하여 각 요소를 반대편 문자열로 바꿔주었다.

🏓 소감

  • 리팩터링을 하기 전 코드는 ASCII 코드를 상수에 할당하지 않고 그대로 비즈니스 로직에 가져다가 사용하지 해당 숫자가 무엇을 의미하는지 파악하기 힘들어 상수에 할당하니 가독성이 높아졌다.
  • changeOppositeStr() 함수 내부에서 reverse()[] 이 부분이 중복되어 함수로 추출하려 했지만, 짧은 코드에 너무 많은 함수가 오히려 더 가독성을 해친다고 판단하여 그대로 두었다.
  • 로직을 ASCII 코드 대신 정규표현식으로 구현하였다면 비즈니스 로직이 훨씬 더 간결해질 것 같기에 다음에 문자열 문제가 나온다면 정규표현식으로도 도전해봐야겠다.

댓글 공유

🚀 기능 요구 사항

배달이가 좋아하는 369게임을 하고자 한다. 놀이법은 1부터 숫자를 하나씩 대면서, 3, 6, 9가 들어가는 숫자는 숫자를 말하는 대신 3, 6, 9의 개수만큼 손뼉을 쳐야 한다.

숫자 number가 매개변수로 주어질 때, 1부터 number까지 손뼉을 몇 번 쳐야 하는지 횟수를 return 하도록 solution 메서드를 완성하라.

제한사항

  • number는 1 이상 10,000 이하인 자연수이다.

실행 결과 예시

number result
13 4
33 14

내 코드

1
2
3
4
5
6
7
8
9
10
11
12
function problem3(number) {
if (number <= 0 || number > 10000 || number !== parseInt(number)) throw new Error('매개변수는 1이상 10,000 이하의 자연수만 입력 가능합니다.');

let clapCount = 0;
for (let i = 1; i < number + 1; i++) {
const splitStr = [...String(i)];

splitStr.forEach(elem => clapCount = elem === '3' || elem === '6' || elem === '9' ? clapCount + 1 : clapCount);
}

return clapCount;
}
  • 매개변수만큼 반복문을 돌면서 숫자 1부터 문자열로 변환한 다음 문자열에 3,6,9가 있으면 clapCount를 1 증가시켜주는 코드를 작성하였다.

🏓 소감

  • parseInt() 메서드 대신 +number를 사용하여 형변환을 시켰으면 어떨까하는 아쉬움이 남는다. 왜냐하면 parseInt() 메서드는 특정 진수법에 해당하는 정수로 반환하는 역할을 하기에 단순히 숫자로 형변환을 위함이 목적이라면 의미가 벗어난다고 생각했다. 마찬가지로 String() 메서드 대신 (i + ‘’)로 형변환 시켜야겠다.
  • 수강생들과 코드리뷰를 하면서 느낀점은 문자열을 다룰 때, 정규표현식을 사용할 수 있다면 좀 더 가독성이 좋은 코드를 작성할 수 있을 것 이라는 생각이 들었다. 지금부터라도 문자열 문제가 나온다면 정규표현식으로 풀어보는 연습을 해야겠다.

댓글 공유

올바른 리드미 작성법

카테고리 git

📌 리드미란?

리드미란, 다른 사람에게 나의 프로젝트가 얼마나 유용한지를 설명하기 위한 소개글이다.

  • 이 프로젝트를 가지고 무엇을 할 수 있는지?
  • 프로젝트를 어떻게 사용할 수 있는지?

위 두가지를 목적으로 작성하는 것이 바로 리드미이다.

💡 좋은 리드미 작성법

기본만 잘 따라도 좋은 리드미처럼 보일 수 있다.

  1. 프로젝트가 하는 일
  2. 프로젝트가 유용한 이유
  3. 프로젝트 시작하는 방법
  4. 프로젝트에 대한 도움을 받을 수 있는 곳
  5. 프로젝트를 유지하고 기여하는 사람

위 5가지가 github에서 언급하는 리드미 작성에 포함되어야 할 정보이다.

💼 오늘 할 일

[x] 리드미 작성 기준에 맞게 리드미 작성하기

bad-readme-case

위 예시는 내가 과제 제출을 위해 처음에 적었던 리드미이다. 이 리드미를 좋은 리드미 작성법에 맞게 개선해보자.

1. 프로젝트가 하는 일

이 프로젝트는 사용자가 다양한 상태의 업로드 버튼 UI가 필요할 때, 재사용 가능한 컴포넌트를 제공하는 프로젝트이다.

2. 프로젝트가 유용한 이유

  • 재사용이 가능한 업로드 버튼 UI를 사용할 수 있습니다.
  • 기본 제공되는 버튼 UI가 훌륭합니다.

3. 프로젝트 시작하는 방법

UploadButton

기본적인 사용법은 다음과 같습니다.

1
<UploadButton type="idle">업로드</UploadButton>

타입 설정

아래의 타입을 설정하면 다음과 같은 모양의 버튼 UI가 생성됩니다.

type shape
idle status=idle
pending status=pending
resolved status=resolved
rejected status=rejected
disabled status=disabled

4. 프로젝트에 대해 도움 받을 수 있는 곳

만약 프로젝트에 대해 도움이 필요하시다면 아래 이메일로 연락을 주세요!

klj9939@gmail.com

5. 프로젝트를 유지하고 기여하는 사람

https://github.com/loco9939
gitprofile

⭐️ 결과

https://github.com/loco9939/zero-base/tree/icon-upload-button/complete

🏓 소감

리드미란 처음 내 프로젝트를 다른 사람에게 알리는 첫 게시물이므로 사람들이 내 프로젝트를 어떻게 하면 편리하게 사용할 수 있을지, 또 사용하고 싶도록 만들지를 생각하면서 작성해야하는 것을 알게되었다.

이것을 정말 쉽지 않다. 게다가 내 프로젝트에 대해서 정리하여 남에게 설명하는 연습이 잘 되어있지 않아서 낯설고 힘들었지만 아직은 처음이니깐 앞으로 프로젝트를 하게된다면 오늘 배운 점을 고려하면서 작성하도록 노력할 것이다.

댓글 공유

boj-1157 단어공부

카테고리 Algorithm, boj

📌 문제

알파벳 대소문자로 된 단어가 주어지면, 이 단어에서 가장 많이 사용된 알파벳이 무엇인지 알아내는 프로그램을 작성하시오. 단, 대문자와 소문자를 구분하지 않는다.

입력

첫째 줄에 알파벳 대소문자로 이루어진 단어가 주어진다. 주어지는 단어의 길이는 1,000,000을 넘지 않는다.

출력

첫째 줄에 이 단어에서 가장 많이 사용된 알파벳을 대문자로 출력한다. 단, 가장 많이 사용된 알파벳이 여러 개 존재하는 경우에는 ?를 출력한다.

입력 출력
Mississipi ?
zZa Z
z Z
baaa A

내 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const input = require("fs").readFileSync("/dev/stdin").toString().trim();

const alphabets = [...new Set([...input.toLowerCase()])];
let max = 0;
let current = ''

alphabets.forEach((alphabet,i) => {
const regExp = new RegExp(alphabet, 'ig');
const count = input.match(regExp).length;
if (max < count) {
max = count;
current = alphabet.toUpperCase();
}
else if (max === count && alphabet.toUpperCase() !== current) current = '?';
})
console.log(current)
  1. 입력값의 알파벳을 소문자로 만들고 중복을 제거해주었다.
  2. 해당 알파벳을 forEach 문으로 순회하면서 초기에 입력값으로 주어진 문자열을 정규표현식으로 확인한다.
  3. 정규표현식에 매치되는 요소의 갯수가 최대값으로 갱신해주고 그 때의 알파벳을 대문자로 저장해둔다.
  4. 만약 새로운 알파벳의 갯수가 최댓값과 같고 현재의 알파벳 대문자와 current에 할당된 알파벳이 같으면 current에 “?”를 할당한다.

🏓 소감

문자열을 풀 때, 정규표현식을 사용하면 반복문을 줄이고 코드를 깔끔하게 하여 가독성을 키우는 연습을 하는데 도움이 될 것 같다.

주어진 테스트 케이스를 다 만족했지만, 제출했을 때 오답이라고 나와서 당황했다. 어떻게 하면 내 제출이 만족 못시키는 케이스가 있을까하고 생각해보면서 여러가지 상황을 제시하면서 테스트 케이스를 찾아내었다.

그 결과 처음 정규표현식을 생성할 때, alphabets 배열을 순회하면서가 아닌 초기 입력값의 순회한 요소를 넣어주었기 때문에 제대로 된 결과가 나오지 않는다는 것을 깨닫고 수정하였다.

테스트 케이스를 어떻게 찾아내지라고 생각하면서 막막했었는데,

  1. 주어진 문제에서 제한사항 중 내가 놓친 부분이 있는지를 확인한다.
  2. 조건을 만족시키는 다양한 상황을 만들어본다.

위 두가지를 실행해보다보니 찾게되어서 기뻤다.

댓글 공유

📌 JSX가 뭐길래?

JSX는 리액트 컴포넌트 사용을 HTML 처럼 사용하기 위해 생겨났다. JSX는 XML 처럼 생긴 문법 표현식이다.

1
2
3
4
5
6
const name = "loco";

const element = <h1>안녕하세요~! {name}</h1>;

// rendered
<h1>안녕하세요~! loco</h1>;

하지만 우리는 이렇게 편리한 JSX를 바로 사용할 수 없다. 왜냐하면 브라우저는 HTML, CSS, Javascript 파일만 읽을 수 있으므로 JSX 문법은 읽을 수 없다.

하지만 이렇게 편리한데 사용하고 싶으니 babel이라는 컴파일러를 사용한다.

🌈 babel이 뭐길래?

Babel은 자바스크립트 컴파일러로 아래의 역할을 하여 브라우저 호환 문제를 해결한다.

  • 구문 변환
  • 대상 환경에 누락된 폴리필 기능
  • 소스 코드 변환

babel을 사용하면 최신 문법으로 작성한 코드를 원하는 브라우저 환경에 잘 동작하도록 알아서 코드를 변환한 새로운 파일을 생성해준다.

또한, 애플리케이션이 방대해짐에 따라 한곳에서 파일을 관리하기가 어려워 모듈로써 파일을 구분하기 시작했다.

그렇게 파일을 구분을 하긴 했는데 그 많은 파일을 선후 관계를 따져가면서 HTML 파일에 등록시키고 최적화 하는 작업은 매우 번거로운 작업이다.

이러한 배경이 있기에 webpack이라는 모듈 번들러가 등장하였다.

🌏 webpack이 뭐길래?

앞서 설명했듯이 webpack은 모듈 번들러이다. 웹 브라우저 환경은 CBD, Module 프로그래밍에 적합하지 않다.

왜냐하면 브라우저 호환성때문이다. 모던 브라우저가 모듈을 사용할 수 있긴 하지만 모든 브라우저에서 가능한 것은 아니다. 또한, 브라우저는 애플리케이션을 build하는 환경을 기본적으로 제공하지 않기에 애플리케이션을 build하기 위한 환경을 개발자가 직접 설정해줘야 한다.

☄️ webpack을 사용하는 이유

1. 모듈 번들링

애플리케이션이 방대해짐에 따라 복잡성이 증가하여 코드가 혼란스러워지고 뒤섞이게 되어 개발이 어려워지는 문제가 발생하였다.

이를 해결하기 위해 코드 베이스를 모듈로 관리하고 번들링 해줘야한다.

2. 트리 쉐이킹(Tree Shaking)

애플리케이션에서 사용되지 않는 코드는 최적화 과정을 통해 제거되어 꼭 필요한 코드만 번들에 포함되도록 한다.

3. 코드 분할 (Code Spliting)

번들링으로 한곳에 묶기는 하였는데 이게 또 너무 크다보니 로드시간이 길어지는 문제가 발생하였다. 이를 해결하기 위해 코드 분할을 하여 런타임 중에 나눠진 파일들을 동적으로 불러오도록 하였다.

4. 코드 최적화와 소스맵(Source Map)

주석, 공백, 긴 함수명 등을 모두 축소 또는 제거하여 파일의 크기를 크게 줄일 수 있다. 이렇게 최적화된 코드는 사람이 읽기 힘들기에 디버깅하기가 어렵다.

이를 위해 코드를 추적하는 소스맵(Source Map)을 사용하여 버그를 추적하기가 용이하게 되었다.

결과적으로 편리한 JSX를 사용하기 위해서는 babel이라는 컴파일러로 변환은 하여 브라우저가 읽을 수 있는 파일로 변환해주어야 하며, 브라우저는 애플리케이션을 build 해주는 환경을 기본 제공해주지 않고 수많은 모듈 파일들을 번들링해주는 도구로서 webpack을 사용한다.

🏓 소감

수업을 하면서 개발을 하기 위한 Tool이 왜 이렇게나 많은지, 하나를 배우는 데에도 시간이 오래걸려 쉽게 접근하지 못한 부분이었다.

그동안 CRA(Create React App)이라는 명령어를 통해 리액트를 쉽게 사용할 수 있는데, 이는 기본적인 설정을 지원하고 프로젝트에 맞게 설정을 변경할 수가 없다. 그렇기 때문에 config 파일들의 설정이 필요하다는 것을 알게되었다.

앞으로 더 훌륭한 개발자가 되기 위해서는 기본 설정에만 의존하지 않고 내가 진행하려는 목적에 맞는 세팅을 알아둘 필요가 있어 열심히 배워보려고한다.

댓글 공유

요구사항

결과물

  • 버튼 컴포넌트를 만들어 재사용 가능하게 만들어라
  • 아이콘 컴포넌트를 만들어 재사용 가능하게 만들어라
  • 컴포넌트를 분리하여라

해결

1. src/main.js 파일에서 렌더링 준비를 한다.

1
2
3
4
5
6
7
8
const container = document.querySelector('root');
const root = React.createRoot(container);

root.render(
<React.StrictMode>
<h1>Get Ready?</h1>
</React.StrictMode>
)
  • 업로드 버튼 컴포넌트의 type, content를 지정해주어 컴포넌트를 재사용할 수 있다.
  • 사용자가 컴포넌트를 사용하려면 type과 content를 입력하여 아이콘과 내용을 적어주면 된다.

2. 버튼 컴포넌트 생성

1
2
3
4
5
6
7
8
9
10
11
const Button = props => {
const {children, type} = props;

return (
<button className="upload-button" disabled={type === 'disabled' ? "disabled" : ''}>
{children}<Icon type={type} />
</button>
)
}

export default Button
  • 버튼 컴포넌트에게 전달받은 props의 type값을 그대로 Icon 컴포넌트에게 전달해주었다.
  • props 객체로 전달받은 type값에 따라 조건부로 disable 속성을 등록/해제 해주었다.

3. 아이콘 컴포넌트 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const icons = {
"idle" : "M1.80202 3.65685L5.27085 0.292893C5.67355 -0.0976316 6.32645 -0.0976317 6.72915 0.292893L10.198 3.65685C10.6007 4.04738 10.6007 4.68054 10.198 5.07107C9.79528 5.46159 9.14238 5.46159 8.73968 5.07107L7.03117 3.41421L7.03117 11C7.03117 11.5523 6.5695 12 6 12C5.4305 12 4.96883 11.5523 4.96883 11L4.96883 3.41421L3.26032 5.07107C2.85762 5.46159 2.20472 5.46159 1.80202 5.07107C1.39933 4.68054 1.39933 4.04738 1.80202 3.65685Z",
"pending": "M9.96461 3.87118C9.61452 3.2192 9.10905 2.66362 8.49296 2.25365C7.87687 1.84368 7.16918 1.59198 6.43257 1.52084C5.69597 1.4497 4.95319 1.56132 4.27003 1.84582C3.58688 2.13031 2.98442 2.5789 2.51604 3.15184C2.04766 3.72478 1.7278 4.40439 1.58481 5.13047C1.44181 5.85656 1.48009 6.6067 1.69626 7.31445C1.91242 8.02221 2.29979 8.66573 2.82407 9.18802C3.34834 9.7103 3.99333 10.0952 4.7019 10.3087L5.1124 8.94617C4.6279 8.8002 4.18687 8.53699 3.82839 8.17987C3.46991 7.82275 3.20503 7.38273 3.05723 6.89879C2.90942 6.41484 2.88325 5.90192 2.98102 5.40544C3.0788 4.90897 3.2975 4.44427 3.61777 4.05251C3.93804 3.66075 4.34998 3.35402 4.8171 3.15949C5.28422 2.96496 5.79211 2.88864 6.29578 2.93728C6.79945 2.98592 7.28335 3.15803 7.70461 3.43835C8.12588 3.71868 8.47151 4.09857 8.71088 4.54438L9.96461 3.87118Z",
"resolved": "M11.6321 0.725152C12.0601 1.07426 12.124 1.70419 11.7749 2.13214L4.43311 11.1321C4.24683 11.3605 3.96931 11.4951 3.67466 11.4999C3.38 11.5047 3.09822 11.3794 2.90454 11.1573L0.246308 8.10888C-0.116669 7.69263 -0.0734819 7.06094 0.34277 6.69796C0.759022 6.33499 1.39071 6.37817 1.75369 6.79443L3.63274 8.94927L10.2251 0.867921C10.5742 0.439968 11.2042 0.376047 11.6321 0.725152Z",
"rejected": "M6 12C9.31371 12 12 9.31371 12 6C12 2.68629 9.31371 0 6 0C2.68629 0 0 2.68629 0 6C0 9.31371 2.68629 12 6 12ZM3.40106 3.25359C3.79159 2.86306 4.42475 2.86306 4.81528 3.25359L6.01883 4.45714L7.22238 3.25359C7.61291 2.86306 8.24607 2.86306 8.6366 3.25359C9.02712 3.64411 9.02712 4.27728 8.6366 4.6678L7.43304 5.87135L8.6366 7.07491C9.02712 7.46543 9.02712 8.0986 8.6366 8.48912C8.24607 8.87964 7.61291 8.87964 7.22238 8.48912L6.01883 7.28557L4.81528 8.48912C4.42475 8.87964 3.79159 8.87964 3.40106 8.48912C3.01054 8.0986 3.01054 7.46543 3.40106 7.07491L4.60462 5.87135L3.40106 4.6678C3.01054 4.27728 3.01054 3.64411 3.40106 3.25359Z",
"disabled": "M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM9.65221 6.00001C9.65221 8.01707 8.01707 9.65221 6.00001 9.65221C5.37082 9.65221 4.77878 9.4931 4.26195 9.21293L9.21292 4.26194C9.4931 4.77878 9.65221 5.37081 9.65221 6.00001ZM2.83053 7.81592L7.81591 2.83053C7.28103 2.52342 6.66103 2.34781 6.00001 2.34781C3.98296 2.34781 2.34781 3.98296 2.34781 6.00001C2.34781 6.66103 2.52342 7.28104 2.83053 7.81592Z"
}

const Icon = props => {
const {type} = props;

return (
<svg className={`button-icon ${type === 'pending' ? "spinner_V8m1" : ""}`} width={12} height={12} viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d={icons[type]} fill={type === 'disabled' ? "#ADAEB6" : "#525577"} />
</svg>
)
}
  • props 객체로 전달받은 type 값을 가지고 상황에 맞는 아이콘을 보여주고 그에 따라 스타일링도 해주었다.
  • icons 객체에 props 객체로 받은 타입에 대한 값을 저장하여 관리하였다.

4. 컴포넌트 분리

이 모든 코드가 한 파일에 있기에 이를 컴포넌트 단위와 역할별로 구분해주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ./main.js
import UploadButton from './components/Button.js'

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);

root.render(
<React.StrictMode>
<UploadButton type="idle">업로드</UploadButton>
<UploadButton type="pending">업로드 중</UploadButton>
<UploadButton type="resolved">완료</UploadButton>
<UploadButton type="rejected">실패</UploadButton>
<UploadButton type="disabled">업로드</UploadButton>
</React.StrictMode>
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ./components/UploadButton.js
import Icon from './Icon.js'

const Button = props => {
const {children, type} = props;

return (
<button className="upload-button" disabled={type === 'disabled' ? "disabled" : ''}>
{children}<Icon type={type} />
</button>
)
}

export default Button
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ./components/Icon.js
const icons = {
...
}

const Icon = props => {
const {type} = props;

return (
<svg className={`button-icon ${type === 'pending' ? "spinner_V8m1" : ""}`} width={12} height={12} viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d={icons[type]} fill={type === 'disabled' ? "#ADAEB6" : "#525577"} />
</svg>
)
}


export default Icon
  • 모듈이 하나의 개체로 이루어져 있으므로 기본 내보내기를 해주었다.
  • 기본 내보내기를 해주었으므로 import시 이름을 변경하여 가져올 수 있다.

💪 맞닥뜨린 문제

1. 리액트 import 오류

import오류

1
2
3
4
5
6
7
8
9
10
import {createRoot, StrictMode} from './react.js';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
<StrictMode>
<h1>Hi~!</h1>
</StrictMode>
)

위와 같이 import를 해오고 싶었지만 CDN으로 React를 사용하는 것이기 때문에 react.js를 불러올 수 없다.

그러므로 ReactDOM, React로 객체처럼 사용해야만 했다.

🏓 소감

간단한 업로드 버튼 컴포넌트를 구현하는 과제를 해보면서 리액트와 좀 더 친숙해지는 계기가 된 것 같다. figma 시안이 워낙 꼼꼼하게 잘 나와있어서 스타일링 하는데는 큰 어려움 없이 해서 좋았다.

처음 딱 시작하려는 순간부터 오류를 맞이해서 당황하였지만 문제도 잘 해결했고 SVG 파일에 애니메이션과 스타일을 입혀보는 것을 직접 해보니 더 기억에 오래 남을 것 같다.

댓글 공유

🚀 기능 요구 사항

암호문을 좋아하는 괴짜 개발자 브라운이 이번에는 중복 문자를 이용한 새로운 암호를 만들었다. 예를 들어 “browoanoommnaon”이라는 암호문은 다음과 같은 순서로 해독할 수 있다.

  1. “browoanoommnaon”
  2. “browoannaon”
  3. “browoaaon”
  4. “browoon”
  5. “brown”

임의의 문자열 cryptogram이 매개변수로 주어질 때, 연속하는 중복 문자들을 삭제한 결과를 return 하도록 solution 메서드를 완성하라.

제한사항

  • cryptogram은 길이가 1 이상 1000 이하인 문자열이다.
  • cryptogram은 알파벳 소문자로만 이루어져 있다.

실행 결과 예시

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function problem2(cryptogram) {
if (cryptogram !== cryptogram.toLowerCase()) throw new TypeError('매개변수는 소문자만 입력할 수 있습니다.')
if (!cryptogram.length || cryptogram.length > 1000) throw new RangeError('매개변수는 1자 이상 1000자 이하만 입력할 수 있습니다.')

let strArr = [...cryptogram];
let duplicatedStrPosition = [];
let hasDuplicatedStr = true;

while(hasDuplicatedStr) {
strArr.forEach((_, i, init) => {
if (init[i] === init[i+1]) duplicatedStrPosition = [i, ...duplicatedStrPosition];
})

hasDuplicatedStr = !duplicatedStrPosition.length ? false : true;

duplicatedStrPosition = duplicatedStrPosition.filter(elem => {
strArr = [...strArr.slice(0, elem), ...strArr.slice(elem + 2)];
return false;
})
}

return strArr.join('');
}
  • strArr로 문자열을 배열로 관리
  • strArr 배열이 더 이상 중복이 없을 때 까지 반복해야하고, 문자열의 순회하며 중복을 확인해야 하므로 시간 복잡도 $n^2$
  • Spread문법으로 원본 배열의 변형을 최소화하는 방향으로 배열을 관리
  • for문 대신 배열 메서드 forEach문 사용
  • 원본 배열을 변형시키는 splice 메서드 대신 slice 메서드 사용

🏓 소감

  • 내가 코드를 처음 보는 사람의 입장으로 변수명부터 잘 이해할 수 있는지 고려하여 리팩터링을 하였다.
  • 처음에는 while 조건식을 단순히 true라고 하였는데, 의미를 부여하여 코드의 가독성을 높이기 위해 변수에 할당해주었다.
  • 배열을 다루기 때문에 for문 대신 forEach문을 사용하였다.
  • splice 메서드 같이 원본을 변형시키는 메서드 대신 spread 문법과 slice 메서드를 사용하였다.
  • 리팩터링 전에는 duplicatedStrPosition 배열을 직접 빈 배열로 초기화해주었지만, 리팩터링을 하면서 filter 메서드를 사용하여 좀 더 배열을 다루는 의미를 부여하였다.

하나 걸리는 부분은 제한사항을 에러처리를 해주어야 하나 고민을 하였다. 사용자가 제한사항에 벗어나는 입력을 할 수도 있으므로 이에 대한 에러를 발생시켜주는 것이 사용성에 더 옳다고 생각하여 if 조건문을 통해 에러처리를 해주었습니다.

에러처리에 대해서는 깊게 생각해보지 못했는데, 제한사항에 대해 한번 더 깊이 생각해보면서 에러처리의 필요성을 느낄 수 있었던 문제여서 즐거웠다.

또한 stack 자료구조를 생각하면서 문제를 해석하면 지금의 코드보다 훨씬 더 간결하고 가독성있는 코드를 구현할 수 있다는 것을 오늘 코드리뷰를 통해 깨닫게 되었다. stack으로 구현해보는 것은 다음 시간에 해보도록 하자.

댓글 공유

📌 React란?

화면에 렌더링되는 UI를 컴포넌트 단위로 쪼개 사용할 수 있는 Javascript 라이브러리리 혹은 프레임워크이다.
라이브러리인가? 프레임워크인가?는 크게 중요하지 않다. 우리가 중점적으로 생각해야할 부분은 리액트는 Progressive(점진적)이라는 것이다.

특징

  1. 리액트는 점진적이므로 애플리케이션을 모두 리액트로 구성할 필요가 없다.

  2. 리액트는 Javascript 중점 라이브러리이다. 그러므로 자바스크립트를 잘하면 재밌을 것이다.

  3. 리액트는 별도의 설치가 필요없이 컴포넌트를 즉시 사용해볼 수 있다.

  4. 선언형 프로그래밍으로 명령형 프로그래밍보다 코드를 이해하기가 쉽다.

  5. 리액트를 배우면 웹, 리액트 네이티브를 배우면 모바일 등 한가지를 배워 확장성이 넓다.

시작하기 전

1. React API 라이브러리를 사용하여 UI를 구상하는 VitualDOM을 생성한다.

1
2
// CDN
<script src="//unpkg.com/react@17/umd/react.development.js" crossorigin></script>
  • 만약 버전을 변경하고 싶다면 @version을 써준다.

  • 만약 IE를 고려 해야한다면 17 버전을 사용하고 그렇지 않다면 18 버전 사용하자.

2. ReactDOM API 라이브러리를 사용하여 VirtualDOM을 RealDOM에 렌더링하여 UI를 구현한다.

1
<script src="//unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
  • React API와 버젼을 일치시켜 줘야한다.

💡 React v17의 render()와 v18의 createRoot() 차이

1
2
3
4
5
6
7
// version 17
const app = React.createElement('div');

ReactDOM.render(app,
document.getElementById('root'),
() => {console.log('rendered!')}
);
  • 렌더링할 React 요소인 app을 인자로 전달하고 이를 렌더링할 container인 root container를 render()에 전달한다.

리액트 버전 18부터 render() 대신 createRoot()를 사용한다.

1
createRoot(container[, options])
1
2
3
4
5
6
// version 18
const root = createRoot(document.getElementById('root'));

const element = <h1>Hello, world</h1>; // JSX

root.render(element);

문서의 RealDOM 요소 노드인 container를 React Root로 만들어 반환하고, React Root를 render() 메서드를 사용하여 React 요소를 RealDOM에 렌더링한다.

실습

1. React 요소 노드와 Real DOM 요소 노드 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Real
const headlineNode = document.createElement('h1');
headlineNode.classList.add('headline');
headlineNode.setAttribute('land','en');
headlineNode.insertAdjacentHTML('beforeend', `Add <strong>React</strong> to a Website`);

// React
const headlineVnode = React.createElement(
'h1',
{
className : 'headline',
lang: 'en'
},
'Add ',
React.createElement('strong', null, 'React'),
'to a Website'
)

React.createElement

  • RealDOM 요소 노드는 브라우저가 렌더링하면서 생성한 것이고 이와 달리 React 요소 노드는 Object(객체)로 그 형태가 다르게 생겼다.

  • 위 사진을 보면 props라는 객체에 children, className, lang 등이 담겨있는 것을 알 수 있다. props에 대해서는 이후에 알아보자.

❗️ StrictMode 오류 해결

ECMAScript에서 ‘use strict’ 사용하여 문법적 오류를 미리 알려주었듯이 React에서도 이러한 오류를 미리 알려준다.

이에 대한 경고를 해결하기 위해서는 StrictMode 컴포넌트를 사용해야한다.

1
2
3
4
5
React.createElement(
React.StrictMode,
null,
React.createElement(App, { children: [headline, reactLogo] })
)

사용법은 간단하다. 생성하려는 React 요소를 React.StrictMode의 자식 요소로 전달해주면 된다.

2. React Component vs React Element

자바스크립트에서는 재사용을 위해 함수를 사용하고 리액트에서는 재사용을 위해 컴포넌트를 사용한다. 컴포넌트 생성 방법은 함수를 생성하듯이 생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
function SvgPath(props) {
return React.createElement('path',{
d: props.d,
fill: 'currentColor'
});
}

const App = props => React.createElement('div',
{ className: 'App' },
...props.children
);
  • 리액트 컴포넌트는 매개변수로 props를 받는다.

  • props 객체를 통해 다른 컴포넌트에게 본인의 HTML attributes, 자바스크립트 값(객체, 배열, 함수 등)을 전달해줄 수 있다.

  • 함수선언식, 함수표현식 둘 다 가능하다.

하지만 createElement()를 사용하여 컴포넌트를 생성하는 것은 매우 번거로운 일이다. 그 대안으로 생겨난 것이 바로 JSX이다.

JSX란?

JSX는 리액트 컴포넌트 사용을 HTML 처럼 사용하기 위해 생겨났다. JSX는 XML 처럼 생긴 문법 표현식이다.

1
2
3
4
5
6
7
8
9
10
const name = 'loco';

const element = (
<h1>
안녕하세요~! {name}
</h1>
);

// rendered
<h1>안녕하세요~! loco</h1>

자동 세미콜론 삽입이 되는 것을 방지하기 위해 괄호로 감싸는 것을 추천한다.

특징

  1. 선언형, HTML과 비슷한 구조, {}를 사용한 데이터 바인딩이 편리하다.

  2. 브라우저엔진에 의해 해석되지 못하므로 babel이 컴파일을 해줘야만 한다.

  3. HTML 보단 JS에 가까우므로 camelCase 명명규칙을 따른다.

  4. JSX는 렌더링하기 전에 이스케이프하므로 애플리케이션에서 명시적으로 작성되지 않는 내용은 script에 주입되지 않아 XSS 공격으로부터 안전하다.

❗️ React에서 babel 추가 설정

React바벨추가설정

  1. 이전에 React를 사용하기 전에 설정해두었던 babel 사양에서는 React를 컴파일 해줄 수 없다.

  2. 컴포넌트를 모듈 파일로 구분할 때에 babel은 input으로 지정된 파일만 컴파일 해주고 input 파일에서 import한 파일까지 컴파일해주지 않는다.

위 2가지 문제 해결을 위해서는 플러그인을 설치 해줘야한다.

1
npm i -D @babel/preset-react
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// babel.config.js
module.exports = {
comments: false,
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
},
],
'@babel/preset-react',
],
};

  • 설치가 끝났다면 babel.config.js 파일에 해당 플러그인을 사용할 것이라고 등록을 해줘야 정상 동작한다.

  • 옵션값이 있을 경우 []로 감싸서 넣어주고 그렇지 않은 경우 문자열로만 추가한다.

JSX로 컴포넌트 생성하기

이제 JSX를 사용할 준비가 끝났으니 JSX를 사용하여 컴포넌트를 생성해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const HeadLine = () => (
<h1 className="headline" lang="en">
Add <strong>React</strong> to a Website
</h1>
);

const App = () => {
<div className="App">
<HeadLine></HeadLine>
</div>
}

const container = document.getElementById('react-root');
const ReactDOMRoot = ReactDOM.createRoot(container);

ReactDOMRoot.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
  • React.createElement()를 사용하여 React Element를 생성하여 컴포넌트를 생성하는 방법보다 훨씬 편리하다는 것을 알 수 있다.

🏓 소감

리액트 수업을 듣고 배운것을 차근차근 익혀가면서 정리해보았다. props에 대한 개념도 확실히 잡히고, JSX를 왜 쓰게 되었는지, 안쓰면 무엇인 문제점이고 불편한지를 깨달을 수 있는 시간이여서 좋았다.

공식문서를 읽어볼 때도 영어로 된 것을 자동번역하여 읽지 않고 원문을 보면서 읽으니 시간이 조금 더디지만 그렇기 때문에 기억에 더 오래 남을 것이라고 생각하여 영어로 읽는 연습을 하고있다.

댓글 공유

loco9939

author.bio


author.job