[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 7일차(폰트, 컴포넌트)

김하은·2024년 7월 28일
0

개발자가 되고 싶어

목록 보기
12/12
post-thumbnail

🧐 회고

1. 배운 것 (핵심 내용 & 인사이트)

  • 컴포넌트 구조 및 이벤트 처리 방식: React의 기본 컴포넌트 구조와 이벤트 핸들링 방법을 배웠다.
  • props: 읽기 전용 특성과 단방향 데이터 흐름에 대한 깊이 있게 조사했다.
  • 즉시 실행 함수로 조건부 렌더링: 즉시 실행 함수를 사용해서 조건부 렌더링을 하는 방법을 새롭게 알게 됐다.

2. 앞으로 더 조사해볼 내용

  • Tailwind CSS: 커스텀 클래스 정의 및 효율적인 사용 방법을 더 고민해 봐야겠다.
  • 공통 컴포넌트 범용성: type과 스프레드 문법을 이용해서 컴포넌트의 범용성을 높이는 게 인상 깊었다. 이 외에 다른 벙법이 더 있는지 찾아볼 예정이다. (polymorphic components 공부하기)
  • @import@font-family의 차이점: 폰트를 적용할 때 @import@font-face를 통한 폰트 적용의 차이점과 각 방법의 장단점이 궁금해졌다.

3. 앞으로 적용해야겠다고 느낀 점

  • twMerge: Tailwind CSS에서 중복 클래스 문제를 해결하고 코드 가독성을 높이기 위해 twMerge를 적극적으로 활용할 계획이다.
  • Children: 재사용 가능한 컴포넌트를 설계할 때 children prop을 더 활용해야 겠다. 현재는 children을 활용할 수 있는 상황에도 일반 prop을 이용하는 경향이 있다.

4. 학습 평가 및 다짐/목표

이번 학습을 통해 React와 CSS 관리의 기초적인 부분을 더욱 확실히 다질 수 있었다. 앞으로는 더 복잡한 상태 관리와 데이터 흐름을 관리하는 패턴들을 학습하고, 이를 실제 프로젝트에 적용해보고 싶다. 또한, 다양한 조건부 렌더링 기법을 익혀서, 복잡한 UI를 구현하고 코드의 가독성과 유지보수성을 높일 수 있도록 해야겠다.


📒 학습 내용

Section 02 - 리액트 시작하기

💡 실무 팁

  • package.json 파일은 아주 중요한 파일이므로 직접 수정하지 않도록 해야함
  • 오류를 해결할 때 node_modules 폴더 내의 코드를 수정하지 않아야 함
    • 로컬에서는 정상적으로 동작할지라도 실제 환경에서는 문제가 발생할 수 있음
    • 특히, 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로 제공한다면 복사해서 사용하면 되지만 그렇지 않다면 직접 폰트를 로컬에 다운로드 해야함

  • 다운 받은 TTF 또는 OTF 파일을 woff로 변환
  • /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의 차이점

컴포넌트

이벤트

이벤트 타입

  • 태그에서 사용: HTML 태그에 이벤트를 지정할 때는 이벤트 타입 앞에 on이 붙음
  • 카멜케이스 사용: HTML과 달리 React에서는 이벤트 핸들러를 지정할 때 카멜케이스(CamelCase) 표기법을 사용함
  • 인라인 이벤트 처리: React에서는 DOM 요소에 이벤트 리스너를 직접 추가할 수 없으며, 모든 이벤트를 인라인으로 설정해야 함. 따라서 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('버튼이 클릭되었습니다.');
};

props

컴포넌트 간에 데이터를 전달하는 방법
부모 컴포넌트가 자식 컴포넌트로 데이터를 전달하는 방법
UI 구성 요소 간의 독립성과 재사용성을 높이는 핵심 개념

