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 버튼 없애기
UMC 동아리의 여름방학 프로젝트가 시작되었다. 프로젝트의 이름은 DrawMap이다. 상세한 설명은 나중에 따로 정리하고..
프로젝트를 시작하면서 일단 내가 맡은 부분은 버튼 컴포넌트를 제작하는 것이다.
버튼은 다양한 종류가 존재하는데 나는 크게 두개로 나뉘었다. Button 과 ButtonSelct. Button은 단순 클릭만 하는 버튼이라면 ButtonSelect는 말 그대로 Select, 드롭다운해서 항목을 선택하는 버튼이다. ButtonSelect는 추가 디자인이 필요해서 일단 미뤄두고, 해당 글은 Button 컴포넌트에 관한 글이다.
Button 컴포넌트의 props는 일단 3가지로 type, img, content이다.
img는 이름 그대로 이미지를 넣는 부분이다.
content는 버튼이 이미지가 아닌 글자를 담고 있는 버튼이거나, 클릭하거나 hover하면 이미지였던 버튼이 글자를 담고 있는 버튼으로 바뀔 때 나타나야 할 버튼 글자를 담는다.
type은 버튼의 종류는 작성하는 부분이다. 버튼의 종류는 default, arrow, text-btn, more, text-more, acting이 있다.

-
more
이미지의 가장 상단의 버튼으로, default 버튼보다 사이즈가 작기 때문에 따로 분류하였다.
-
default
모든 버튼의 기본이 되는 버튼이다. 따로 type을 지정해주지 않아도 될 것 같긴 하지만, 버튼을 사용할 때 확실하기 분류해주기 위해서 default라는 type명을 부여했다.
-
acting
default 버튼에서 hover나 click시 이미지였던 버튼이 글자로 변경되는 버튼이다. 해당 버튼때문에 따로 span 태그를 추가하여 style로 값이 들어오지 않는다면 display:none
을 해줬다.

-
text-btn
버튼의 내용이 글자만 존재하는 버튼이다. 이미지는 들어오지 않기 때문에 img 태그의 style에 img가 들어오지 않는다면 display:none
으로 변경되도록 해줬다. 이걸 해주지 않는다면 해당 버튼을 사용할 때 이미지 alt가 보이게 된다.
-
ButtonSelect
Select 버튼인데 추후 따로 컴포넌트를 만들어서 제작할 예정
-
arrow
이미지가 화살표일 때, 해당 타입의 버튼을 사용해준다. default와 more 버튼과는 이미지 사이즈가 약간 다르기 때문에 따로 정의해줬다.
-
text-more
해당 버튼은 text-btn과 다르게 사이즈가 굉장히 작기 때문에, 따로 정의해줬다.
auto와 max-content 설명
버튼 사이즈에 맞춰서 사용할 이미지의 사이즈가 자동으로 조절되도록 코드를 짜고 싶었다. 최대한.. 코드를 줄이고 싶었달까…
서칭의 서칭 끝에 다음과 같이 아주 좋은 방법을 찾아냈다.
-
width: auto;
width 속성을 명시하지 않으면 auto가 기본값으로 사용이 된다.
100%가 기본값이 아니다..
auto를 사용하면 브라우저가 해당 요소의 width 속성값을 자동으로 계산해준다. 부모 요소로 부터 주어진 가용 너비에서 좌우 margin 크기를 제외한 너비를 width 값으로 사용한다.
-
width: max-content
요소의 너비를 최대한 늘이고 싶을 때 사용한다. 최대 너비 역시 해당 요소가 담고 있는 컨텐츠에 의해 변경된다.
부모 요소에 이미지 사이즈 자동 맞추기

부모 요소의 width가 존재해도 이미지 width와 height가 있기 때문에 대부분 부모 박스 밖으로 튀어나온다.
그렇기에 해결 방법(?)은 다음과 같다.
부모요소에 width를 주고 img에 width:100%
를 주면 부모 요소의 너비의 100%를 차지하게 된다. 그리고 height:auto
를 주면 원래 이미지가 가지고 있던 비율에 맞춰서 자동으로 높이가 조절된다!
글자 내용(컨텐츠)에 따라 버튼 사이즈 자동 조절

text-btn(이미지에서는, level로 명시)의 경우에는 버튼 안의 내용이 글자이다. 이때, 버튼에 width를 한정시키게 되면 안의 컨텐츠가 버튼을 삐져나가거나 세로로 글자가 배열되거나.. 쨋든 이상하게 변한다.
해당 문제는 width를 max-content로 하면 해결된다. width: max-content
로 하면 글자(컨텐츠)의 사이즈에 맞춰 자동으로 width가 조절된다!
마치며
새로운 프로젝트를 시작하게 되었는데, 어김없이 몰랐던 부분이 나타나고 그러다보면 새롭게 알게되는 내용이 많은 것 같다. 이래서 프로젝트는 꼭 해봐야 하는 것 같다.
그리고, 역시 효율적으로 살기 위해서 사람은 노력을 해야 하는 것 같다. 나중에 귀찮아지지 않기 Button 컴포넌트를 수정하지 않기 위해서 최대한 효율적으로 코드를 작성하려고 하는데 생각보다 쉽지 않다. 편히 살려면 그만큼 노력이 필요한게 맞다…
참고
부모 요소(element) 속 이미지 크기 맞추기
CSS의 width 속성과 너비 결정 매커니즘
input type='text'
값이 들어왔을 때, 배경색 변경하기
값이 들어왔을 떄, 배경색을 변경시켜주는 걸 css로 구현하기 위해서는 일단 input에 required 속성을 추가해야 한다. 값으로 required를 넣어, required="required"
를 input 태그 안에 추가한다.
글을 작성하다가, required="required"
의 내용은 안 나와서.. 그냥 required
를 작성하면 동작하지 않을까? 라는 의문이 들어서 테스트를 해본 결과 그냥 required
를 작성해도 잘 작동된다…!

