본문 바로가기

Frontend Study

[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 3일차

컴포넌트 CSS 스타일링

  1. 인라인 스타일
    • HTML의 style 속성을 이용해 스타일을 지정하는 방법
    • style 속성은 객체 형식으로 전달되어야 함
    • camel-case로 작성
<h1
  style={{
    width: "100%",
    backgroundColor: "red",
  }}
>
  Hello World
</h1>;
  1. 외부 css 파일 사용
    • css 파일을 import하여 className으로 스타일 지정
    • 외부 css 파일을 import한 컴포넌트가 렌더링 되어있기만 하면, 모든 컴포넌트는 해당 컴포넌트에서 import한 css의 영향을 받음 → global하게 적용
      • ex) App.tsx 아래에 Second.tsx와 Third.tsx라는 컴포넌트가 존재
      • Second.tsx에 style.css가 import 되어있어도, Second.tsx가 렌더링 되어있다면 App.tsx에서도 스타일 사용 가능
      • 그렇기 때문에, 최상위 컴포넌트 (Main.tsx)에 import 하는 것이 best
/* style.css */
.title {
  width: 100%;
  background-color: red;
}
//App.tsx
import styles from "./style.css";

<h1 className="title">
	Hello World
</h1>
  1. 모듈 CSS
    • 모듈 방식을 사용해서 특정 컴포넌트에만 종속되는 스타일 작성
    • 외부 스타일 방법과 비슷하지만, 파일명이 *.module.css
    • import 한 컴포넌트 이외의 컴포넌트에서는 스타일 사용 불가
/* style.module.css */
.title {
  width: 100%;
  background-color: red;
}
//App.tsx
import styles from "./style.module.css";

<h1 className={styles.title}>Hello World!</h1>
	Hello World
</h1>
  1. Tailwind CSS
    • React와 Typescript 사용시 가장 핫한 라이브러리
    • 가독성이 매우 떨어지고 코드가 복잡해지는 단점
    • NEXT.JS에서 Tailwind를 권장

Tailwind css

tailwind-merge → 중복 스타일 제거 및 클래스 코드 병합

import { twMerge } from "tailwind-merge";

export default function App() {
  return (
    <h1 className={twMerge("text-rose-500 font-bold", "text-black")}>
      Hello world!
    </h1>
  );
}

//className에 바로 지정하면 class="text-rose-500 font-bold", "text-black"
//twMerge를 사용하면 class="font-bold", "text-black"로 출력
//중복 스타일은 뒤쪽의 스타일을 적용

twMerge를 사용하면 조건부 스타일링도 가능

import { twMerge } from "tailwind-merge";

export default function App() {
	let isActive = true;
  return (
    <button
      className={twMerge(
        "text-rose-500",
        isActive && "text-white font-bold"
      )}
    >
      Hello world!
    </button>
  );
}

tailwind의 css 속성이 아닌 클래스 역시 twMerge 내에 넣으면 병합 가능

import { twMerge } from "tailwind-merge";

export default function App() {  return (
    <button
      className={twMerge(
	      "isActive", 
        "text-rose-500 text-white font-bold"
      )}
    >
      Hello world!
    </button>
  );
}

클래스명의 일부를 바꾸는 것은 허용되지 않음

import { twMerge } from "tailwind-merge";

export default function App() {
	let color = "rose";
  return (
    <button
      className={twMerge(
	      // 문자열 일부를 변수로 치환하는 것은 허용하지 않음
        "text-${color}-500 font-bold"
      )}
    >
      Hello world!
    </button>
  );
}

export default function App() {
	let isActive = true;
  return (
    <button
      className={twMerge(
	      // 문자열 전체를 치환하는 것은 가능
        `${isActive ? "text-rose-500" : "text-blue-500"}`,
        "font-bold"
      )}
    >
      Hello world!
    </button>
  );
}

CSS 우선순위

important > 인라인 스타일 > id > 클래스 > 태그

구글 폰트 적용 방법

  1. 구글 폰트에서 폰트 검색 후 Get Font
  2. import문을 css 파일에 추가 or link 태그를 index.html에 추가
  3. css 파일에 클래스 선택자 생성
  4. 클래스명을 이용해 폰트 적용

다운로드 폰트 적용 방법

@font-face {
  font-family: "fontName"; /* 폰트 이름 정의 */
  src: url("./fonts/EF_jejudoldam\\(OTF\\).woff2"),
    url("./fonts/EF_jejudoldam_OTF_.woff"); /* 폰트파일 위치 및 포맷 지정 */
}

컴포넌트 렌더링

React는 DFS를 바탕으로 컴포넌트를 탐색

→ 최상위 컴포넌트로부터 렌더링 시작 → 가장 먼저 렌더링되는 컴포넌트의 자식 컴포넌트들을 모두 렌더링→ 그 다음 컴포넌트를 렌더링

export default function App() {
  return;
  <>
    <A>
      <E />
      <F />
    </A>
    <B>
      <G />
    </B>
    <C>
      <H />
    </C>
    <D></D>
  </>;
}

위 구조에서, 렌더링 순서는

A → E → F → B → G → C → H → D

컴포넌트 이벤트

컴포넌트에 이벤트 props를 전달하여 이벤트 추가 가능

