지난편에 이어 selectBox 구현 글을 작성하겠다.
이번편이 굉장히.. 날 고통스럽게 만들었던 부분이다.
해당 편에서 구현해야 할 기능
- 선택한 값을 select(
.sbtn-select
)에 띄우기
- ButtonSelect 컴포넌트를 사용하는 페이지(CourseList)에 선택한 값들이 추가 박스(
.cl-location
)로 나타나게 하기
미리 보는 완성된 영상

.sbtn-select
에 선택한 값 띄우기
선택한 값이 들어갈 span 태그 .sbtn-select
를 만들어주고, useState로 selectValue를 만든다.
저번편에 li태그 값들을 뿌려줬던 해당 코드에서 li태그 부분에 onClick 이벤트를 추가한다.
onClick 이벤트에 setSelectValue(`${it.item}`)
를 넣어준다. 해당 코드는 li태그를 클릭하면 selectValue를 it.item
값으로 변경해준다는 의미이다.
자, 그러면 원하는 li태그를 클릭했다면 해당 li 태그의 값이 selectValue에 들어가게 된다. 그러면 .sbtn-select
에 selectValue 값을 넣어주면, 우리가 선택한 값이 띄워지게 되는 것이다.
여기서 추가로, html의 select 태그의 경우에는 값을 선택하기 전에 기본적으로 띄워주는 값을 설정할 수 있다. 그래서, 나도 해당 기능을 추가해주기로 했다.
ButtonSelect에 title이라는 props를 추가하여 부모 요소(CourseList)에서 title의 값을 받아준다. 해당 title이 값을 선택하기 전에 기본적으로 띄워줄 값이다.
.sbtn-select
에서 삼항연산자를 통해 selectValue의 값이 있다면 selectValue를 뿌려주고, 값이 없다면(즉, 빈배열이라면) title값을 뿌리도록 해줬다.

.sbtn-select
에 말줄임 표시
.sbtn-select
에 들어오는 값이 너무 길면 말줄임 표시를 해주도록 했다.
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space:nowrap
을 통하여 자동으로 줄바꿈하는 것을 막아준다.
그리고 overflow:hidden
을 통해 지정한 박스 사이즈를 넘어가게 되면 잘라준다.
text-overflow:ellipsis
를 통해 넘어간 부분을 말줄임표(…) 처리가 되도록 한다.
자식에서 부모로 값 전달하기
우리가 props를 사용할때 대부분 부모에서 자식요소로 값을 전달해준다. 그러나 나는 자식에서 부모로 값을 전달해야 하는 경우가 발생했다.
여기서 부모는 CourseList, 자식은 ButtonSelect이다.
CourseList는 page인데, 위의 완성 영상을 보면 값을 선택하면 select 박스 옆에 선택한 값의 박스(.cl-location
)가 나타나야한다.
우리는 선택한 값을 자식에서만 다루는데, 이 자식에서 선택한 값을 부모로 보내서 해당 페이지(CourseList)에 작은 박스로 뿌려주도록 해야한다.
여튼, 결론은 자식에서 부모로 값을 전달해야 한다는 것
일단, 부모에 getTextValue(text)라는 함수를 만들어 준다. 그리고 자식에 props를 getTextValue를 만들어서 전달하는 값으로 getTextValue를 넘겨준다. 자식에게 해당 함수가 부모에 존재한다고 알려주는 것이다.
그리고 자식에서 해당 함수를 실행시켜 준다. 이때, 우리는 참고한 글과 는 조금 다르게 실행시킨다. useEffect를 사용해주는 것이다.
참고한 글 처럼 sendTextValue 함수(부모로 값 전달하는 함수)를 만들어서 실행시켜줘도 된다. 해당 방법으로 코드를 작성하면 li 태그의 onClick 이벤트가 실행되면서 sendTextValue 함수가 실행된다.
그러나 이 방법은 선택한 값이 select의 .sbtn-select
는 바로 적용되지만 .cl-location
에는 한 박자 늦게 적용된다.
내가 가장 처음 1을 선택하면 .cl-location
은 나타나지 않는다. 그 다음에 2를 선택해야 .cl-location
에 1 값이 나타나게 된다.
이런 기능적인 오류가 발생하는 이유를 추측하기로는 onClick에 두개의 함수(getTextValue랑 setSelectValue)가 있는데, selectValue 값이 getTextValue랑 동시에 실행돼서 selectValue값이 업데이트 되기 전에 getTextValue가 실행하는 것이 아닐까.. 따라서 값의 변경의 한 박자 느리게 반영된것이 아닐까 추측중이다.
따라서, 해당 기능 오류를 해결하기 위해서 useEffect를 사용해줘야 한다. useEffect로 변화를 바로 감지할 수 있도록 하는 것이다.
useEffect의 deps로 selectValue를 하여 selectValue의 값의 변경을 감지하면 getTextValue함수가 실행되도록 한다.
자식에서 getTextValue(selectValue)를 실행시키도록 해주면, 부모에서 getTextValue 함수가 실행되면서 setTextValue(text)를
(자식에서의 selectValue가 부모의 text에 들어오는 것)
통해 textValue에 값에 text(selectValue)가 들어가게 된다.
해당 코드를 적용하면 textValue가 .cl-location
에 바로 적용된다!
위에 설명한 코드 로직은 다음과 같다.
- 자식에서 값 선택(setSelectValue 실행)
- selectValue의 변화를 useEffect가 감지
- 부모로 값 전달 (getTextValue 실행)
- 부모에서 getTextValue 실행 (setTextValue 실행)
.cl-location
에 textValue 반영
ButtonSelect(자식) 코드는 위에 이미지 참고
CourseList(부모) 코드
주석은 치열한 오류잡기의 흔적..이므로 무시할 것

