javascript 및 typescript는 개인적으로 혼자 작업하더라도 prettier가 없으면 코드 읽기가 불편하다고 생각한다. 그래서 어제 적용하려다가 귀찮아서 안하고 있었는데, 오늘은 실제로 적용했다. 이런건 내가 직접 하나하나 셋팅하는 것 보다 이미 react에 국룰 처럼 자리잡아 있는 걸 쓰는게 좋다고 생각해서 블로그를 참조했다.
React - vscode에서 React개발을 위한 Prettier, ESLint설정
그리고 저장 시에 항상 동작할 수 있도록 설정을 건드렸다. 그냥 전역으로 걸어버릴까 하다가 나중에 귀찮아 지는 일을 방지하고자 react project에만 onSave로 동작하고 prettier를 기본적 formatter로 설정했다.
}
...
"[javascriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
...
}
화면 안에 있는 요소들(텍스트, 버튼, 이미지 등)의 위치와 배치 방식이다. html은 기본적으로 작성한 순서대로 화면에 표시되기 때문에 이를 도와주기 위해 여러 CSS 기술들이 있는데, 이 중 유명한 것이 Flexbox와 Grid이다. flexbox는 가로 세로로 일렬로 나열하는 것에 강한 것 같고 grid는 격자 설계에 유리한 것 같다. 자세하게는 둘다 사용해보아야 알 것 같은데, 지금 만들고 있는 youtube clone coding에서는 flex기반 layout을 배우고 있어 우선은 ‘내가 계속 사용하고 있는 flex- 가 layout을 편하게 만들어주는 css 도구이구나~’ 정도로 이해하고 넘어가겠다.
이걸 검색하니 margin, border, padding이 함께 나왔다. 이걸 css box-model이라고 부르는 듯 하다.

Outline과 Border의 차이가 뭘까
이름이 비슷하게 생겼는데 outline은 focus 되었을 때 시각적으로 표현하려고 사용한다고 한다. outline은 css box-model에 들어가지 않는데 이게 뭘까? 좀 찾아봤는데 시원한 글은 못봤고, 생각해 보았을 때 border는 실제 layout에 포함되는 영역인것 같고, outline은 그 border부분을 따라 그리는 그 위에 덮어지는 선의 느낌같다. 그런데 실제로 덮어져 있진 않고, border 외곽에 위치한다. 거리를 벌리고 싶다면 offset 값을 줘서 벌릴 수 있다.
유튜브 화면을 마구 줄여보면 검색창이 없어지는데, 그렇게 되면 돋보기 아이콘이 생긴다.

이 돋보기 아이콘을 누르면, 긴 검색창이 나온다.

사실 이런부분까지 화면에서 체크해본 적이 없어서 신기했다. 화면을 구성하는 것은 많은 디테일이 필요한 작업인 것 같다. 여튼, 그래서 우리는 저걸 구현하기로 했다.
이를 위해 화면 크기에 따라 요소들이 나타나고 사라지는 것을 먼저 간단히 구현했다. md:flex 와 md:hidden을 적절히 적용했다.
...
<Button size='icon' variant='ghost' className='md:hidden'>
<Mic />
</Button>
...
이렇게 하면 화면이 medium 사이즈 보다 작아지면 특정 요소를 표시하거나 숨기는 것이 가능하기에 유튜브에서 나오는 것처럼 만들 수 있다. 허나 문제는 우리는 위의 사진처럼 버튼을 누르면 화면의 layout이 바뀌는 작업을 하고 싶다. 간단하게 생각해보아도 “돋보기 Button을 누르면 어떤 변수를 변경해서 변수에 따라 layout들이 case로 나뉘어 동작하면 안될까?” 라는 생각이 번뜩 들었다. 이를 위해 react에서는 state 변수라는 것을 사용하는 것 같다. 선언은 아래와 같이 했다.
export function PageHeader() {
const [showFullWidthSearch, setShowFullWidthSearch] = useState(false);
...
}
찾아보니 배열 첫번째 요소는 값, 두번째 요소는 값을 변경하는 함수이다. 값에 직접 접근이 가능하지만 값을 변경할 때에는 변경 함수를 사용해야하는 듯 하다. useState 괄호 안에 들어가는 값은 처음에 값을 어떤 값으로 설정할 지 초기화하는 작업이다. 지금 위의 state 변수 showFullWidthSearch에 fasel 값이 담겨 있는 것이다.
이걸 layout의 변화를 줄 entry point에 적용하면 된다. 위의 케이스에서는 화면이 줄어들면 돋보기 모양이 나오고, 이 돋보기 모양을 나오면 layout의 변화를 주어야 하므로 저 돋보기 버튼이 entry point이다. 이 버튼을 click하면 setState 함수를 호출해 state 변수 값을 변경하면 된다.
...
<Button
onClick={() => setShowFullWidthSearch(true)}
size='icon'
variant='ghost'
className='md:hidden'
>
<Search />
</Button>
...
이제 부터는 원래 상상했던 가설 그대로 삼항연산자를 통해 layout 표시를 적용해주면 된다.
<form
className={`gap-4 flex-grow justify-center ${showFullWidthSearch ? 'flex' : 'hidden md:flex'}`}
>
...
</form>
그리고 이를 되돌려줄 뒤로가기 버튼에는 삼항연산자가 아닌 조금 다른 방식으로 접근을 한다. 삼항연산자를 사용하는 이유는 상황에 따라 1번 layout이었다가, 2번 layout이었다가 변화가 되는 느낌이라면, “뒤로가기” 버튼은 “특정 조건에만 존재” 해야하는 요소이다. 그래서 hidden 과는 엄연히 차이가 있으므로 아래와 같이 표현한다.
...
{showFullWidthSearch && (
<Button
onClick={() => setShowFullWidthSearch(false)}
type='button'
variant='ghost'
size='icon'
className='flex-shrink-0'
>
<ArrowLeft />
</Button>
)}
...
더 간단하게 할 수 있을 것 같은데, 왜 이렇게할까? let 변수를 하나 전역으로 선언하고 초기화 해두면 위와 똑같은 방식으로 동작할 수 있지 않을까?
해보면 알겠지만 당연히 그렇게 안된다. 정확히는 되긴 되는데 더 복잡한 과정을 거쳐야 한다. 이걸 알기 위해서는 react에 대해서 조금 알아야할 필요가 있다.
태초의 web page
원래 웹페이지는 html로 구성되어 있다. 지금 우리가 보는 대부분의 web page는 전부 html 형식으로 되어있다. 개발자 도구 버튼을 눌러보면 바로 알 수가 있는데, html에는 큰 단점이 있다. 한 번 만들어진 페이지가 변경되기 어렵다 라는 점이었다. 사람들은 정적인 웹페이지 내부에서 동적인 작업을 하고 싶었고 그래서 다른 방법을 생각하게 된다.
javascript의 등장
web browser가 web page의 html을 읽고 화면을 보여주는 것이기에 browser에 동적인 작업을 할 수 있도록 기능을 추가했다. 이 때 사용하는 언어가 바로 javascript이다. <script> tag를 통해서 javascript를 html에 함께 작성하게 되면 browser가 이를 해석하여 실행해준다. 지금도 동적인 화면을 구성하기 위해 javascript가 이용되고 있다. 그런데 사람들은 더 단순하게 코드를 작성하고 싶었다. javascript가 어차피 동적인 작업을 해준다면, html도 javascript가 생성해줄 수 있지 않을까?
React의 등장
그래서 react와 같은 기술들이 등장했다. react는 javascript 만으로 web page를 작성할 수 있고, 나중에 rendering 될 때 이를 html로 바꿔준다. 이 정도만 알아도 왜 let을 사용하면 안되는 지 감이 온다.
let으로 초기화 하고 layout을 이렇게 변경하겠다고 아무리 javascript 코드에서 명령을 해도 바뀐 let 변수를 통해 rerendering이 되지 않았기 때문에 화면은 바뀌지 않은 것이다. 이를 위해 let을 사용할 것이라면 render 함수를 재호출 해야하는데, 이렇게 작성하면 rerendering을 해야하는 모든 코드에 render 함수를 작성해야 한다. 하나의 변수 변경으로 변화하는 layout이 100개면 모드 render 함수를 적어야 한다. 이는 너무 비효율적이다. 그래서 state 변수를 선언하여 setState함수로 변수를 변경하면, react에서 알아서 이를 감지해서 필요한 부분을 rerendering 해준다.

