Next.js 프로젝트 캠프에 참가한 지도 벌써 2주가 지났습니다. 이번에도 새로운 내용들을 배울 수 있었는데요, 어떠한 것들을 알게 되었는지 정리해보도록 하겠습니다.
웹 개발에서 form 태그는 데이터 입력과 전송을 위한 기본적인 요소입니다. 하지만 React에서는 Submit으로 폼 데이터를 전송할 경우 페이지가 리로드 되어 문제가 발생할 수 있습니다. 예를 들어, 쇼핑몰에서 구매 정보를 입력하고 제출 버튼을 눌렀을 때 페이지가 새로 고침되면 작성 중이던 모든 정보가 사라지는 문제가 생길 수 있습니다. 제가 사용자라면 불편하다고 느낄 것 같습니다.
이러한 문제를 방지하기 위해 preventDefault()
메서드를 사용하여 form이 제출될 때 발생하는 기본 동작(페이지 새로 고침)을 막을 수 있습니다. 대신에, AJAX를 통해 비동기적으로 데이터를 서버에 전송해야 하죠. 이렇게 하면 사용자가 이렵한 데이터를 유지시킨 상태에서 폼을 제출할 수 있게 됩니다.
style-components
, Emotion
, Vanilla Extract
, TailwindCSS
등의 CSS-in-JS도 간단하게 다루어주셨는데요, 이 중에서 강사님께서는 TailwindCSS
를 사용하여 강의하실거라고 말씀해주셨습니다.
저는 여기에서 style-components
말고는 전부 처음 븓어보는 라이브러리들이라 흥미롭게 들었습니다. 간단하게 이들을 소개하자면....
Emotion은 CSS를 JavaScript 파일 내에서 작성할 수 있게 해주는 라이브러리로 코드의 응집성을 높여주며, javaScript 변수를 사용해 조건부 스타일링이나 동적 스타일링을 쉽게 구현할 수 있습니다. 그리고 자동으로 고유한 클래스명을 생성해 스타일 충돌을 방지해주기도 합니다. 하지만 Emotion
은 Internet Explorer 11을 지원하지 않기 때문에 자신이 진행하는 프로젝트가 ES11을 지원해야 하는지 아닌지 잘 알아보고 사용해야 합니다.
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const buttonStyle = css`
background-color: hotpink;
&:hover {
color: lightgreen;
}
`;
const Button = () => (
<button css={buttonStyle}>Click me</button>
);
Vanilla Extract
는 CSS를 작성하고 관리하기 위한 도구로, 빌드 타임에 CSS를 추출해준다고 합니다. 런타임 오버헤드를 제거해서 성능을 최적화하는 데 주 목적이 있다고 보시면 됩니다. (이를 '제로 런타임'이라고도 부르더군요)
// styles.css.ts
import { style } from '@vanilla-extract/css';
export const buttonStyle = style({
backgroundColor: 'hotpink',
':hover': {
color: 'lightgreen',
},
});
// Button.tsx
import { buttonStyle } from './styles.css';
const Button = () => (
<button className={buttonStyle}>Click me</button>
);
TailwindCSS
는 유틸리티 클래스 기반의 CSS 프레임워크로, 미리 정의된 유틸리티 클래스를 사용해 빠르게 스타일링할 수 있습니다. 그렇다보니 프로젝트 전반에 걸쳐 일관된 스타일링을 유지하기 쉽고, HTML에서 클래스명으로 스타일을 직접 확인할 수 있어 코드의 가독성이 높습니다.
<button class="bg-pink-500 hover:text-green-500">Click me</
tailwindCSS에는 불필요한 클래스 중복을 방지하고 최적화된 스타일링을 할 수기 해주는 twMerge
와 유틸리티 클래스를 하나의 문자열로 쉽게 결합할 수 있게 해주는 twJoin
이 있습니다.
import { twMerge } from 'tailwind-merge';
import { twJoin } from 'tailwind-merge';
const buttonClassMerge = twMerge('bg-pink-500 hover:text-green-500', 'bg-blue-500');
// 'bg-blue-500 hover:text-green-500'
const buttonClassJoin = twJoin('bg-pink-500', 'hover:text-green-500');
// 'bg-pink-500 hover:text-green-500'
TailwindCSS의 @apply를 사용해서 자주 이용하는 유틸리티 클래스를 하나의 커스텀 클래스에 모아 재사용할 수도 있습니다. 이렇게 하면 반복되는 유틸리티 클래스를 줄여 코드의 가독성을 높여줄 수 있어 유용한 것 같습니다.
/* styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply bg-pink-500 hover:bg-pink-700 text-white font-bold py-2 px-4 rounded;
}
}
<!-- index.html -->
<button class="btn">Click me</button>
React에서 list를 반복문으로 출력해보일 때, list 각각에 고유 키를 지정해주지 않으면 리액트에서 경고 메시지를 보내는데요, 그 이유는 상태 변경이 발생했을 React가 변경된 부분만 실제 돔에 적용하기 위해 새로운 가상 돔과 이전 가상돔을 비교하는 과정에서 각 리스트 항목마다 지니고 있는 고유 키를 바탕으로 변경된 항목만 찾아서 업데이트할 수 있기 때문입니다. 즉, 변경된 부분만 실제 돔에 적용하여 실제 DOM의 변경 사항을 최소화하고 성능을 최적화하기 위해서이죠. 이는 마치 우리가 쇼핑 목록을 작성할 때 각 항목에 번호를 붙여 두면, 어떤 항목이 추가되거나 제거되었는지 쉽게 알아차릴 수 있는 것과 비슷하다고 할 수 있습니다.
그렇다면 고유한 키를 생성하는 방법엔 어떠한 것들이 있을까요?
useId
훅은 비교적 최근에 등장한 훅인데요, 보통은 재사용성이 높은 컴포넌트 내의 폼 요소에 고유 ID를 생성할 때 사용합니다.
import React, { useId } from 'react';
const CheckboxList = () => {
const id = useId();
return (
<div>
<input type="checkbox" id={id} />
<label htmlFor={id}>Item 1</label>
</div>
);
};
nanoId
라이브러리를 이용해서도 고유 id를 생성할 수 있는데, 해당 라이브러리와 비슷한 것이 uuid
죠, uuid
보다 난수화가 잘 되어 보안 관련 로직에도 사용한다고 합니다.
import { nanoid } from 'nanoid';
const items = ['Item 1', 'Item 2', 'Item 3'];
const itemList = items.map(item => ({
id: nanoid(),
name: item
}));
const List = () => (
<ul>
{itemList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
Date.now
를 사용하여 고유한 타임스탬프를 키로 생성할 수 있습니다. 다만 이 방법은 빠른 연속 생성 시엔 고유 키가 충돌할 수 있어 다른 방법에 비해선 안전하지 않습니다. (추천하는 방법은 아님!)
const items = ['Item 1', 'Item 2', 'Item 3'];
const itemList = items.map(item => ({
id: Date.now() + Math.random(),
name: item
}));
const List = () => (
<ul>
{itemList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
마지막으로는 uuid
인데요, 왜 인지는 잘 모르겠으나 주변에서 보면 uuid
를 많이 사용해서 고유키를 생성하는 것 같습니다.
import { v4 as uuidv4 } from 'uuid';
const items = ['Item 1', 'Item 2', 'Item 3'];
const itemList = items.map(item => ({
id: uuidv4(),
name: item
}));
const List = () => (
<ul>
{itemList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
수업 실습을 하다가 똑같이 이벤트 핸들러를 전달해주는데 왜 하나는 화살표 함수로 감싸고, 하나는 그렇지 않은지 궁금했습니다. 그래서 그 이유를 조사해보니 우선, React에서는 이벤트 핸들러를 두 가지 방식으로 전달할 수 있다고 합니다. 하나는 함수 참조를 직접 전달하는 방법, 다른 하나는 화살표 함수로 감싸서 전달하는 방법입니다. 각각 어떠한 상황에 따라 사용되는지 살펴보겠습니다.
함수가 이벤트 객체를 인자로 받아 처리할 때는 함수 참조를 직접 전달할 수 있습니다. 이 방식이 가능한 이유는 함수가 이미 이벤트 핸들러로 적합하게 정의되어 있기 때문입니다.
const handleSubmitTodoList = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// submit 로직
};
<form onSubmit={handleSubmitTodoList}>
예를 들어, 위 예제에서 handleSubmitTodoList
함수는 event 객체를 인자로 받으므로 이벤트 핸들러로 적합합니다. 그래서 onSubmit 속성에 직접 전달할 수 있는 것이죠.
인자가 event 객체가 아닌 경우에는 함수를 호출할 때 이벤트 객체를 전달하지 않기 때문에, 함수 실행 결과를 이벤트에 전달하면 오류가 발생할 수 있습니다. 이런 경우에는 함수 자체를 이벤트 핸들러로 등록하기 위해 화살표 함수로 감싸서 전달합니다.
const onToggleTodo = (id: number) => {
// toggle 로직
};
<Checkbox onToggle={() => onToggleTodoList(list.id)} />
예를 들면, 위 예제에서 onToggleTodo
함수는 id
를 인자로 받고 있는데, 이는 이벤트 객체와는 무관합니다. 따라서 이벤트 핸들러로 직접 전달할 수 없고, 화살표 함수로 감싸서 전달해야 합니다.
결론적으로, 두 방식 모두 이벤트 핸들러를 전달하는 방법이나, 함수가 이미 이벤트 객체를 인자로 받도록 정의된 경우엔 직접 함수의 참조를 전달해도 가능하고, 인자로 이벤트 객체가 아닌 다른 값을 전달해야 하는 경우앤 화살표 함수로 감싸서 함수 실행 결과가 아닌 함수 자체를 전달하여 처리해줘야 이벤트 발생시 함수가 올바르게 실행됩니다.
이번 2주차에서는 form 요소와 같이 리액트 내부에서 이벤트 처리하는 방법, TailwindCSS 등등.. 이 외에도 (이 곳에 정리하진 않았지만) 렌더링을 최적화하는 메모이제이션 기법과 useContext, useReducer, zustand 같은 상태 관리 라이브러리 등 리액트에 관한 전반적인 것들을 복습하며 디테일 적인 부분들을 다질 수 있었습니다.
이제 남은 한주간은 본격적으로 서버와 통신하는 방법, Next.js를 다루는 방법을 배울 것 같은데 이제 시작인 것 같네요. 프로젝트때 그래도 1인분 하려면 남은 한주 동안 정말 열심히 공부해야 할 것 같습니다.😎 (웬지 2주차보다 더 빨리 지나갈 것 같은 기분... 하ㅣ하ㅏㅏ 화이팅!)
본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 과정(B-log) 리뷰로 작성 되었습니다.