마치며
select 태그 기능을 직접 구현해봤는데, 처음에 생각한 것 보다는 엄청 어렵지는 않았다. 1편의 부분은 구현하는데 힘들기는 했어도 못할 정도는 아니었는데, 2편의 부분이.. 특히 getTextValue 부분이 정말.. 기능 오류..잡는게 너무 힘들고 어려웠다.. 포기할까 생각도 했지만, 그건 찝찝하고… 그리고 이렇게 오래 붙들고 있었는데 그 시간들이 아까웠달까
그래도 결론적으로는 잘 구현해서 정말 뿌듯했다. 완성한 결과물을 보니.. 그간 고생하고 힘들었던게 싹 사라진달까..
한달 후에 적고 있는 지금도 그때 생각하니 정말 감격스러운 감정이 든다..
해당 기능을 구현하면서 useEffect가 이런 거구나!!를 완벽히 깨달았고, useState 또한 완벽히 이해할 수 있었다.
그전에 강의를 보면서 할때는 useEffect랑 useState는 이런거구나..를 머리로만 이해했는데, 해당 기능을 구현하기 위해 별 자료를 다 찾아보고 내 프로젝트에 맞게 변경하려다보니 해당 기능들을 완벽히 이해할 수 있었다.
이래서.. 개발은 직접 원하는 프로젝트를 진행하면서 공부하라는 말이 이런거구나! 를 깨달았다.
이제 다음번에 select 태그 구현한다면 좀 더 빨리 잘 할 수 있을 것 같다.
참고
부모에서 자식, 자식에서 부모 값 전달하는 방법 prose, function
부모에서 자식, 자식에서 부모 값 전달하는 방법
useEffect 변화의 감지
css에서 텍스트 ellipsis(‘…‘)말줄임 표시 처리하는 방법
알아두면 유용한 잡다한 CSS
들어가는 말 (잡담)
사실 해당 기능은 9월에 진작 끝냈지만 ^^…
9월에 프로젝트 일이 휘몰아치면서 이제야.. 적게 되었다.. (과거의 나 반성해..) 한달 정도 늦게 작성하는 거라 기억이 많이 휘발되었지만, 그 당시에 막혔던 부분들을 복습한다 생각하고 작성해보겠다.
SelectBox 직접 구현하기
html에 select 태그가 존재하지만, 드로맵 프로젝트에서 해당 태그를 사용할 수 없었다. 디자이너 분이 select 박스의 디자인을 이쁘게 만들어주셨는데.. select 태그로는 해당 디자인 구현이 불가능해서 ㅜ… 직접 select 박스를 만들었어야 했다.
기본적인 select 박스의 틀은 해당 영상을 참고하여 구현하였다.
다 만들고 생각했던건데, 굳이? 영상 참고 안하고 해도 됐을 것 같았다. 처음 만든다는 두려움 때문에 참고자료를 찾고 싶어서 고생했던게 기억 난다..
쨋든, select 박스의 틀은 본인이 원하는 디자인으로 만들면 된다.
select 리스트 열고 닫기
나는 select 리스트(li(값)들)를 열고 닫을 수 있도록 img(여닫기 화살표)를 구현해줬다. useState로 isActive를 만들고 기본값은 false(닫힘)으로 했다. 버튼(여닫기) 이미지의 style에 transform: isActive
로 하여, isActive가 false이면 화살표가 아래로, true이면 화살표가 위로 (rotate(-180deg)
)향하도록 했다.
또한, class가 sbtn-content를 보면 display에도 isActive를 주었는데, isActive값이 false이면 none을 (ul 태그가 보이지 않음) true면 block을 (ul 태그가 보임/ 즉, select 리스트 박스가 보임) 해줬다.
map 함수를 사용하여, select list의 li 값 뿌리기
정말 간단하게 그냥 ul 태그 하단에 넣고 싶은 li 값들을 다 쓰면 되겠지만! 해당 selectBox 컴포넌트는 여러 페이지에서 재사용이 필요하기 때문에 map 함수를 이용하여 값들을 넣어주기로 했다.
추후에 백엔드로부터 해당 select에 넣을 값 리스트를 받기만 하면 되도록 말이다!