이제 useState를 통한 interaction 구현을 마지막으로 page header 구현이 마무리되었다.

import { ArrowLeft, Bell, Menu, Mic, Search, Upload, User } from 'lucide-react';
import logo from '../assets/react.svg';
import { Button } from '../components/Button';
import { useState } from 'react';
export function PageHeader() {
const [showFullWidthSearch, setShowFullWidthSearch] = useState(false);
return (
<div className='flex gap-10 lg:gap-20 justify-between pt-2 mb-6 mx-4'>
<div
className={`flex gap-4 items-center flex-shrink-0 ${showFullWidthSearch ? 'hidden' : 'flex'}`}
>
<Button variant='ghost' size='icon'>
<Menu />
</Button>
<a href='/'>
<img src={logo} className='h-6' />
</a>
</div>
<form
className={`gap-4 flex-grow justify-center ${showFullWidthSearch ? 'flex' : 'hidden md:flex'}`}
>
{showFullWidthSearch && (
<Button
onClick={() => setShowFullWidthSearch(false)}
type='button'
variant='ghost'
size='icon'
className='flex-shrink-0'
>
<ArrowLeft />
</Button>
)}
<div className='flex flex-grow max-w-[600px]'>
<input
type='search'
placeholder='Search'
className='rounded-l-full border border-secondary-border shadow-inner
shadow-secondary py-1 px-4 text-lg w-full
focus:border-blue-500 outline-none'
/>
<Button className='py-2 px-4 rounded-r-full border-secondary-border border border-l-0 flex-shrink-0'>
<Search />
</Button>
</div>
<Button type='button' size='icon' className='flex-shrink-0'>
<Mic />
</Button>
</form>
<div
className={`flex flex-shrink-0 md:gap-2 ${showFullWidthSearch ? 'hidden' : 'flex'}`}
>
<Button
onClick={() => setShowFullWidthSearch(true)}
size='icon'
variant='ghost'
className='md:hidden'
>
<Search />
</Button>
<Button size='icon' variant='ghost' className='md:hidden'>
<Mic />
</Button>
<Button size='icon' variant='ghost'>
<Upload />
</Button>
<Button size='icon' variant='ghost'>
<Bell />
</Button>
<Button size='icon' variant='ghost'>
<User />
</Button>
</div>
</div>
);
}
이제 코드가 좀 보기 좋아졌다. interaction이 있는 페이지를 만드는 것은 항상 즐거운 일인 것 같다. 근데 실제 youtube를 보면 화면이 작을 때 검색을 눌렀어도 나중에 화면을 다시 늘리면 그 창이 사라지게 하던데, 이는 내가 따로 구현해보고 싶다. 오늘은 여기까지.
출처