일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 유데미
- 리액트프로젝트
- sucoding
- Server State
- React
- 프로젝트캠프
- 상태 관리 라이브러리
- 프론트엔드 개발
- 수코딩
- 공식문서
- 프론트엔드
- 스나이퍼팩토리
- STATE
- React Query
- 개발
- frontend
- 웅진씽크빅
- TypeScript
- tanstack query
- Today
- Total
yunicornlab
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 6일차 본문
예제 코드 중에, 배열 타입인 상태 변수에서 하나의 요소(처음 또는 마지막 요소)를 제외한 나머지 요소들로만 업데이트하고 싶을 때
shift나 pop을 사용하거나 slice를 사용했었는데, 구조분해할당으로 더 간단한 방법도 알게되었다.
const [del, ...remain] = imgSet;
이렇게 두고, setImgSet(remain) 이렇게 나머지만 넣어주면 된다.
React Hook - useEffect
useEffect의 첫 번째 인자에는 콜백함수가 들어가고, 두 번째 인자에는 의존성 배열이 들어간다.
콜백함수에는 먼저 컴포넌트가 생성될 때(또는 의존성 배열 인자가 변경될때마다) 실행할 로직을 작성한다.
그리고 return 문에는 컴포넌트가 삭제될 때 실행할 로직을 작성해준다.
이 return문에 작성한 함수를 clean up 함수라고 부른다.
그리고 의존성 배열에 들어가있는 상태 변수값이 변경되면, useEffect가 메모리에서 먼저 삭제되고 다시 실행되기 때문에 return문의 clean up 함수가 먼저 실행 되고 다시 useEffect가 실행된다.
useEffect는 하나의 컴포넌트에서 목적과 상황에 따라 여러 번 사용할 수 있다. 단, 의존성 배열이 다를 경우에만 여러 번 사용해주어야 한다.
useEffect를 분리해서 작성하면 각 의존성 인자마다 생성 시점과 종료 시점의 코드를 분리해서 사용할 수 있다.
clean up 함수는 왜 필요할까?
하나의 예시로, useEffect 안에 setInterval 함수를 작성하고 clean up 함수를 지정하지 않으면, 컴포넌트가 화면에서 사라져도 setInterval 함수는 계속 실행되고 있다. 게다가 사용자가 페이지를 왔다갔다 이동하는 등의 작업을 통해 해당 컴포넌트의 생성과 삭제가 반복적으로 실행되면 setInterval 함수는 더 많이 더 빨리 실행되게 되면서 메모리 누수 문제를 발생시킨다.
그렇기 때문에 (setInterval은 clearInterval로) clean up 함수에 적절히 종료하는 코드를 작성해줘야 한다.
import { useEffect } from "react";
const EffectTest = () => {
useEffect(() => {
const timer = setInterval(() => {
console.log("useEffect");
}, 1000);
return () => {
clearInterval(timer);
};
});
return (
<>
<h1>EffectTest</h1>
</>
);
};
export default EffectTest;
API를 호출하는 코드 또한 clean up 함수를 지정해주지 않으면 다른 페이지로 이탈하게 되더라도 API 응답이 될 때까지 계속 작업이 진행되고, 다시 해당 페이지로 돌아오면 API 호출 코드가 또 실행되면서 이전 작업이 없어지는 것이 아니라 오히려 누적되면서 메모리에 쌓여가게 된다.
Tailwind CSS로 다크모드 기능 구현하기
tailwind.config.js 파일에 아래와 같이 darkMode: "class" 코드를 추가해준다.
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
그리고 index.html에서 id가 rootdls div 태그의 class에 다크모드와 관련된 색상이나 스타일 클래스를 추가해준다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div
id="root"
class="dark:bg-black dark:text-white transition duration-500 min-h-screen"
></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
이후, 컴포넌트에서 아래 코드를 추가해서 사용하면 된다.
// 다크모드
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
if (darkMode) {
document.body.className = "dark";
} else {
document.body.className = "";
}
}, [darkMode]);
참고로, 리액트에서는 useState, useRef, useEffect와 같은 훅은 반드시 컴포넌트 함수 내의 root 영역(최상단/전역)에 작성해주어야 한다. 함수 내부에서 선언할 수 없다.
사용자 시스템 환경 정보 이용하기
사용자 컴퓨터에서 다크모드가 적용되어있는지에 대한 정보를 가져와서 사용하는 꿀팁도 알려주셨다.
useEffect(() => {
const userPrefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
setDarkMode(userPrefersDark);
}, []);
PreLoad 기법
클릭한 버튼에 따라 이미지가 바뀌는 예제를 진행했었는데, 이때 아주 잠깐이지만 순간적으로 깜빡임 현상이 일어났다.
이미지를 네트워크 통신으로 불러오는 그 시간에 의한 현상이었다.
이런 초기 이미지 렌더링 속도를 높이고 싶을 때 사용할 수 있는 기술로, preload 기법이 있다.
간단히 말해, 이름 그대로 먼저 불러와놓고 사용하는 기법이다.
import travel from "../assets/images/travel.png";
import Seoul from "../assets/images/seoul.jpg";
import London from "../assets/images/london.jpg";
import Paris from "../assets/images/paris.jpg";
import NewYork from "../assets/images/newyork.jpg";
import { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
export default function Travel() {
const areas = ["Seoul", "London", "Paris", "NewYork"];
const [location, setLocation] = useState("Seoul");
// 객체 생략 표현
const images: { [key: string]: string } = { Seoul, London, Paris, NewYork };
// 이미지 PreLoad 기법
function userImagePreloader(images: string[]) {
useEffect(() => {
images.forEach((image) => {
const img = new Image();
img.src = image;
});
}, [images]);
}
userImagePreloader([Seoul, London, Paris, NewYork]);
return (
<>
<ul className="flex items-center antialiased justify-around w-full">
{areas.map((area, index) => (
<li key={index} className={`${location === area && "font-bold"}`}>
<a href="#" onClick={() => setLocation(area)}>
{area}
</a>
</li>
))}
</ul>
<div>
{areas.map((area) => {
return location === area && <img src={images[area]} alt={area} />;
})}
</div>
</>
);
}
preload 기법을 사용하면 깜빡임 현상 없이 자연스럽게 전환이 가능하다. 하지만 순간 트래픽이 중요한 부분에서는 preload 기법으로 너무 많은 리소스를 한번에 불러오게되면 문제가 생길 수 있기 때문에 상황에 따라 적절하게, 가능한 중요한 리소스에만 사용하는 것이 좋다.
아니면 transition으로 자연스럽게 보일 수 있도록 하는 방법도 있다.
React Hook - useLayoutEffect
useEffect는 컴포넌트가 생성된 후 비동기적으로 실행되기 때문에 DOM을 조작하는 경우에는 순간적으로 깜빡임이 발생할 수 있다.
useEffect와 달리 useLayoutEffect()는 화면에 컴포넌트를 그리기 바로 직전에 동기적으로 실행되어서, DOM을 조작하는 경우에 깜빡임 현상을 일으키지 않는다.
하지만 동기적 작업이기 때문에 부하가 많이 걸리는 작업을 넣게 되면, useLayoutEffect의 코드가 실행되는 동안 다른 기능들을 사용할 수 없다.
그래서 데이터 통신 관련 코드는 useEffect를 사용한다. useLayoutEffect는 레이아웃이 변경(위치 변경 등)과 같은 작업에서 사용하면 좋다.
재사용할 type 관리
src > types > 파일이름.d.ts 파일을 생성해서 아래의 예시 코드처럼 작성해준 다음에
사용할 컴포넌트에서 import 해서 사용해주면 된다!! 아주 간편하당
export type TInventoryItem = {
name: string;
description: string;
quantity: string;
price: string;
};
렌더링 최적화 기법 - Debounce
예를 들어, 검색 기능에서 검색하는 글자 하나하나마다 재렌더링되는 것은 너무 비효율적이기 때문에, 이러한 문제를 해결할 수 있는 방법 중 하나이다.
Debounce 기법은 사용자가 특정 시간 동안 입력하지 않을때까지 기다렸다가, 기준치의 시간을 넘기면 그때야 입력이 끝났다고 판단하고 렌더링하는 것이다. 입력하는 중에는 이벤트 핸들러가 발생하지 않고, 입력의 흐름이 잠깐 멈췄을 때 호출하는 방법으로 렌더링을 최적화하는 방법이다.
Todo List 예제
Todo.tsx 컴포넌트 안에 TodoEditor.tsx와 TodoList.tsx를 불러오고, TodoList.tsx 안에 TodoListItem.tsx 컴포넌트를 불러오는 방식으로 예제를 진행했다.
사실 투두리스트는 다른 강의에서도 항상 거의 기본으로 들어가있는 예제인데, 오늘 해보니까 왜 거의 필수로 거치고 가는 지 알겠다.
그만큼 가장 기본적이면서 중요한 부분이다.
컴포넌트를 적절히 분리하고, 상태 관리를 고민하는 가장 시작이 되는 예제인 것 같다.
디자인을 조금 다르게 해서 직접 다시 작성해보아야겠다.
테일윈드도 이번 교육을 통해 처음 접해본 건데, 어느 정도 적응이 된 것 같고, 사용할수록 간편하고 사용하기 편하다는 느낌이 든다.
Styled Component를 사용했을 때는 한 두개의 간단한 css를 적용할때도 일일이 스타일 컴포넌트로 선언하고 작성해주어야 했는데, 테일윈드는 className에만 정해진 키워드로 입력만 해주면 손쉽게 적용이 된다.
React v18 + Hook - useTransition
시스템의 부하를 완화시킬 때 사용할 수 있는 좋은 훅이다. (Next.js에서는 특히 많이 사용되는 훅이라고 한다.)
이 훅은 startTransition 내부의 코드의 우선순위를 낮추어서, 빨리 실행될 수 있는 것부터 실행시키는 방식으로 부하를 해소한다.
import { useState, useTransition } from "react";
const UseTransition = () => {
const [input, setInput] = useState("");
const [dummy, setDummy] = useState<number[]>([]);
const [pending, startTransition] = useTransition();
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
// startTransition 내부는 시스템 부하를 낮춰주는 영역임
startTransition(() => {
// 의도적으로 시스템 부하를 주기 위한 코드
const i: number[] = [];
for (let j = 0; j < 20000; j++) {
i.push(j);
}
setDummy((d) => [...d, ...i]);
// 이 내부 코드가 실행 후 종료되면 pending이 false가 된다. (기본값이 true)
});
};
return (
<>
<h1>UseTranstion</h1>
<input type="text" value={input} onChange={onChange} />
{!pending ? (
<ul>
{dummy.map((d) => (
<li key={d}>{d}</li>
))}
</ul>
) : (
<h1>"Loading"</h1>
)}
</>
);
};
export default UseTransition;
기타
- onSubmit으로 form을 이용하면, 제출 버튼을 누르지 않고 엔터만 입력해도 제출이 된다.
- setState 함수에서 이전에는 "prev"를 사용하면서 "이전 상태값"을 참조한다고 이해했는데, 이제는 이전 값이라고 하는 것보다, 그리고 prev를 사용하기보다, "최신 상태값"이라고 생각하고 변수도 prev 대신에 어떤 변수인지 알수 있는 변수명을 사용하는 게 더 좋은 것 같다.
- strict mode는 꼭 사용하기를 권장한다. 개발할 때는 console이 두 번씩 출력되는데, 개발 모드에서만 그런 것이고 실제로는 한 번만 출력된다. strict mode에서는 컴포넌트를 생성한 후에 이 컴포넌트에 문제가 없는지 확인 후에 지우고 다시 보여준다.
'Developer' 카테고리의 다른 글
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 5일차 (0) | 2024.08.25 |
---|---|
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 4일차 (0) | 2024.08.22 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 3일차 (0) | 2024.08.21 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 2일차 (0) | 2024.08.21 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 1일차 (4) | 2024.08.19 |