따라서 나는 일단 dataList
라는 배열 변수를 만들어줬다.
해당 배열 안에 key는 item이고 value에는 원하는 값들을 넣은 객체를 만들어줬다.
그리고 ul 태그 하단에 dataList
를 map함수를 통해 뿌려줬다.
이때, map은 함수니까 꼭! 중괄호{}
로 감싸야 한다는 것을 잊지 말자.
중괄호 안에 dataList.map((it)=>{return(<li>{it.item}</li>)})
코드를 작성했다. dataList 배열을 map을 통해서 반복을 할건데, li 태그의 값(?)에 it.item을 넣어준다. 라는 것이다.
Error 발생, expected an assignment or function call and instead saw an expression
위의 설명한 방법으로 했을 때는 에러가 발생하지 않겠지만.. 과거에 작성했을 때는 중괄호 안에 return을 작성하지 않았다. 그러니 expected an assignment or function call and instead saw an expression.
에러가 발생했다.
에러가 발생한 이유는 map을 사용할 때, .map(()=>{})
형식으로 작성했다면 반드시 중괄호 안에 return이 존재해야 한다는 것이었다.
만약에 return을 작성하고 싶지 않다면 요소를 소괄호()
로 감싸도록 하자. dataList.map((it)=>(<li>{it.item}</li>))


우리 디자이너가 스크롤바 역시도 이쁘게 디자인해줬기 때문에 스크롤바 스타일 역시 변경해야 했다.
스크롤바 스타일은 -webkit-scrollbar
를 통해 변경해준다.
스크롤바 변경 webkit의 의미는 다음과 같다
다음 속성들을 적용하여 스크롤바를 변경해주었다.

스크롤바 위치 변경
스크롤바 커스텀을 하고 나서도 스크롤바의 위치가 너무 오른쪽에 붙어있어, 해당 바를 좀 더 왼쪽으로 옮겨주고 싶었다.
이때 좀 바보짓을 했는데…ㅋ
박스에 패딩을 주고 뭐 별 짓을 다 했는데도 스타일 적용이 안돼서.. 애먹고 있다가, “다시 스크롤바의 위치가 어디일지를 보자” 란 생각이 들어 다시 개발자도구를 통해 확인해보니 ul 태그에 해야 할 것 같은 것이다…! 그래서 ul태그에 margin을 주었더니 스크롤바의 위치가 안 쪽으로 이쁘게 잘 들어갔다..ㅎ..
결론: 스크롤바 위치를 옮기고 싶으면 ul태그에 margin 주기… 잊지말것..
스크롤바 위치 수정 전

