재사용성이 높은 컴포넌트 만들기
props로 tailwind의 className과 HTML 속성들을 전달해 쉽게 스타일 및 속성을 변경 할 수 있음
ComponentPropsWithoutRef → React에서 특정 HTML 요소나 React 컴포넌트에 대한 props 타입을 유추할 때 사용하는 유틸리티 타입
쉽게 말해, 태그 내에 쓰이는 props들의 타입들을 자동으로 추론해서 전달해 줌
전달받은 props들은 구조 분해 할당을 통해 필요한 것들만 사용
//App.tsx
export default function App() {
return (
<Input className="bg-black" type="text" placeholder="입력" />
);
}
//Input.tsx
import { twMerge } from "tailwind-merge";
type InputProps = React.ComponentPropsWithoutRef<"input">;
export const Input = (props: InputProps) => {
//구조 분해 할당으로 className과 기타 props들을 구분
const { className, ...rest } = props;
//className으로 스타일 추가 가능
return (
<input
className={twMerge("w-20 h-5", className)}
// type, value, placeholder 등 다양한 rest로 props들을 전달
{...rest}
></input>
);
};
조건부 렌더링
- if문 사용
export default function App() {
const isTrue = true;
if (isTrue) return <h1>true</h1>;
return <h1>false</h1>;
}
- 삼항연산자 사용
export default function App() {
const isTrue = true;
return isTrue ? <h1>true</h1> : <h1>false</h1>;
}
- 조건이 참일때만 렌더링 (&&)
export default function App() {
const isTrue = true;
return isTrue && <h1>true</h1>;
}
반복 렌더링
map() 메서드를 사용하여 배열의 내용을 반복
→ 고유한 key를 설정해 주어야 함(습관화 하기)
export default function App() {
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return (
<div>
{array.map((num, idx) => (
<h1 key={idx}>{num}</h1>
))}
</div>
);
}
// 1 2 3 4 5 6 7 8 9 10
객체 배열 렌더링 → 배열 안에 객체가 들어 있어야만 반복 가능
export default function App() {
const array = [
{
num: 1,
name: 'james'
},
{
num: 2,
name: 'kirk'
},
{
num: 3,
name: 'robert'
},
{
num: 4,
name: 'lars'
},
]
return (
<div>
{array.map((item) => (
<h1 key={item.num}>{item.name}</h1>
))}
</div>
);
}
컴포넌트 역시 반복 가능
const Print = ({ num }: { num: number }) => {
return <h1>{num}번째 컴포넌트</h1>;
};
export default function App() {
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return (
<div>
{array.map((num, idx) => (
<Print key={idx} num={num}></Print>
))}
</div>
);
}
이미지 불러오기
- public 폴더 안에 넣기
function App() {
return (
<div>
// 현재 주소/이미지 이름으로 접근
<img src="<http://localhost:5173/cat.jpg>"></img>
</div>
);
}
export default App;
public 폴더는 빌드 전/후의 주소가 똑같음
- src 폴더 안에 넣기
// 이미지 파일의 경로에서 직접 import 해야 함
import bird from "./asset/bird.webp";
function App() {
return (
<div>
<img src={bird}></img>
</div>
);
}
export default App;
src 폴더 안에 넣으면 빌드시 url이 달라짐
public 폴더에 넣으면 빌드가 되지 않음 → 빌드가 필요한 이미지는 src에 넣는게 좋음
- 파비콘, 로고 등 → public 폴더
- 컴포넌트 배경, 이미지 등 → src 폴더
gap을 이용한 스타일링
gap → 컴포넌트 내부 chilren 요소들이 일정 간격씩 떨어져 있도록 만드는 방법
<div className="flex flex-col gap-[10px]">
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
</div>
//각 h1 태그가 10px씩 떨어져 있게 됨
React Hook
- useState
state → 상태 ⇒ React 내에서 사용하는 변수
import { useState } from "react";
function App() {
const [num, setNum] = useState(0);
return (
<div>
<h1>{num}</h1>
<button onClick={() => setNum(num + 1)}>증가</button>
</div>
);
}
export default App;
useState는 비동기적이기 때문에 set을 여러개 써도 동작은 한번만 실행된다
import { useState } from "react";
function App() {
const [num, setNum] = useState(0);
const increase = () => {
setNum(num + 1);
setNum(num + 1);
setNum(num + 1);
//한번 실행했을 때, num = 1이 됨
};
return (
<div>
<h1>{num}</h1>
<button onClick={increase}>증가</button>
</div>
);
}
export default App;
set 함수에는 매개변수로 콜백 함수를 넘길 수 있음 → 콜백함수는 동기적으로 실행되기 때문에 늘 최신 state를 이용
- set 함수를 콜백 형식으로 만들어주지 않으면 render 시점의 값을 참조 => 빈 배열
- 콜백 형식으로 만들어주면 이전의 값을 참조하기 때문에, 함수 실행 시점의 state(useState로 설정된 state)를 참조 => 값이 존재함
import { useState } from "react";
function App() {
const [num, setNum] = useState(0);
const increase = () => {
setNum((num) => num + 1);
setNum((num) => num + 1);
setNum((num) => num + 1);
//num이 1 증가 -> 증가한 num을 콜백함수의 매개변수로 전달
//최종적으로 num은 총 3 증가
};
return (
<div>
<h1>{num}</h1>
<button onClick={increase}>증가</button>
</div>
);
}
export default App;
상태값 참조 여부에 따라 매개변수로 상태를 전달할지, 콜백을 전달할지 정함
- 원래 상태값을 참조해야 할 때 → 콜백함수
- 참조하지 않아도 될 때 → 상태
👏 input 태그에서 required를 사용하면 자동으로 valid check를 해 줌
Props Drilling
useState만을 이용해서 다른 컴포넌트에 state를 전달하기 위해서는 자식 컴포넌트로 계속해서 state를 전달해야 하는 불편함이 생김 → Props Drilling이라고 함
- useRef
HTML Dom에 접근하거나, 렌더링 여부와 상관없이 값을 유지하고 싶을 때 사용
→ document selector(getElementById 등)
⇒ DOM 참조시 current 객체로 참조
import { useRef } from "react";
const App = () => {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
// current 객체를 통해 기능 접근 가능
inputEl.current?.focus();
};
return (
<div>
// inputEl이라는 ref로 input 태그를 참조
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
};
export default App;
부모 컴포넌트에서 자식 컴포넌트의 ref에 접근하기 위해서는 forwardRef() 메서드 사용
import { forwardRef } from "react";
export const Forward = forwardRef((ref) => {
return <h1 ref={ref}></h1>;
});
export default Forward;
- useEffect
- 컴포넌스 생성 : 웹 브라우저에 렌더링 되는 순간
- 컴포넌트 삭제 : 컴포넌트 렌더링이 종료되는 순간
- 컴포넌트 수정 : 컴포넌트의 상태나 변수가 변경되었을 때
- 컴포넌트 생성
import { useEffect } from "react"; export default function App() { useEffect(() => { // 컴포넌트 생성(렌더링) 시점에 log 출력 console.log("컴포넌트 생성"); }, []); return <h1 className="text-3xl font-bold underline">Hello world!</h1>; }
- 컴포넌트 수정
import { useEffect, useState } from "react"; export default function App() { const [num, setNum] = useState(0); useEffect(() => { console.log("컴포넌트 생성"); }, []); useEffect(() => { console.log("컴포넌트 수정"); }, [num]); return ( <> <h1 className="text-3xl font-bold underline">{num}</h1> <button onClick={() => setNum(num + 1)}>증가</button> </> ); }
- 컴포넌트 삭제
- 컴포넌트는 useEffect에서 함수를 반환 할 때 unmount 됨
import { useEffect } from "react"; const Interval = () => { useEffect(() => { const interval = setInterval(() => { console.log("Interval Component Updated!"); }, 1000); return () => { clearInterval(interval); }; }, []); return ( <> <h1>Interval Component</h1> </> ); }; export default Interval;
- useLayoutEffect
- useEffect는 컴포넌트가 화면에 그려진 후(paint)에 실행되는 hook
- useLayoutEffect는 컴포넌트가 화면에 그려지기 전에 실행되는 hook
- 내부 로직이 모두 실행된 후 화면에 그리기 때문에, 너무 복잡한 로직은 넣지 않는 것이 좋다
import { useEffect, useLayoutEffect, useState } from "react"; const UseLayoutEffect = () => { const [count, setCount] = useState(0); const now = performance.now(); while (performance.now() - now < 200) { // Artificial delay -- do nothing } useLayoutEffect(() => { if (count === 10) setCount(0); console.log("useLayoutEffect"); }, [count]); return ( <> <h1>Count: {count} </h1> <button onClick={() => setCount(10)}>클릭</button> </> ); }; export default UseLayoutEffect;
- 컴포넌트 생명주기 → 컴포넌트 생성부터 삭제까지의 일련의 과정
- 컴포넌스 생성 : 웹 브라우저에 렌더링 되는 순간
- 컴포넌트 삭제 : 컴포넌트 렌더링이 종료되는 순간
- 컴포넌트 수정 : 컴포넌트의 상태나 변수가 변경되었을 때
- 컴포넌트 생성
import { useEffect } from "react"; export default function App() { useEffect(() => { // 컴포넌트 생성(렌더링) 시점에 log 출력 console.log("컴포넌트 생성"); }, []); return <h1 className="text-3xl font-bold underline">Hello world!</h1>; }
- 컴포넌트 수정
import { useEffect, useState } from "react"; export default function App() { const [num, setNum] = useState(0); useEffect(() => { console.log("컴포넌트 생성"); }, []); useEffect(() => { console.log("컴포넌트 수정"); }, [num]); return ( <> <h1 className="text-3xl font-bold underline">{num}</h1> <button onClick={() => setNum(num + 1)}>증가</button> </> ); }
- 컴포넌트 삭제
- 컴포넌트는 useEffect에서 함수를 반환 할 때 unmount 됨
import { useEffect } from "react"; const Interval = () => { useEffect(() => { const interval = setInterval(() => { console.log("Interval Component Updated!"); }, 1000); return () => { clearInterval(interval); }; }, []); return ( <> <h1>Interval Component</h1> </> ); }; export default Interval;
- useLayoutEffect
- useEffect는 컴포넌트가 화면에 그려진 후(paint)에 실행되는 hook
- useLayoutEffect는 컴포넌트가 화면에 그려지기 전에 실행되는 hook
- 내부 로직이 모두 실행된 후 화면에 그리기 때문에, 너무 복잡한 로직은 넣지 않는 것이 좋다
import { useEffect, useLayoutEffect, useState } from "react"; const UseLayoutEffect = () => { const [count, setCount] = useState(0); const now = performance.now(); while (performance.now() - now < 200) { // Artificial delay -- do nothing } useLayoutEffect(() => { if (count === 10) setCount(0); console.log("useLayoutEffect"); }, [count]); return ( <> <h1>Count: {count} </h1> <button onClick={() => setCount(10)}>클릭</button> </> ); }; export default UseLayoutEffect;
본 게시글의 예제 코드는 수코딩(https://www.sucoding.kr)을 참고했습니다
'Frontend Study' 카테고리의 다른 글
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 6일차 (1) | 2024.10.08 |
---|---|
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 5일차 (1) | 2024.09.27 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 3일차 (0) | 2024.09.25 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 2일차 (0) | 2024.09.24 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 1일차 (0) | 2024.09.23 |