치춘짱베리굿나이스
[프리온보딩] 220507 내 과제 리팩토링 2 본문
내 과제 리팩토링
공식적인 내생각
컴포넌트 완전 재구축의 현장 (점 점 점 점 점)
div
로 떡칠을 하질 않나, 클래스명 없이 nth-child()
선택자로 어떤걸 선택했는지 알아보기도 힘들게 하질 않나, onClick
이벤트를 굳이 div
에 걸질 않나, ...
반성을 좀 많이 (...) 하면서 작업 중이다
심지어 어제 작업하면서 린터도 예전에 쓰던 버전 그대로 놔둬서... 새로 교체했더니 아주 시뻘겋게 죽죽
작업 내용
Toggle
토글은 컴포넌트 자체가 단순해서 리팩토링이 그렇게 어렵지 않았다
const handleOnClick = (e) => {
e.preventDefault();
setIsSelected(isSelected ? false : true);
}
const handleToggleClick = () => {
setIsSelected((prevState) => !prevState)
}
필요없는 e.preventDefault()
를 제거해 주었다
isSelected
상태값 세팅은 prevState
를 이용하였다
<ToggleWrapper toggle={ifToggle}>
<div className="selector-parent" onClick={handleOnClick}>
...
</div>
</ToggleWrapper>
<div className={styles.toggleDiv}>
<button type='button' className={styles.toggleParent} onClick={handleToggleClick}>
...
</button>
</div>
Styled
를 전부 제거해주었고, onClick
이벤트가 걸리는 div
는 button
태그로 변경하였다
<div className={!ifToggle ? 'selected' : ''}>{firstString}</div>
<div className={ifToggle ? 'selected' : ''}>{secondString}</div>
<div className={cx(styles.toggleElement, { [styles.selected]: !isSelected })}>{firstString}</div>
<div className={cx(styles.toggleElement, { [styles.selected]: isSelected })}>{secondString}</div>
className
지정 시에 삼항연산자 대신 classnames
라이브러리를 이용해 상태값에 따라 추가적인 클래스를 가질 수 있도록 하였다
<div className="back">
<div></div>
{/*
margin-left: ${props => (props.toggle ? '10rem' : '0')};
*/}
</div>
<div className={styles.toggleBack}>
<div className={cx(styles.toggleThumb, { [styles.moved]: isSelected })} />
{/*
&.moved {
margin-left: 10rem;
}
*/}
</div>
아무 내용도 없고 클래스도 없는 div를 덩그러니 놔두는 것보단 클래스명을 지정해주었다
또한 원래는 styled
를 사용해서 스타일에 변수를 사용할 수 있었는데, 이제 불가능하므로 isSelected
상태값에 따라 추가적인 클래스를 사용하여 토글 버튼의 좌우 이동을 구현하였다
Tab
<TabWrapper length={selectorArr.length} index={selectedIndex}>
<div>
...
</div>
<div>
...
</div>
</TabWrapper>
<div className={styles.tabDiv}>
<section className={styles.tabTop}>
...
</section>
<section className={styles.tabBottom}>
...
</section>
</div>
마찬가지로 styled
를 제거해주었고, 정체모를 div
두쌍보단 위아래로 나뉘어져 있는게 section
을 써도 괜찮겠다 싶어 section
태그로 교체해 보았다
클래스명 없이 nth-child()
에 의지하면 누가 임의로 요소를 끼워넣었을 때 스타일이 전부 망가질 수 있다는 우려에 따라 거의 모든 요소에 클래스명을 달아주었다
심지어 다른 scss 파일에서 한 태그 (예를 들면 button
) 스타일을 건들면 페이지의 모든 컴포넌트가 영향을 받아버려서 왠만하면 그냥 클래스명을 다 달아줬다
버튼 스타일 적용한게 다 어그러져서 깜짝 놀랐다;; 스타일드에 너무 익숙해진 탓인가보다
{selectorArr.map((v, i) => (
<SelectorDiv
className={selectedIndex === i ? 'selected' : ''}
key={i}
onClick={e => handleOnClick(e, i)}
length={selectorArr.length}
>
{selectorArr[i]}
</SelectorDiv>
)
)
}
{selectorArr.map((v, i) => (
<button
type='button'
className={cx(styles.selectorDiv, { [styles.selectedDiv]: selectedIndex === i })}
key={`selector-arr-${v}`}
onClick={() => handleOnClick(i)}
style={{ width: `calc(24rem / ${selectorLength})` }}
>
{selectorArr[i]}
</button>
)
)
}
onClick
이벤트 핸들러를 달기 위해 각 탭을 div
가 아닌 button
으로 바꿔 주었다
버튼은 항상 기본 스타일이 적용되는 게 안 예뻐서 손이 잘 안 갔는데 border
속성만 none
으로 지워줘도 div
랑 비슷하게 스타일이 적용돼서 좋음
key
prop으론 i
를 아예 사용 안 했다
onClick
핸들러는 인덱스 i
를 반드시 사용해야 하기 때문에 (무슨 탭을 선택했는지 알기 위해서) 부득이하게 화살표 함수를 사용하였다
마찬가지로 삼항연산자로 클래스명을 설정하던 것을 classnames
라이브러리로 바꿔버렸다
인라인 스타일은 쓰고싶지 않았지만 탭의 개수 (selectorLength
) 가 변동될 여지가 있음을 고려하여 탭 각각의 폭과 아래의 슬라이더만 인라인으로 맞춰주었다
Slider
수정할 것 투성이였다...
for (let i = 0; i <= 4; i++) {
...
<div
className="pos-indicator"
onClick={e => handleOnClick(e, i * 25)}
>
<div>{i * 25}%</div>
</div>
...
const SLIDER_VALUES = [1, 25, 50, 75, 100]
const SLIDER_CLASSNAMES = [styles.slider1P, styles.slider25P, styles.slider50P, styles.slider75P, styles.slider100P]
...
for (let i = 0; i < 5; i += 1) {
...
<li
key={`slider-value-${SLIDER_VALUES[i]}`}
className={cx(styles.sliderBackElem, SLIDER_CLASSNAMES[i], {
[styles.sliderFilled]: sliderValue >= SLIDER_VALUES[i],
})}
>
...
<button type='button' className={styles.sliderIndicator} onClick={() => handleButtonClick(SLIDER_VALUES[i])}>
{SLIDER_VALUES[i]}%
</button>
...
일단 슬라이더 시작점이 0이 아니라 1인 것부터 간과해버려서............ 아예 SLIDER_VALUES
와 SLIDER_CLASSNAMES
배열 상수를 선언해놨다
클래스명 배열도 선언한 이유는 styled
컴포넌트에 변수 넘겨서 사용하는 꼼수를 못 사용하기 때문이지! 하하!
슬라이더 뒷부분 눈금과 버튼의 픽셀 매치를 위해서는 각 숫자별로 margin
을 다르게 적용해야 해서 styled
를 사용할 땐 그냥 단일 클래스 내에서 calc()
를 통해 margin
너비를 계산해주었는데, 그걸 사용하지 못하니 어쩔 수 없이 클래스를 5종류로 분리했다
<SliderWrapper>
<div className="slider-head">
<div>{sliderValue}</div>
<div>%</div>
</div>
<SliderBody sliderValue={sliderValue} setSliderValue={setSliderValue} />
</SliderWrapper>
<div className={styles.sliderDiv}>
<section className={styles.sliderHead}>
<span className={styles.sliderValue}>{sliderValue}</span>
<span className={styles.sliderPercent}>%</span>
</section>
<section className={styles.sliderBody}>
...
</section>
</div>
div
범벅에서 탈출하기 위해 이번에도 슬라이더가 위 (숫자 나오는 부분) 와 아래 (슬라이더 본체) 로 나뉘어지는 것을 이용하여 section 태그를 사용해 보았다
main
이나 aside
, header
, footer
태그를 사용하기엔 쪼끔 아쉬운 부분이 있다
문자열이 들어가는 부분은 div
대신 span
으로 해결하였고, 모든 태그에 클래스를 달아주었다
<input
className={styles.sliderInput}
type='range'
value={sliderValue}
min='1'
max='100'
onChange={handleSliderChange}
style={{
background: `linear-gradient(to right, #699092 0%, #699092 ${sliderValue}%, #dddddd ${sliderValue}%, #dddddd 100%)`,
}}
/>
input
태그의 min
값을 1로 설정하여 1 이하로 떨어지지 않게 해주었다
또한 여기에도 인라인 스타일이 사용되었는데, 슬라이더에 색상이 채워지는 효과를 위해 linear-gradient
를 사용하려면 현재 슬라이더의 값 (sliderValue
) 이 필요하기 때문이다
이런 경우에는 어쩔 수 없는 것 같다 어차피 움직일 때마다 리렌더링 되어야하니,,
<SliderBackgroundWrapper key={i} curValue={sliderValue} indiValue={i}>
<div className="pos-dot">
<div></div>
</div>
<div
className="pos-indicator"
onClick={e => handleOnClick(e, i * 25)}
>
<div>{i * 25}%</div>
</div>
</SliderBackgroundWrapper>,
<li
key={`slider-value-${SLIDER_VALUES[i]}`}
className={cx(styles.sliderBackElem, SLIDER_CLASSNAMES[i], {
[styles.sliderFilled]: sliderValue >= SLIDER_VALUES[i],
})}
>
<div className={styles.sliderDot}>
<CircleIcon />
</div>
<button type='button' className={styles.sliderIndicator} onClick={() => handleButtonClick(SLIDER_VALUES[i])}>
{SLIDER_VALUES[i]}%
</button>
</li>
여러 요소를 한 줄로 나열시켜야 한다는 점에 착안하여 div
대신 li
를 사용하였고, div
에 onClick
이벤트를 거는 대신 button
을 사용하였다
위에서 선언한 SLIDER_CLASSNAMES
와 SLIDER_VALUES
배열을 이용하여 %값마다 서로 다른 클래스명을 가지도록 하였고, 슬라이더가 채워지는 효과를 눈금에도 적용하기 위해 classnames
를 통해 조건부 클래스명을 지정하였다
눈금을 만들기 위해 빈 div
를 굳이 border-radius
를 통해 동그랗게 만들기보단 그냥 있는 SVG를 사용하자 싶어 CircleIcon
svg를 불러와 사용하였다
margin-left: ${props => (0.9 - 0.5) * props.indiValue}rem;
&.slider1P {
.sliderDot,
.sliderIndicator {
margin-left: -2.4rem;
}
}
&.slider25P {
.sliderDot,
.sliderIndicator {
margin-left: -1.2rem;
}
}
...
styled
의 장점 중 하나인 변수 들고오기를 못 사용해서 아쉬울 따름이다
각 기준 %값 (1, 25, 50, 75, 100) 별로 클래스명을 다르게 지정하였고, margin
을 다르게 주었다
sliderDot
와 sliderIndicator
은 justify-content
와 align-items
를 통해 정확히 위아래에 위치하므로, 같은 margin
간격을 적용해 주었다
Input
<InputWrapper>
<EmailInput
...
/>
<PasswordInput
...
/>
</InputWrapper>
<div className={styles.inputDiv}>
<section className={cx(styles.inputContainer, styles.inputTop)}>
...
</section>
<section className={styles.inputContainer}>
...
</section>
</div>
이메일 섹션과 비밀번호 섹션으로 분리해 주었다
inputTop
클래스는 margin-bottom
을 적용하기 위한 클래스이다
<label style={{ display: `${ifShown ? '' : 'none'}` }}>
Invalid E-mail address.
</label>
<svg>
...
</svg>
<label htmlFor='email' className={cx(styles.labelInvalid, { [styles.isHidden]: isHidden })}>
Invalid E-mail address.
</label>
<CheckIcon className={cx(styles.checkIcon, { [styles.isEmailValid]: isEmailValid })} />
svg
는 컴포넌트로 불러오고, 클래스명을 삼항연산자 대신 classnames
를 통해 상태값에 따라 다르게 적용하였다
라벨 인라인 스타일도 그냥 추가 클래스로 해결하였다
Dropdown
슬라이더와 마찬가지로 리팩토링마저도 좀 어려웠다
<div className="dropdown-top" onClick={handleOnClick}>
<div className="dropdown-top-header">{selectedStr}</div>
<div className="dropdown-top-arrow">
<svg>
...
</svg>
</div>
</div>
<button type='button' className={styles.dropdownTop} onClick={handleTopClick}>
<span className={styles.dropdownTopSpan}>{selectedStr}</span>
<TopArrowIcon className={styles.dropdownSvgs} />
</button>
div
범벅에서 벗어나 보자
<ul className={styles.selectList}>
{dropdownArr
.filter((v) => {
if (searchInput === '') return v
if (v.toLowerCase().includes(searchInput)) return v
return 0
})
.map((v) => (
<li key={`dropdown-list-${v}`}>
<button type='button' onClick={() => handleListClick(v)}>
{v}
</button>
</li>
))}
</ul>
드롭다운을 리스트로 만들었고, startsOf
대신 includes
를 사용해 단어가 포함만 되어도 검색되도록 하였다
li
태그는 겉만 감싸는 용도로 사용하고, 내부 버튼은 button
태그를 통해 onClick
이벤트를 적용하였다
다만 handleListClick
이 인자로 v
를 받는 바람에 무한 리렌더링을 막기 위해 화살표 함수를 사용하였다
useClickAway(ref, () => setIsHidden(true))
react-use
라이브러리의 훅을 사용하여 드롭다운 바깥을 눌렀을 때 숨겨지는 기능도 쉽게 구현하였다
리팩토링 결과물
바로 깃허브 페이지로 deploy해 주었다
생각보다 반영 시간이 짧아서 좋은듯
'프로젝트 > 원티드 프리온보딩' 카테고리의 다른 글
[프리온보딩] 220503 강의 메모 04 (투두 리스트 예시) (0) | 2022.05.09 |
---|---|
[프리온보딩] 220508 그룹과제 #1 끝 (0) | 2022.05.09 |
[프리온보딩] 220506 내 과제 리팩토링 (0) | 2022.05.07 |
[프리온보딩] 220505 투두리스트 작업하기 (0) | 2022.05.06 |
[프리온보딩] 220503 강의 메모 03 (VSCode, 레포 세팅) (0) | 2022.05.06 |