스크롤바 위치 수정 후

스크롤바 수정 전/후 영상

다음 편에는 선택한 값을 띄워주는 것을 작성하도록 하겠다.
참고
HTML, CSS: 스크롤바 스타일(디자인)하는 방법 - webkit-scrollbar > Css 브라우저 스크롤바 스타일 지정, 바꾸는 방법 알아보기
🌟 스크롤 바(Scrollbar) 스타일링 💯 총정리
리액트: 버튼 클릭시 요소 표시, 숨기기 >React hooks로 Select Option 처리하기
React:expected an assignment or function call and instead saw an expression.
React에서 (api 데이터) json형식 데이터 가져오기 및 원하는 데이터 추출 등 데이터 관리 방법!
Webpack & Babel
Webpack?
오픈 소스 자바스크립트 모듈 번들러(여러개를 합쳐주는 것)로, 여러개로 나눠져 있는 파일들을 하나의 자바스크립트 코드로 압축하고 최적화하는 라이브러리이다.

Webpack의 장점
-
여러 파일의 자바스크립트 코드를 압축하여 최적화 할 수 있어 로딩에 대한 네트워크 비용을 줄일 수 있다.
-
모듈 단위로 개발이 가능하여, 가독성과 유지보수가 쉽다.
모듈 단위로 개발의 말은, 한가지 파일에 모든 것들을 넣어두는 것이 아닌 파일을 여러개로 나눠서 Webpack으로 뭉쳐(번들)주는 것을 말한다. 파일을 나눴기 때문에 가독성이 올라가고 유지보수가 쉬워지는 것이다.
Babel?
최신 자바스크립트 문법을 지원하지 않는 브라우저를 위해서 최신 자바스크립트 문법을 구형 브라우저에서도 사용할 수 있게 변환시켜주는 라이브러리이다.
Creat-React-App
CRA는 Webpack이나 Babel을 따로 설치하지 않아도 알아서 설치하고 설정해주는 것이다.
npx creat-react-app <폴더명>
# 혹은 cra를 설치하고 싶은 폴더로 이동
npm creat-react-app ./
npx??
노드 패키지 실행을 도와주는 도구이다. CRA는 npm 레지스트리(registry)에 있는 패키지를 우리의 폴더에서 실행해서 리액트를 설치해준다.
npm 레지스트리(라이브러리가 저장된 곳) ⊃ CRA 패키지 → npx를 통해 CRA 설치 npx creat-react-app ./
→ 우리가 사용할 폴더
React?
사용자 인터페이스(보이는 것)를 만들기 위한 라이브러리이다.
프레임워크 vs 라이브러리
-
프레임워크
Ex) Vue.js, Angular
어떠한 앱을 만들기 위해 필요한 대부분의 것을 가지고 있다.
즉, 프레임워크는 라이브러리를 포함한다. (프레임워크 안에는 많은 라이브러리가 존재)
프레임워크는 라이브러리를 포함하고 작성한 소스코드를 호출한다.
소스코드는 어떤 기능을 구현하기 위해서 라이브러리를 호출하게 된다.
-
라이브러리
Ex) React.js
어떠한 특정 기능을 모듈화 한 것이다.
React가 프레임워크가 아닌 라이브러리인 이유
React는 전적으로 UI를 랜더링하는데 관여하기 때문이다. (View, Model, Controller 중에서 View만 담당)
React 생태계
- 라우팅(페이지 이동): react-router-dom
- 상태관리: redux, recoil, mobx
- 테스트: Jest, Mocha
React는 UI 담당이다. 화면을 바꾸는 라우팅은 react-router-dom 모듈을 사용하며, 상태 관리는 redux, mobx 등 여러 모듈을 사용한다. 빌드를 위해서는 webpack, npm 등 과 테스팅을 위해서는 Eslint, Mocha 등을 이용하기 때문에 React는 프레임워크가 아닌 라이브러리이다.
브라우저가 그려지는 원리 및 가상돔
React의 주요 특징 중 하나는 가상DOM을 사용한다는 것이다.
가상DOM이 무엇인지 알기에 앞서서 왜 사용해야 하는지 브라우저가 렌더링하는 과정을 살펴보자.
웹 페이지 빌드 과정(Critical Rendering Path, CRP)
CRP는 웹 브라우저가 서버로부터 HTML 문서를 받아서 읽고, 스타일을 입히고 뷰포트에 표시하는 과정이다.
브라우저가 서버에서 페이지에 대한 HTML 응답을 받고 화면에 표시하기 전까지 여러 단계가 존재한다. 단계는 아래 그림과 같다.

