요구사항

결과물

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

해결

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으로 구현해보는 것은 다음 시간에 해보도록 하자.

댓글 공유

loco9939

author.bio


author.job