//인라인 이벤트 -> 컴포넌트에 직접 이벤트 코드 작성
function App() {
  return (
    <button
      onClick={() => {
        alert("클릭");
      }}
    >
      클릭
    </button>
  );
}
export default App;

//이벤트 함수 할당 -> 이벤트 함수 작성 후 컴포넌트에 할당
function App() {
  const alerClick = () => {
    alert("클릭");
  };
  return <button onClick={alerClick}>클릭</button>;
}
export default App;

이벤트 타입

React 이벤트도 타입을 명시해 주어야 타입스크립트 오류가 나지 않음 → 이벤트에 마우스를 올리면 툴팁으로 표시

React.적용하려는 이벤트<적용하려는 태그>

ex) 클릭 이벤트 ⇒ React.MouseEvent<HTMLDivElement>

function App() {
  const submit = (e: React.FormEvent<HTMLFormElement>) => {
	  alert("제출!");
  };
  return (
    <>
      <form onSubmit={submit}>
        <input type="text" name="email"></input>
        <input type="password" name="password"></input>
        <button>로그인</button>
      </form>
    </>
  );
}
export default App;

👏 form 태그에는 submit을 넣어주면 매우 편리

이벤트 전파

이벤트는 부모 컴포넌트에서 자식 컴포넌트로 전달 가능 → 반대는 불가

컴포넌트 props

컴포넌트에서 컴포넌트로 전달하는 값 → 객체 형태로 전달

//App.tsx
import Count from "./components/count";

function App() {
  return (
    <div>
      <h1>title</h1>
      <Count count={10} operator="+"></Count>
    </div>
  );
}
export default App;

//Count.tsx

//props는 객체 형태로 전달되므로, 객체의 타입을 지정해줘야 함
interface Props {
  count: number;
  operator: string;
}

function Count(props: Props) {
  return (
    <div>
      {props.count} {props.operator}
    </div>
  );
}
export default Count;

//비구조화 할당 가능
interface Props {
  count: number;
  operator: string;
}

function Count({ count, operator }: Props) {
  return (
    <div>
      {count} {operator}
    </div>
  );
}
export default Count;

props의 타입

props는 객체이기 때문에, interface 등으로 객체의 타입을 지정해 주는 것이 중요

→ 파일명.d.ts 파일에 interface를 저장하면 import 없이 인터페이스 사용 가능

⇒ 타입스크립트에서 자체적으로 d.ts 파일을 인식하여 전역으로 타입 관리

//count.d.ts

//Props를 export 하지 않아도 사용 가능
interface Props {
  count: number;
  operator: string;
}

//Subcount.tsx

//Props를 import 하지 않아도 사용 가능
function SubCount({ count, operator }: Props) {
  return (
    <div>
      {count} {operator}
    </div>
  );
}
export default SubCount;

함수 props 전달

함수 역시 타입 추론을 통해 interface에 타입을 지정하고, prop로 전달 가능

//App.tsx
import Count from "./components/Count";

function App() {
  const calculate = (count: number, operator: string): number => {
    switch (operator) {
      case "+":
        return count + count;
      case "-":
        return count - count;
      case "*":
        return count * count;
      case "/":
        return count / count;
    }
    return 0;
  };
  return (
    <div>
      <h1>title</h1>
      <Count count={10} operator="+" calculate={calculate}></Count>
    </div>
  );
}
export default App;

//Count.tsx
function Count({ count, operator, calculate }: Props) {
  return <div>{calculate(count, operator)}</div>;
}
export default Count;

//count.d.ts
interface Props {
  count: number;
  operator: string;
  calculate: (count: number, operator: string) => number;
}

매개변수의 유무에 따라 함수 전달 방식이 바뀜

//매개변수가 있을 때
export default function App() {
  const printText = (str: string) => {
    alert(str);
  };
  return (
    <>
      <h1>App Coponent</h1>
      <button onClick={() => printText('클릭')}>클릭</button>
    </>
  );
}

//매개변수가 없을 때
export default function App() {
  const printText = () => {
    alert("클릭");
  };
  return (
    <>
      <h1>App Coponent</h1>
      <button onClick={printText}>클릭</button>
    </>
  );
}

children

태그 내에 존재하는 요소 = Content

컴포넌트의 Content를 props로 전달하기 위해서는 ‘children’으로 전달

children의 타입은 React.ReactNode로 고정

children은 props와 다르게 HTML 태그까지 전달 가능 + 컴포넌트도 children으로 전달 가능

//App.tsx
import Button from "./components/html/Button";

export default function App() {
  const printText = (str: string) => {
    alert(str);
  };
  return (
    <>
      <div className="item-middle">
	      //"클릭" 텍스트만 전달
        <Button children="클릭"></Button>
        //em(기울임) 태그가 적용된 텍스트 전달
        <Button children={<em>엔터</em>}></Button>
      </div>
    </>
  );
}

//Button.tsx
function Button({ children }: { children: React.ReactNode }) {
  return (
    <>
      <button className="py-2 px-4 rounded-lg text-sm border border-gray-500">
        {children}
      </button>
    </>
  );
}
export default Button;


본 게시글의 예제 코드는 수코딩(https://www.sucoding.kr)을 참고했습니다