@import
와 @font-family
의 차이점: 폰트를 적용할 때 @import
와 @font-face
를 통한 폰트 적용의 차이점과 각 방법의 장단점이 궁금해졌다.이번 학습을 통해 React와 CSS 관리의 기초적인 부분을 더욱 확실히 다질 수 있었다. 앞으로는 더 복잡한 상태 관리와 데이터 흐름을 관리하는 패턴들을 학습하고, 이를 실제 프로젝트에 적용해보고 싶다. 또한, 다양한 조건부 렌더링 기법을 익혀서, 복잡한 UI를 구현하고 코드의 가독성과 유지보수성을 높일 수 있도록 해야겠다.
node_modules/
는 GitHub에 올리지 않기 때문에 실제 환경에서는 수정이 반영되지 않을 가능성이 높음Google Fonts에서 원하는 폰트를 선택하여 @import
문을 사용해 CSS 파일에 추가하기
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap");
body {
font-family: "Inter", sans-serif;
}
대표적으로 눈누 사이트가 있음
웹 폰트를 font-face
로 제공한다면 복사해서 사용하면 되지만 그렇지 않다면 직접 폰트를 로컬에 다운로드 해야함
/src/assets/fonts
등의 경로에 저장font-face
등록@font-face {
font-family: "JejuDoldam"; /* 사용할 폰트의 이름을 정의 */
src: url("./assets/fonts/EF_jejudoldam\(OTF\).woff2"),
url("./assets/fonts/EF_jejudoldam_OTF_.woff"); /* 폰트 파일의 위치 */
}
.woff
는 더 널리 지원되지만, .woff2
는 더 작은 파일 크기와 더 나은 성능을 제공함@import
와 @font-family
의 차이점
on
이 붙음onClick
과 같은 이벤트 속성에 함수를 직접 전달함<!--HTML에서 버튼 클릭 이벤트를 처리하는 방식-->
<button onclick="activateLasers()">
Activate Lasers
</button>
// React에서 버튼 클릭 이벤트를 처리하는 방식
<button onClick={activateLasers}>
Activate Lasers
</button>
HTML에서는 이벤트 핸들러에서 false
를 반환하여 기본 동작을 방지할 수 있음.
예를 들어, 폼 제출을 막기 위해 return false
를 사용함
<form onsubmit="console.log('You clicked submit.'); return false">
<button type="submit">Submit</button>
</form>
하지만 React에서는 false
를 반환해도 기본 동작이 방지되지 않음.
기본 동작을 방지하려면 preventDefault()
메서드를 명시적으로 호출해야 함
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
React 이벤트는 기본 HTML 이벤트와 유사하지만, React 이벤트 시스템으로 감싸져 있음.
이 시스템은 크로스 브라우저 호환성을 보장하며, 추가적인 기능을 제공함
타입스크립트를 사용할 때, 이벤트 핸들러의 타입을 명시적으로 지정하지 않아도 자동으로 추론됨. 콜백 함수를 직접 작성하면 타입스크립트가 올바른 타입을 추론하여 적용함.
const onClickHandler = (event: React.MouseEvent<HTMLButtonElement>) => {
alert('버튼이 클릭되었습니다.');
};
컴포넌트 간에 데이터를 전달하는 방법
부모 컴포넌트가 자식 컴포넌트로 데이터를 전달하는 방법
UI 구성 요소 간의 독립성과 재사용성을 높이는 핵심 개념
읽기 전용(Read-only): Props는 자식 컴포넌트 내에서 수정할 수 없는 읽기 전용 데이터로 부모 컴포넌트가 전달한 값이 변경되면 자식 컴포넌트가 다시 렌더링됨
이 원칙은 컴포넌트의 예측 가능성을 유지하는 데 중요한 역할을 함
단방향 데이터 흐름: Props는 부모에서 자식으로만 전달됨
자식 컴포넌트는 부모 컴포넌트의 상태나 데이터를 직접 변경할 수 없으며, 변경이 필요할 경우 부모에게 알려야 함
다양한 데이터 타입: Props는 문자열, 숫자, 배열, 객체, 함수 등 다양한 데이터 타입을 전달할 수 있음
이로 인해 컴포넌트 간의 기능과 인터페이스를 유연하게 정의할 수 있음
기본 값 설정: Props는 기본 값을 설정할 수 있음
이를 통해 부모 컴포넌트에서 특정 값을 전달하지 않을 경우에도 자식 컴포넌트가 예상 가능한 동작을 수행할 수 있음
'읽기 전용', '단방향 데이터 흐름'이라고 했는데 useState의 setter를 props로 받으면 데이터를 변경할 수 있는 거 아닌가?
특정 태그의 컨텐츠 자체를 prop으로 넘겨주는 것
JSX 태그 사이에 있는 내용을 자식 컴포넌트로 전달함
const Wrapper = (props: { children: React.ReactNode }) => {
return <div className="box">{props.children}</div>;
};
export default Wrapper;
특정 조건에 따라 컴포넌트나 요소를 렌더링하는 것
import { useState } from "react";
const App = () => {
const [isLogin, setIsLogin] = useState(true);
if (isLogin) {
return (
<>
<h1>Hello, Login!</h1>
</>
);
}
return (
<>
<h1>Hello, Not Login!</h1>
</>
);
};
export default App;
짧은 조건부 렌더링에서 유용
import { useState } from "react";
const App = () => {
const [isLogin, setIsLogin] = useState(true);
return <>{isLogin ? <h1>Hello, Login!</h1> : <h1>Hello, Not Login!</h1>}</>;
};
export default App;
import { useState } from "react";
const App = () => {
const [isLogin, setIsLogin] = useState(true);
return (
<>
{isLogin ? <h1>Hello, Login!</h1> : <h1>Hello, Not Login!</h1>}
{!isLogin && <button onClick={() => setIsLogin(true)}>Login</button>}
{isLogin && <button onClick={() => setIsLogin(false)}>Logout</button>}
</>
);
};
export default App;
더 복잡한 로직이 필요하거나, 변수의 범위를 제한하고 싶을 때 유용
import { useState } from "react";
const App = () => {
const [isLogin, setIsLogin] = useState(true);
return (
<>
{(() => {
if (isLogin) {
return (
<>
<h1>Hello, Login!</h1>
<button onClick={() => setIsLogin(false)}>Logout</button>
</>
);
} else {
return (
<>
<h1>Hello, Not Login!</h1>
<button onClick={() => setIsLogin(true)}>Login</button>
</>
);
}
})()}
</>
);
};
export default App;
<h1 className={isLoggedIn ? ' text-5xl text-rose-500' : 'text-3xl underline'}>App Component</h1>
tailwind-merge의 twMerge
를 이용하면 중복된 클래스를 자동으로 제거하고, 동일한 속성을 설정하는 클래스 중 마지막으로 적용된 클래스를 우선시함
이는 코드의 중복을 줄이고, CSS 적용 우선순위와 관련된 문제를 방지하는 데 유용함
<h1 className={twMerge('text-3xl underline', isLoggedIn ? 'text-5xl text-rose-500' : '')}>App Component</h1>
import { twMerge } from 'tailwind-merge';
type TInputProps = React.ComponentPropsWithoutRef<'input'>;
const Input = ({ className, ...rest }: TInputProps) => {
return (
<>
<input
className={twMerge(
'inter w-[240px] h-11 text-sm font-medium px-[16px] py-[13.5px] border border-[#4F4F4F] rounded-lg placeholdr:text-[#ACACAC] outline-none',
className
)}
{...rest}
/>
</>
);
};
export default Input;
type TButtonProps = React.ComponentPropsWithoutRef<'button'>;
import { twMerge } from 'tailwind-merge';
const Button = ({ className, children, ...rest }: TButtonProps) => {
return (
<>
<button
className={twMerge('w-[77px] h-[44px] text-[#F5F5F5] rounded-[8px] bg-rose-500 cursor-pointer', className)}
{...rest}
>
{children}
</button>
</>
);
};
export default Button;