특징

  • 읽기 전용(Read-only): Props는 자식 컴포넌트 내에서 수정할 수 없는 읽기 전용 데이터로 부모 컴포넌트가 전달한 값이 변경되면 자식 컴포넌트가 다시 렌더링됨
    이 원칙은 컴포넌트의 예측 가능성을 유지하는 데 중요한 역할을 함

  • 단방향 데이터 흐름: Props는 부모에서 자식으로만 전달됨
    자식 컴포넌트는 부모 컴포넌트의 상태나 데이터를 직접 변경할 수 없으며, 변경이 필요할 경우 부모에게 알려야 함

  • 다양한 데이터 타입: Props는 문자열, 숫자, 배열, 객체, 함수 등 다양한 데이터 타입을 전달할 수 있음
    이로 인해 컴포넌트 간의 기능과 인터페이스를 유연하게 정의할 수 있음

  • 기본 값 설정: Props는 기본 값을 설정할 수 있음
    이를 통해 부모 컴포넌트에서 특정 값을 전달하지 않을 경우에도 자식 컴포넌트가 예상 가능한 동작을 수행할 수 있음

➕ 더 알아볼것

'읽기 전용', '단방향 데이터 흐름'이라고 했는데 useState의 setter를 props로 받으면 데이터를 변경할 수 있는 거 아닌가?

  • 부모 컴포넌트의 상태를 변경할 수 있는 함수(예: useState의 setter 함수)를 props로 전달하여, 자식 컴포넌트에서 부모 컴포넌트의 상태를 간접적으로 변경할 수 있음
  • 이 경우, 자식 컴포넌트는 상태 자체를 변경하는 것이 아니라, 상태 변경을 요청하는 함수만 호출하는 것임
  • 또한, 자식 컴포넌트가 props로 받은 함수를 수정하거나 교체하는 것이 아니라, 주어진 함수를 사용하는 것으로 자식 컴포넌트는 부모 컴포넌트의 상태나 그 상태를 변경하는 로직을 직접 제어하지 않음
  • 이는 React의 단방향 데이터 흐름을 유지하면서도 상위 컴포넌트의 상태를 자식 컴포넌트가 조작할 수 있게 해줌

Children

특정 태그의 컨텐츠 자체를 prop으로 넘겨주는 것
JSX 태그 사이에 있는 내용을 자식 컴포넌트로 전달함

  • children을 사용하면 컴포넌트 내부에 어떤 내용을 표시할지 부모 컴포넌트가 결정할 수 있어, 재사용 가능한 컴포넌트를 만들 때 유용함
  • children prop은 모든 React 컴포넌트에 기본적으로 존재하며, 부모 컴포넌트는 자식 컴포넌트를 동적으로 렌더링할 수 있음
const Wrapper = (props: { children: React.ReactNode }) => {
  return <div className="box">{props.children}</div>;
};

export default Wrapper;

조건부 렌더링

특정 조건에 따라 컴포넌트나 요소를 렌더링하는 것

방법 1. if

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;

방법 2. 삼항 연산자

짧은 조건부 렌더링에서 유용

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;

방법 3. 논리 연산자 &&

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;

방법 4. 즉시 실행 함수

더 복잡한 로직이 필요하거나, 변수의 범위를 제한하고 싶을 때 유용

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;

조건부로 Tailwind 적용

방법 1. 기본

  • 이 방법은 스타일 길이가 길 때 불편해질 수 있음
 <h1 className={isLoggedIn ? ' text-5xl text-rose-500' : 'text-3xl underline'}>App Component</h1>

방법 2. tailwind-merge 라이브러리

tailwind-mergetwMerge를 이용하면 중복된 클래스를 자동으로 제거하고, 동일한 속성을 설정하는 클래스 중 마지막으로 적용된 클래스를 우선시함
이는 코드의 중복을 줄이고, CSS 적용 우선순위와 관련된 문제를 방지하는 데 유용함

<h1 className={twMerge('text-3xl underline', isLoggedIn ? 'text-5xl text-rose-500' : '')}>App Component</h1>

공통 컴포넌트 만들기

Input

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;

Button

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;
profile
아이디어와 구현을 좋아합니다!

0개의 댓글