해당 속성을 추가하고 css는 input:valid
일시, 즉 input에 값이 들어있을 때 배경색상을 흰색으로 변경하게 한다.

🚨주의🚨
input에 required 속성을 추가하지 않으면, 값을 입력했다가 다시 모든 값을 지우고 focus를 해제했을 때, 배경색상이 회색(값이 없을 때)으로 변경되지 않고 흰색(값이 있을 때)으로 유지된다.
radio를 checked 했을 때, radio와 연결된 label의 색상을 변경시켜주기 위한 코드(css)는 다음과 같다.
input:checked + label {
color: red;
}
input이 checked 되었을 때, 인접 형제 요소인 label의 color를 변경하는 css 코드이다.
그런데, 해당 코드를 작성하면 스타일이 적용되지 않는 문제가 발생했다. 나의 html 코드는 다음과 같았다.
<div>
<label>라디오와 연결된 라벨</label>
<input type="radio" />
</div>
위와 같이, label이 먼저 오고 input이 그 다음에 오는 형태이다. 이 순서가 가장 큰 문제였다…
radio가 checked시, label에 스타일을 적용하기 위해서는 radio가 먼저 오고 label이 그 다음에 왔을 때만 css 코드가 작동했다.
Bard에 물어본 결과 다음과 같은 답을 받았다.

그렇다고 한다… radio 앞에 label을 배치하게 되면 radio를 가리게 되어 작동하지 않는다고 한다..ㅜ
label을 앞에 했을 때, html은 잘 작동을 하는데 (label 클릭시, 연결된 radio가 체크되는) 스타일은 적용되지 않으니… 순서를 바꿔주기로 했다.

순서를 변경해주니 css 코드가 잘 작동했다.

그리고, checked일 때, radio 버튼의 색상을 변경할 때 그냥 color로 하면 스타일이 적용되지 않았다. 서칭을 해보니 accent-color
로 적용하면 색상이 잘 적용되었다.
accent-color
는 UI 컨트롤의 색상을 변경시켜줄 때 사용한다고 한다.

마무리
이번 기능들은 js가 아닌, css로도 충분히 가능할 것 같아서 css로 구현해보았다. css로 해서 금방 끝날 줄 알았는데… 생각보다 많은 난관에 봉착하여^^… 시간이 꽤 걸렸다. 역시, 코딩의 세계는 만만하지 않아…ㅜ
참고
CSS 선택자
label 태그
accent-color
checked
프로젝트를 진행하던중 textarea의 글자수를 세는 기능이 필요했다. 그래서 음~ 금방 끝나겠군 이라는 마음으로 빠르게 코드를 작성하였으나..? 문제가 생겨서 정말 ^^ 몇시간을 붙들었다..
그렇게.. 겨…우.. 해결할 수 있었다.
기존 코드
const [byte, setByte] = useState(0)
const countByte = (e) => {
setByte(e.target.value.length);
}
...
<textarea
...
onChange={countByte}
{...register
...}
></textarea>
<span>{byte}/150</span>
기존 코드에서 react-hook-form을 사용해야 했고, textarea의 입력값이 필요했기에 onChange를 통해서 value값을 받아왔다.
근데 아무리 해도 값이 안 나오고…
그래서 서칭을 계속 했는데 나도 이런 문제인 줄 알고 currentTarget
으로 바꿨으나 실패..
그렇게 같이 프로젝트를 하는 친구에게 코드를 봐달라고 했고.. 문제는 react-hook-form의 register이었다는 것을 알게되었다…
문제
react-hook-form에도 onChange가 존재하는데, 이게 우리가 가장 널리 알고 있는 일반 이벤트 핸들러의 onChange와 이름이 같기 때문에 충돌이 나는 것 같았다.. 흙..
따라서, react-hook-form의 onChange를 사용하여 코드를 변경하기로 했다.
해결 코드
register안에 onChange를 넣어 기존 코드에서 countByte 함수를 onChange에 바로 작성하였다. 이렇게 하니 기존코드보다 더욱 간결해졌다.

구현 영상

마무리
해당 문제를 통해 아직 react-hook-form을 잘 모르고 있다는 것을 알게되었다… 더.. 많은 공부가 필요할 듯 하다..
참고
react hook form에서 form데이터 관리하기
입력을 다루는 다양한 방법, React-hook-form
stack overflow
Hook Form으로 상태 관리하기.