-
DOM tree 생성
렌더 엔진이 문서를 읽어들여서 파싱하고 어떤 내용을 페이지에 렌더링할 지 결정한다. (문서를 읽어서 DOM tree 생성)
-
Render tree 생성
브라우저가 DOM과 CSSOM을 결합하는 곳이며, 화면에 보이는 모든 콘텐츠의 콘텐츠와 스타일 정보를 모두 포함하는 최종 렌더링 트리를 출력한다. 화면에 표시되는 모든 노드의 콘텐츠 및 스타일 정보를 포함한다.
-
Layout
브라우저가 페이지에 표시되는 각 요소의 크기와 위치를 계산한다.
-
Paint
실제 화면에 그린다.
CRP에는 문제가 존재하는데, 어떤 인터렉션에 의해서 DOM에 변화가 발생하면 매번 Render Tree가 재생성된다. 즉, 모든 요소의 스타일을 다시 계산하고 Layout하고 Repaint 하는 과정을 다시 한다.
인터렉션이 적은 웹이라면 괜찮겠지만 엄청나게 많다면? 불필요하게 DOM을 조작하게 되기에 비용이 너무 커진다.
이러한 문제를 해결하기 위해서 나온 것이 가상DOM(Virtual DOM)이다.
가상DOM(Virtual DOM)
가상DOM? 실제 DOM을 메모리에 복사해준 것이다.

데이터가 바뀌면 가상DOM이 렌더링되고 이전에 생긴 가상DOM과 비교해서 바뀐 부분만 실제 DOM에 적용시켜준다. 바뀐 부분을 찾는 과정을 Diffing이라고 부르며, 바뀐 부분만 실제 DOm에 적용시켜주는 것을 재조정(reconciliation)이라고 부른다.
가상DOM은 요소가 30개가 변하더라도 한번에 묶어서 실제 DOM에 한번만 수정을 하기 때문에 DOM을 조작하는 비용이 줄어들게 된다.
프로젝트를 진행하던 중, 인증번호 입력은 숫자만 입력이 가능하게 하기 위해서 type='number'
로 제한하였다. 그러면 number 타입이 가지고 있는 버튼이 나타나게 된다. 해당 버튼이 굉장히 거슬리기 때문에.. 버튼을 삭제하기로 했다.
화살표 버튼 삭제 전

화살표 버튼 삭제 css 코드
input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
css를 사용하다보면 webkit
과 같은 접두어가 붙은 친구들을 종종 볼 수수 있다. 해당 접두어는 다음과 같은 의미를 갖는다.
-webkit-
: 구글, 사파리 브라우저에 적용
-moz-
: 파이어폭스 브라우저에 적용
-ms-
: 익스플로러에 적용, 보통 생략합니다.
-o-
: 오페라 브라우저에 적용
내가 추측하기로는 해당 화살표 버튼은 브라우저에서 기본적으로 적용되는 사항이기 때문에, 브라우저 접두어를 붙여서 해당 버튼을 안보이게 (-webkit-appearance: none
) 해줘야 하는 것 같다.
화살표 버튼 삭제 후

참고
CSS 접두어 webkit 등
number 버튼 없애기