React
1. 프로젝트 생성
npm create vite@latest [폴더이름아무거나] -- --template react-ts2. 사용 기술
- React v18
- Vite v5
3. Webpack (module bundler) VS. Vite (module bundler) VS. gulp / grunt (task runner)
등장 순서
- grunt (2012)
- webpack (2012)
- gulp (2013)
- vite (2020)
Vite와 Webpack은 모두 현대 웹 개발에서 널리 사용되는 빌드 도구이지만, 그들이 자원을 처리하고 변환하는 방식에 있어 근본적인 차이가 있습니다.
이 차이점은 개발 환경에서 Vite가 Webpack보다 훨씬 빠르게 작동하는 이유를 설명합니다.
Webpack
- 전통적인 번들러로, 개발 중에도 모든 모듈을 하나의 파일(또는 몇 개의 청크로 분할된 파일)로 묶습니다. (메모리상에서)
이 과정에서 모든 소스 코드를 파싱하고 의존성을 해석한 후, 결과적으로 하나의 큰 번들을 생성합니다.
파일 하나에 모든 것이 포함되어 있으므로 초기 시작 시간이 길어질 수 있습니다.
- 전통적인 번들러로, 개발 중에도 모든 모듈을 하나의 파일(또는 몇 개의 청크로 분할된 파일)로 묶습니다. (메모리상에서)
Vite
- vite는 개발 모드에서 ES 모듈을 기반으로 작동합니다.
웹 브라우저가 이미 ES 모듈을 지원하기 때문에, vite는 서버 측에서 필요한 모듈만을 실시간으로 변환하고 제공합니다.
이는 개별 파일을 별도로 요청하고 캐싱할 수 있게 하여, 변경이 필요한 부분만 빠르게 재로드 할 수 있습니다.
- vite는 개발 모드에서 ES 모듈을 기반으로 작동합니다.
코드 변환 및 캐싱:
- Webpack
- 변환 과정(트랜스파일링 및 번들링)은 모든 파일에 대해 일괄적으로 수행되므로, 작은 변경 사항이라도 전체 애플리케이션을 다시 번들링하는데 시간이 걸립니다.
- Vite
- 개발 중에는 모든 파일을 빠르게 변환하고 결과를 캐시합니다.
- Vite는 HMR(Hot Module Replacement)을 사용하여 변경된 모듈만을 빠르게 교체할 수 있습니다.
- 이로 인해 변경 사항을 브라우저에 반영하는 시간이 대폭 감소합니다.
- Webpack
Webpack의 HMR
- 구현 방식:
- Webpack의 HMR은 전체 번들을 메모리에 유지하면서 작동합니다.
- 개발자가 파일을 변경하면, Webpack은 의존성 트리 전체를 재계산하여 변경된 모듈만을 식별하고, 해당 모듈을 포함한 번들의 일부를 다시 컴파일합니다.
- 링크
- 효율성:
- 이 과정은 상대적으로 무겁고 시간이 걸릴 수 있습니다.
- 특히 프로젝트가 크고 복잡할수록 더 그렇습니다.
- 모든 의존성을 해석하고 관리해야 하기 때문에, 초기 구동 시간 또한 길어질 수 있습니다.
- 설정 복잡성:
- HMR을 효과적으로 사용하기 위해서는 종종 추가적인 플러그인 설정이 필요하며, 특정 상황에서는 모듈의 상태를 관리하는 추가적인 코드 작성이 요구될 수도 있습니다.
- 구현 방식:
Vite의 HMR
- 구현 방식:
- Vite는 개발 모드에서 ES 모듈을 기반으로 작동합니다.
- 이는 모듈의 변화가 감지되면 그 모듈만을 직접적으로 다시 요청하고 교체하는 것을 가능하게 합니다.
- 서버는 변경된 모듈만을 신속하게 변환하고 클라이언트에 전송합니다.
- 효율성:
- Vite의 HMR은 변경된 모듈을 즉시 다시 로드하고 처리하는 방식 때문에 매우 빠릅니다.
- 필요한 부분만 처리하므로 전체 번들을 다시 계산하거나 메모리에 유지할 필요가 없습니다.
- 이로 인해 더 빠른 업데이트와 낮은 메모리 사용량을 실현합니다.
- 간소화된 설정:
- Vite는 최신 프레임워크와 라이브러리를 자동으로 지원하기 때문에 복잡한 설정 없이도 HMR을 쉽게 사용할 수 있습니다.
- 또한, Vite는 개발에 최적화된 구성을 제공하기 때문에 추가적인 설정 작업이 크게 줄어듭니다.
- 구현 방식:
초기 로딩 시간:
- Webpack:
- 모든 코드와 의존성을 분석하고 번들링하는 데 시간이 걸리므로, 개발 서버를 시작할 때 초기 로딩 시간이 길어질 수 있습니다.
- Vite:
- 필요한 파일만 로딩하고 변환하기 때문에, 서버 시작이 매우 빠르고, 초기 로딩 시간이 짧습니다.
- Webpack:
개발 편의성 및 효율성:
- Webpack:
- 플러그인과 로더를 사용하여 다양한 파일 타입과 복잡한 프로젝트 구조를 지원합니다.
- 이는 유연성을 제공하지만 설정이 복잡해질 수 있습니다.
- Vite:
- Vite는 Vue, React 같은 최신 프론트엔드 프레임워크와 자연스럽게 통합되며, 설정이 상대적으로 간단합니다.
- 또한, 개발 중 빠른 피드백을 제공하여 개발자의 생산성을 향상시킵니다.
- Webpack:
이러한 차이점 때문에 Vite는 특히 대규모 프로젝트나 자주 변경되는 프로젝트에서 개발 단계에서의 효율성과 속도 면에서 Webpack보다 유리한 면이 많습니다.
Webpack도 프로덕션 빌드에서 강력한 최적화와 성능을 제공하지만, 개발 단계에서는 Vite가 제공하는 빠른 피드백과 재로딩 시간이 큰 장점으로 작용합니다.
grunt
- Grunt는 "task runner"로 알려져 있으며, 2012년에 등장했습니다.
- Grunt는 설정 기반 접근 방식을 사용하며, Gruntfile이라는 이름의 파일에서 작업을 구성합니다.
- 이 파일 내에서, 개발자는 수행하고자 하는 작업(예: 파일 합치기, 압축, 미니파이, 테스트 실행 등)을 정의하고, 이러한 작업을 실행할 때 필요한 설정을 지정합니다.
- Grunt는 다양한 플러그인을 통해 확장 가능하며, 이를 통해 거의 모든 종류의 자동화 작업을 처리할 수 있습니다.
Gulp
- Gulp는 2013년에 등장한 또 다른 자동화 도구로, Grunt보다 좀 더 직관적인 코드 기반 접근을 제공합니다.
- Gulp는 스트림을 사용하여 파일을 읽고 쓰므로, 메모리 사용량이 적고 처리 속도가 빠릅니다.
- Gulp 역시 gulpfile이라는 설정 파일에서 작업을 정의하지만, 작업들이 코드 형태로 기술되어 있어 Grunt보다 더 유연하고 강력한 방식으로 복잡한 작업을 표현할 수 있습니다.
- Gulp 역시 플러그인을 통해 기능을 확장할 수 있으며, 파일 시스템을 효율적으로 다룰 수 있는 파이프라인 구성이 가능합니다.
Grunt는 설정 중심의 접근 방식을 선호하고, 좀 더 자세한 구성 옵션이 필요한 개발자에게 적합할 수 있습니다.
Gulp는 코드를 통한 접근 방식을 선호하고, 작업의 실행과 조합을 더 유연하게 다루고 싶은 개발자에게 좋습니다.
Gulp, Grunt 보다 Webpack 또는 Vite 로 dev server 를 사용하는 것이 훨씬 더 빠릅니다. 그 이유는 다음과 같습니다.
- 핫 모듈 교체 (Hot Module Replacement, HMR)
- Webpack Dev Server는 핫 모듈 교체(HMR) 기능을 지원합니다.
- 이 기능은 개발 중에 애플리케이션의 일부를 변경할 때 전체 페이지를 새로고침하지 않고도 해당 변경 사항을 적용할 수 있게 해줍니다.
- 예를 들어, CSS 파일이나 React 컴포넌트를 수정했을 때, 변경된 부분만 실시간으로 업데이트되어 빠르게 결과를 볼 수 있습니다.
- 이는 전체 빌드 프로세스를 거치지 않아도 되기 때문에 응답 시간이 대폭 단축됩니다.
- 효율적인 자산 관리
- Webpack은 자산을 모듈로 관리합니다.
- Webpack Dev Server는 이 모듈들의 의존성 그래프를 사용하여 변경이 필요한 부분만을 식별하고, 그 부분만을 다시 컴파일합니다.
- 이는 불필요한 자원의 컴파일을 방지하여 성능을 크게 향상시킵니다.
- 메모리 내 컴파일 (In-memory compilation)
- Webpack Dev Server는 파일 시스템에 쓰기 작업을 수행하지 않고 메모리에서 직접 모든 파일을 처리합니다.
- 이로 인해 디스크 I/O 부하가 줄어들고, 파일 처리 속도가 향상됩니다.
- 반면, Gulp나 Grunt에서는 디스크에 파일을 쓰고 읽는 작업이 필요할 수 있어, 같은 작업을 처리하는 데 더 많은 시간이 소요될 수 있습니다.
- 코드 분할 (Code Splitting)
- Webpack은 코드 분할을 지원하여, 필요한 코드만 로드할 수 있도록 합니다.
- 개발 중에 특정 모듈만 변경되었다면, 해당 모듈만 다시 불러와서 처리할 수 있습니다.
- 이 기능은 애플리케이션의 로딩 시간을 최적화하고, 개발 중에도 빠른 피드백을 제공합니다.
- 핫 모듈 교체 (Hot Module Replacement, HMR)
4. JavaScript App VS. React App
JavaScript App
- 자바스크립트 앱: 명령형 코드로 작성되어 있어, 코드가 복잡해지면 유지보수가 어려워집니다.
React App (with vite)
React앱: 선언형 코드로 작성되어 있어, 코드가 복잡해져도 유지보수가 쉽습니다.
5. JSX
JSX는 JavaScript XML의 줄임말로, JavaScript의 확장 문법으로 XML과 유사한 구문을 사용하여 JavaScript 코드를 작성할 수 있게 해줍니다.JSX는 React에서 사용되며, React 엘리먼트를 생성하는 간단한 방법을 제공합니다.
Header 컴포넌트 생성
6. 동적 값 출력
7. 이미지 로딩 방법
8. props 사용 방법
9. props 여러가지 문법
스프레드 연산자로 props 전달하기
단일 props 객체 전달하기
받은 props 를 단일 객체로 그룹화하기
10. children prop / props 으로 event 처리하기 / state, hook 사용하기 (useState)
11. Forwarded Props / Proxy Props
12. 조건적 콘텐츠 렌더링
13. 리스트 출력
14. JSX를 꼭 사용하지 않아도 되는 이유
15. Fragments(프래그먼트)
import {Fragment} from "react";
function App() {
return (
<Fragment>
<h1>React App</h1>
<p>React App Content</p>
</Fragment>
);
}function App() {
return (
<>
<h1>React App</h1>
<p>React App Content</p>
</>
);
}JSX 코드를 작성할 때는 규칙이 하나 있습니다.JSX 값은 하나의 루트(root) 요소만 가질 수 있습니다.
예를 들어, 다음 코드는 유효하지 않으며 오류를 발생시킵니다.
function App() {
return (
<h1>React App</h1>
<p>React App Content</p>
);
}다음 코드도 마찬가지입니다.
const content = (
<h1>React App</h1>
<h2>React App Content</h2>
)위 코드 모두 JSX 값이 두 개의 형제 루트 요소를 가지고 있기 때문에 허용되지 않습니다.
해결 방안 중 하나는 이러한 요소들을 <div>로 감싸는 것입니다.
이렇게하면 단일 루트 JSX 요소로 작동합니다.
function App() {
return (
<div>
<h1>React App</h1>
<p>React App Content</p>
</div>
);
}이 방법으로는 잘 작동하며, 그러므로 쓸만한 해결 방안입니다.
그러나 이 방법에는 단점이 있습니다: 이 오류를 제거하기 위해 실제로 필요하지 않은 별도의 <div>가 DOM에 추가됩니다.
그렇기 때문에 리액트는 더 나은 해결 방안을 제공합니다: "리액트 프래그먼트"라는 특별한 JSX 요소입니다.
이것은 단일 루트 JSX 요소만 있도록 보장하는 래퍼(wrapper)로 사용될 수 있으면서 동시에 어떠한 DOM 요소도 렌더링하지 않습니다.
16. 컴포넌트를 분리해야되는 시점
17. feature 및 state 로 컴포넌트 분리하기
18. JSX children 활용법
19. wrapper 요소 동적으로 적용하기 (div, ul, menu 등등...) / Props 에 기본값 설정하기
20. 모든 콘텐츠가 리액트 앱에 보관될 필요가 없는 이유
Tic-Tac-Toe 게임
21. 이미지 저장 public VS src/assets
public폴더- 이미지를
public폴더에 저장하고index.html또는index.css파일 내에서 직접 참조할 수 있습니다.
이렇게 하는 이유는public에 저장된 이미지(또는 일반적으로 파일)가 프로젝트 개발 서버 및 빌드 프로세스에 의해 공개적으로 제공되기 때문입니다.index.html과 마찬가지로, 이 파일들은 브라우저 내에서 직접 방문할 수 있으며, 따라서 다른 파일에 의해 요청될 수도 있습니다.
예를 들어,localhost:5173/some-image.jpg를 불러오면 해당 이미지를 볼 수 있습니다.
(물론public폴더에 해당 이미지가 있을 경우)
- 이미지를
src/assects폴더- 이미지를
src/assets폴더(또는 실제로는src폴더의 어디든)에 저장할 수도 있습니다.
그렇다면public폴더에 저장하는 것과 어떤 차이가 있을까요? src또는src/assets와 같은 하위 폴더에 저장된 모든 파일(어던 형식이든)은 공개적으로 제공되지 않습니다.
웹사이트 방문자가 접근할 수 없습니다.localhost:5173/src/assets/some-image.jpg를 불러오려고 하면 오류가 발생합니다.- 대신,
src(및 하위 폴더)에 저장된 파일은 코드 파일에서 사용할 수 있습니다.
코드 파일에 가져온 이미지는 빌드 프로세스에 의해 인식되어 최적화되며, 웹사이트에 제공하기 직전에public폴더에 '삽입'됩니다.
가져온 이미지는 참조한 위치에서 자동으로 링크가 생성되어 사용됩니다.
- 이미지를
어떤 폴더를 사용해야 할까요?
- 빌드 프로세스에 의해 처리되지 않는 이미지는
public/폴더를 사용해야 하고 대체적으로 사용 가능합니다.
예를 들면index.html파일이나 파비콘과 같은 이미지가 좋은 후보입니다. - 반면, 컴포넌트 내에서 사용되는 이미지는 일반적으로
src/폴더(예:src/assets/)에 저장되어야 합니다.
- 빌드 프로세스에 의해 처리되지 않는 이미지는
22. 컴포넌트 분리하기
첫번째: FDA 기반
FDA:Feature Driven ArchitectureFDA는 기술적인 기능을 기반으로 폴더를 구성하는 방법론입니다.
각 코드가 하는 역할별로 폴더를 나눠 저장을 해놓았는데 문제는 한 폴더 내부에서 어떤 파일이 앱의 어떤 도메인과 연관이 있는지 파악하기가 매우 어렵다는 것입니다.Vue의Options API도 이와 같은 이유 때문에 문제가 많아Composition API가 등장한 것처럼FDA구조 때문에 코드 파악에 어려움을 겪는 문제가 많이 발생했고 그 때문에FSD가 등장하게 되었습니다.
Tic-Tac-Toe 게임
두번째: FSD 기반
FSD:Feature Sliced DesignFSD는 기능을 기반으로 컴포넌트를 분리하는 방법론입니다.
이때의 기능은 기술적인 기능이 아니라 사용자에게 제공되는 기능을 의미합니다.
이 방법론은 기능을 기반으로 컴포넌트를 분리하여 코드의 가독성을 높이고 유지보수를 쉽게 만듭니다.
Tic-Tac-Toe 게임
23. state 값 수정시 주의할점
상태 업데이트의 비동기성:
리액트에서 setState 함수를 사용하여 상태를 업데이트할 때, 이 변경은 즉시 발생하지 않습니다.
대신 리액트는 이 변경을 '스케줄링'하고, 컴포넌트가 다시 렌더링될 때 변경을 반영합니다.
이는 성능 최적화를 위해 설계된 부분입니다.
이전 상태값을 기반으로 한 상태 업데이트
상태 업데이트가 이전 상태값에 의존할 경우, 즉 새 상태값이 현재 상태값에 의해 결정되어야 할 때는 상태 업데이트 함수에 함수를 전달해야 합니다.
이렇게 하면 리액트가 자동으로 현재 상태값을 해당 함수의 매개변수로 제공하고, 이를 기반으로 새로운 상태값을 계산할 수 있습니다.
리액트 배치(Batch)
리액트에서의 "배치(Batching)"는 여러 개의 상태 업데이트를 그룹화하고 하나의 렌더링 패스에서 처리하는 리액트의 성능 최적화 기술입니다.
이 기능은 특히 이벤트 핸들러나 생명주기 메소드 내에서 발생하는 여러 상태 업데이트를 효율적으로 처리하기 위해 설계되었습니다.
배치의 작동 원리
리액트는 성능을 향상시키기 위해 가능한 경우 상태 업데이트를 "배치"로 처리합니다.
이는 다수의 setState 호출이 발생할 때, 각각의 호출이 즉시 독립적인 렌더링을 일으키는 것이 아니라, 리액트가 자동적으로 이를 하나의 업데이트로 결합하여 처리한다는 의미입니다.
예를 들어, 하나의 함수나 이벤트 핸들러에서 여러 번의 상태 업데이트(setState)를 호출한다면, 리액트는 이 모든 업데이트를 배치 처리하여 단 한 번의 렌더링으로 결과를 반영합니다.
이는 리액트가 각각의 상태 업데이트 후에 돔을 다시 그리는 것이 아니라, 모든 상태 업데이트가 완료된 후 최종 상태를 바탕으로 돔을 업데이트함으로써 불필요한 렌더링을 줄이고 성능을 개선합니다.
배치의 이점
- 성능 최적화:
- 배치 처리는 리액트 애플리케이션의 성능을 향상시킵니다.
- 여러 상태 변경을 하나의 업데이트로 그룹화함으로써 불필요한 렌더링 횟수를 줄일 수 있습니다.
- 일관성 유지:
- 상태 업데이트가 배치 처리되기 때문에, 렌더링 사이의 상태 불일치를 방지하고 UI의 일관성을 유지할 수 있습니다.
- 코드 간결성:
- 배치 처리는 개발자가 성능 최적화를 직접 관리하지 않고도 리액트가 자동으로 처리하게 함으로써, 코드의 복잡성을 줄이고 간결하게 유지할 수 있도록 합니다.
24. 사용자 입력 & 양방향 바인딩
// 아래는 예시 코드임
import React, { useState } from 'react';
function PlayerComponent() {
const [playerName, setPlayerName] = useState('Player 1');
const handleChange = (event) => {
setPlayerName(event.target.value);
};
return (
<div>
<h1>Player Name: {playerName}</h1>
<input
type="text"
value={playerName}
onChange={handleChange}
/>
</div>
);
}
export default PlayerComponent;25. 다차원 배열 렌더링
// 아래는 예시 코드임
import React from 'react';
const initialGameBoard = [
[null, null, null],
[null, null, null],
[null, null, null]
];
function GameBoard() {
return (
<div className="game-board">
{initialGameBoard.map((row, rowIndex) => (
<ol key={rowIndex} className="game-row">
{row.map((cell, colIndex) => (
<li key={colIndex} className="game-cell">
<button type="button">
{cell}
</button>
</li>
))}
</ol>
))}
</div>
);
}
export default GameBoard;26. immutable state 변경
- 상태 변경 불변성 유지:
- 리액트에서는 상태 객체나 배열을 직접 수정하지 않고, 복사본을 만들어서 업데이트하는 것이 좋습니다.
이렇게 하면 예기치 않은 버그를 방지하고, 리액트가 상태 변경을 효율적으로 감지할 수 있습니다.
- 리액트에서는 상태 객체나 배열을 직접 수정하지 않고, 복사본을 만들어서 업데이트하는 것이 좋습니다.
27. state 끌어올리기 (State Lifting)
두 개 이상의 자식 컴포넌트가 같은 데이터(여기서는 현재 플레이어)를 필요로 할 때, 그 상태를 공유하기 위해 가장 가까운 공통 부모 컴포넌트(App 컴포넌트)로 상태를 "끌어올립니다".
FDA (Feature Driven Architecture) 기반
Tic-Tac-Toe 게임
FSD (Feature Sliced Design) 기반
Tic-Tac-Toe 게임
28. state lifting 으로 인해 발생하는 문제점 해결하기
상태를 끌어올리는 것은 상태를 공유하기 위한 좋은 방법이지만, 하위 컴포넌트에서 상위 컴포넌트의 상태를 변경하면, 상위 컴포넌트 자체가 리렌더링되어야 하므로 성능 문제가 발생할 수 있습니다.
FDA (Feature Driven Architecture) 기반
Tic-Tac-Toe 게임
FSD (Feature Sliced Design) 기반
Tic-Tac-Toe 게임
29. 리액트 연습
30. 리액트 컴포넌트 스타일링
CSS기본 사용:- 가장 기본적인 스타일링 방법인
CSS사용법을 다룹니다. - 이는 별도의 추가 패키지 없이
CSS만을 사용하여 리액트 컴포넌트를 스타일링하는 방법입니다.
- 가장 기본적인 스타일링 방법인
CSS모듈:CSS모듈은CSS클래스 이름이 다른 컴포넌트 간에 충돌하는 것을 방지하게 해 주는 기술로, 각 컴포넌트에 로컬CSS를 적용할 수 있게 도와줍니다.
Styled Components:- 자바스크립트를 사용한
CSS-in-JS방식인Styled Components도 다룹니다. - 이 방식은 스타일을 자바스크립트 파일 내에서 직접 작성할 수 있게 해주며, 컴포넌트 단위로 스타일을 캡슐화할 수 있는 장점이 있습니다.
- 자바스크립트를 사용한
Tailwind CSS:Tailwind CSS는 유틸리티-퍼스트(utility-first)CSS프레임워크로, 빠르게 반응형 디자인을 구현할 수 있게 해 줍니다.
- 동적 스타일링:
- 조건부 스타일을 어떻게 적용하는지를 공부해봅니다.
- 예를 들어, 사용자가 유효하지 않은 데이터를 입력했을 때 입력 필드가 하이라이트되는 등의 조건적 스타일링을 설명합니다.
31. CSS 코드 여러 파일에 분단하기
32. CSS 기본으로 리액트 앱 스타일링하기 - 장단점
기본 CSS 사용법
- CSS 파일 작성:
- 스타일을 정의할 CSS 파일을 작성합니다.
- 이 파일에는 다양한 CSS 규칙이 포함될 수 있으며, 요소 선택자, ID 선택자, 클래스 선택자 등을 사용하여 스타일을 지정합니다.
- CSS 파일 Import:
- 생성한 CSS 파일을 필요한 JSX 파일(리액트 컴포넌트 파일)로 직접 import합니다.
- 예를 들어, import './styles.css';와 같이 CSS 파일을 리액트 컴포넌트에 연결할 수 있습니다.
- CSS 파일 작성:
기본 CSS의 장점
- 협업 용이:
- 디자이너나 다른 개발자가 별도로 CSS 파일을 작성하고, 이를 제공할 수 있으므로 개발자는 JSX 코드에 집중할 수 있습니다.
- 스타일 관련 작업을 분리하여 협업을 용이하게 합니다.
- 간단한 사용법:
- 특별한 규칙이나 방법을 배울 필요 없이 기본적인 CSS 지식으로 스타일을 적용할 수 있습니다.
- 이는 새로운 라이브러리나 문법을 배우지 않아도 되는 이점을 제공합니다.
- 분업 가능:
- 스타일링을 담당하는 팀원이 별도로 작업할 수 있으므로, 개발자는 컴포넌트 로직에 더 집중할 수 있습니다.
- 협업 용이:
기본 CSS의 단점
- CSS 지식 필요:
- 프로젝트에 CSS를 적용하기 위해서는 CSS에 대한 충분한 이해가 필요합니다.
- CSS에 익숙하지 않다면, 이를 담당할 수 있는 동료나 팀원의 도움이 필요할 수 있습니다.
- 스타일 충돌:
- 기본 CSS는 컴포넌트 별로 스타일이 스코핑(Scoping)되지 않습니다.
- 즉, 하나의 컴포넌트에서 사용된 CSS 규칙이 다른 컴포넌트에 영향을 미칠 수 있어, 스타일 충돌이 발생할 가능성이 있습니다.
- 이는 서로 다른 컴포넌트 간의 CSS 규칙이 겹치는 경우 문제가 될 수 있습니다.
- CSS 지식 필요:
33. Inline(인라인) 스타일로 리액트 앱 스타일링하기 - 장단점
기본 CSS의 스코핑 문제를 해결하기 위한 대안으로 인라인 스타일을 사용하는 방법이 있습니다.
인라인 스타일링은 CSS 스타일을 JSX 코드에 직접 적용하는 방식입니다.
이 방식을 통해 CSS 스타일이 해당 요소에만 적용되어 전역적인 스타일 충돌을 방지할 수 있습니다.
아래에서 인라인 스타일의 적용 방법과 장단점을 자세히 설명하겠습니다.
인라인 스타일 적용 방법
인라인 스타일 정의:
리액트에서 인라인 스타일은 JSX 요소의 style 속성을 통해 적용됩니다.
이때 스타일은 JavaScript 객체 형태로 제공되며, CSS 속성명은 카멜 케이스(camelCase)로 작성해야 합니다.
예를 들어, HTML의 text-align은 리액트에서 textAlign으로 작성합니다.
// JSX 코드 내 인라인 스타일 예시 function MyComponent() { return ( <p style={{ color: 'red', textAlign: 'left' }}>Hello, world!</p> ); }
스타일 속성 적용:
- 인라인 스타일에서는 각 스타일 속성을 객체의 키-값 쌍으로 지정합니다.
- 값은 문자열이어야 하며, 숫자가 필요한 경우(예: width, margin 등)는 자동으로 'px'가 붙습니다(단위를 지정하려면 문자열로 작성해야 합니다).
인라인 스타일의 장점
- 스코핑 보장:
- 인라인 스타일은 특정 JSX 요소에 직접 적용되므로, 스타일이 해당 요소에만 영향을 미치고 다른 요소에는 적용되지 않습니다.
- 이는 스타일의 격리와 충돌 방지에 유리합니다.
- 동적 스타일링 용이:
인라인 스타일을 사용하면 컴포넌트의 상태나 속성에 따라 동적으로 스타일을 적용하기 쉽습니다.
예를 들어, 조건에 따라 스타일을 변경할 수 있습니다.
// 동적 스타일 예시 function MyComponent({ isActive }) { return ( <p style={{ color: isActive ? 'blue' : 'gray' }}>Active State</p> ); }
인라인 스타일의 단점
- 재사용성 저하:
- 인라인 스타일은 재사용성이 낮습니다.
- 동일한 스타일을 여러 요소에 적용하려면 각 요소에 스타일을 복사해야 하며, 스타일 변경 시 모든 요소를 일일이 수정해야 합니다.
- 유지 관리 어려움:
- 대규모 애플리케이션에서는 인라인 스타일로 인해 코드가 복잡해지고 유지 관리가 어려워질 수 있습니다.
- 스타일 코드가 JSX와 섞이면서 가독성과 구조화가 저하됩니다.
- 퍼포먼스 문제:
- 인라인 스타일은 CSS 클래스를 사용하는 것보다 렌더링 성능에 더 많은 부담을 줄 수 있습니다.
- 스타일이 DOM 요소에 직접 적용되기 때문에 브라우저의 스타일 계산 과정이 늘어날 수 있습니다.
결론
인라인 스타일은 특정 상황에서 유용할 수 있으나, 전반적인 프로젝트 구조와 유지 관리 측면에서 고려할 요소가 많습니다.
스코핑을 효과적으로 관리하면서 재사용성과 유지 관리의 장점을 살릴 수 있는 방법으로 CSS 모듈이나 CSS-in-JS 같은 대안을 고려하는 것이 좋습니다.
34. CSS 파일과 CSS 클래스를 사용한 동적 및 조건적 스타일링
35. CSS 모듈로 리액트 앱 스타일링하기 - 장단점
CSS 모듈을 사용함으로써 전역 CSS의 문제점을 해결하고 각 컴포넌트에 독립적인 스타일을 적용할 수 있습니다.
CSS 모듈 사용의 필요성
프로젝트에서 여러 컴포넌트가 공통 CSS 파일을 사용할 때, 의도치 않게 다른 컴포넌트에 영향을 미치는 문제가 발생할 수 있습니다.
예를 들어, 하나의 CSS 규칙이 여러 요소에 불필요하게 적용되어 스타일이 의도치 않게 변경되는 경우가 이에 해당합니다.
CSS 모듈의 도입
CSS 모듈은 CSS 클래스 이름을 고유하게 만들어, CSS 규칙이 컴포넌트 외부로 영향을 미치지 않도록 합니다.
이는 CSS 파일에 .module.css 확장자를 사용하여 구현됩니다.
CSS 모듈의 장점
- 스코핑: CSS 모듈은 CSS 규칙이 해당 CSS 파일을 포함하는 컴포넌트에만 적용되도록 합니다. 이로 인해 다른 컴포넌트나 전역 스타일에 영향을 주지 않습니다.
- 유지 관리: 각 컴포넌트에 대해 독립적인 스타일 파일을 관리할 수 있어 프로젝트의 유지 보수가 용이해집니다.
CSS 모듈의 단점
- 파일 수의 증가: 많은 컴포넌트가 자체적인 CSS 모듈 파일을 갖게 되면, 프로젝트에 많은 수의 스타일 파일이 존재하게 됩니다. 이는 파일 관리를 복잡하게 할 수 있습니다.
36. styled components
styled-components는 리액트와 함께 사용되어 컴포넌트에 직접 스타일을 적용하는 방식으로, 별도의 CSS 파일이나 인라인 스타일을 사용하지 않고, 스타일을 컴포넌트 내에서 정의할 수 있게 해줍니다.
styled-components 의 기본 개념
- 컴포넌트 스타일링:
styled-components는 스타일을 직접 적용할 수 있는 리액트 컴포넌트를 생성합니다.- 이 방식을 사용하면,
CSS클래스 이름이 자동으로 생성되고, 해당 스타일은 전역CSS규칙의 영향을 받지 않습니다.
- 태그된 템플릿 리터럴:
- 이 패키지는
ES6의 태그된 템플릿 리터럴 기능을 사용하여 스타일을 정의합니다. - 태그된 템플릿 리터럴은 템플릿 리터럴을 입력으로 받아 처리하는 함수입니다.
- 이 패키지는
설치 및 사용 방법
npm i -D styled-componentsimport styled from 'styled-components';
const StyledDiv = styled.div`
background-color: blue;
color: white;
padding: 10px;
`;장점 및 특징
- 스코핑된 스타일: 각 스타일 컴포넌트는 고유한 클래스 이름을 가지므로, 스타일이 다른 컴포넌트에 영향을 주지 않습니다.
- 유지 관리 용이: 스타일을 컴포넌트와 함께 유지할 수 있어, 프로젝트의 구조가 명확해지고 유지 관리가 용이해집니다.
- 동적 스타일링:
props또는 테마를 기반으로 동적으로 스타일을 변경할 수 있습니다.
37. styled components 로 유동적 컴포넌트 생성
38. styled components 로 동적 및 조건적 스타일링 (createGlobalStyle)
39. styled components: 가상 선택자, 중첩 규칙 & 미디어 쿼리
40. 재사용 가능 컴포넌트 생성 및 컴포넌트 조합
41. 리액트 앱 스타일링을 위한 테일윈드 CSS 소개
테일윈드 CSS는 유틸리티-퍼스트라는 접근 방식을 사용하는 CSS 프레임워크로, 웹 애플리케이션에 직접 클래스를 추가하여 스타일을 적용합니다.
이 접근 방식은 HTML 요소에 스타일을 직접 적용하므로 CSS를 깊이 알지 못하는 개발자도 쉽게 사용할 수 있으며, 리액트뿐만 아니라 다양한 웹 프로젝트에서 유용하게 사용됩니다.
테일윈드 CSS의 특징
- 유틸리티-퍼스트 접근 방식:
- 테일윈드 CSS는 개발자가 HTML 요소에 직접 스타일 관련 클래스를 추가하여 사용합니다.
- 이 접근 방식은 스타일을 빠르게 적용할 수 있도록 돕고, 높은 수준의 재사용성과 일관성을 제공합니다.
테일윈드 CSS의 사용과 장점
클래스 추가를 통한 스타일링:
- 테일윈드
CSS를 사용하면className속성에 여러 유틸리티 클래스를 추가함으로써 요소의 스타일을 쉽게 조정할 수 있습니다. - 예를 들어,
padding,margin,font-size,color등을HTML태그에 직접 적용할 수 있습니다.
- 테일윈드
개발 편의성:
- 테일윈드
CSS는 많은 내장 스타일과 색상을 제공하여, 복잡한CSS를 작성하지 않고도 원하는 디자인을 신속하게 적용할 수 있습니다. Visual Studio Code와 같은IDE에서는 테일윈드CSS의Intellisense확장 프로그램을 설치하여 자동 완성 기능을 활용할 수 있습니다.- 이는 개발 속도를 향상시키는 데 도움이 됩니다.
- 테일윈드
사용자 정의 및 확장성:
- 테일윈드
CSS는 사용자 정의가 용이하여 프로젝트의 특정 요구사항에 맞게 색상이나 스타일을 조정할 수 있습니다. - 반응형 디자인을 위한 미디어 쿼리 클래스도 제공하여, 다양한 화면 크기에 따른 스타일 적용이 가능합니다.
- 테일윈드
tailwind 사용방법
- 테일윈드 설치
- postcss 설치
- autoprefixer 설치
42. 리액트 프로젝트에서 tailwind css 추가 및 사용법
tailwind는 유틸리티-퍼스트 CSS 프레임워크로서, 개발자들이 HTML 요소에 직접 스타일링 클래스를 적용할 수 있게 합니다.
그러나 이 프레임워크는 기본 설정 외에도 사용자 정의를 통해 개별 프로젝트의 요구에 맞게 조정할 수 있습니다.
테일윈드 CSS의 커스터마이징 방법
커스텀 배경과 폰트 추가:
- 배경:
- 프로젝트의 index.css 파일에 CSS 규칙을 추가하여 원하는 배경 이미지를 설정할 수 있습니다.
- 테일윈드를 사용하는 것이 일반적인 CSS 규칙을 추가하는 것을 금지하지 않기 때문에, 사용자 정의 배경을 적용할 수 있습니다.
- 폰트:
- Google Fonts에서 폰트를 불러와 HTML 파일에 추가한 후, 테일윈드의 설정 파일에서 fontFamily 설정을 조정하여 프로젝트 전체에서 사용할 수 있도록 합니다.
- 이를 통해 특정 요소에 새로운 폰트 스타일을 적용할 수 있습니다.
- 배경:
테일윈드 구성 파일 수정:
tailwind.config.js파일에서extend객체 내의theme설정을 사용하여fontFamily를 추가하고 새 폰트 식별자를 등록합니다.- 이 과정에서 폰트 이름은 배열 형태로 제공되며, 필요한 경우 폴백 폰트를 지정할 수 있습니다.
실제 예시의 적용
index.html
<link href="https://fonts.googleapis.com/css?family=YourFontName" rel="stylesheet">tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
title: ['"YourFontName"', 'cursive']
}
}
}
};YourFontName은Google Fonts나 다른 폰트 제공 서비스에서 제공하는 폰트의 이름을 나타냅니다.- 이 이름은 폰트를 웹 페이지에 적용하기 위해 사용됩니다. 예를 들어,
Google Fonts에서Roboto폰트를 사용하고 싶다면,YourFontName자리에Roboto를 입력합니다. - 이 이름은
HTML문서의<link>태그나CSS의@font-face규칙에 사용되어 웹 페이지에서 해당 폰트를 불러오고 사용할 수 있게 합니다.
- 이 이름은 폰트를 웹 페이지에 적용하기 위해 사용됩니다. 예를 들어,
cursive는 폰트 패밀리의 일반적인 범주 중 하나로, 필기체 스타일의 폰트를 의미합니다.- 이는 웹 표준
CSS폰트 패밀리 이름 중 하나로, 만약 사용자의 시스템이YourFontName을 로드하지 못할 경우 대체 폰트로 사용됩니다. cursive는 필기체와 유사한 폰트 스타일을 가진 시스템 기본 폰트로 대체됩니다.- 이는 폰트 로드 실패 같은 예외 상황에서 웹 페이지의 텍스트가 완전히 스타일 없이 표시되는 것을 방지하기 위해 사용됩니다.
- 이는 웹 표준
테일윈드 CSS에서의 폰트 설정
테일윈드 CSS 설정 파일(tailwind.config.js)에서 fontFamily 설정을 통해 사용자 정의 폰트를 적용하는 방법은 다음과 같습니다.
module.exports = {
theme: {
extend: {
fontFamily: {
title: ['"Roboto"', 'cursive']
}
}
}
};이 설정은 "Roboto" 폰트를 주 폰트로 사용하며, "Roboto"를 로드할 수 없는 경우 시스템의 기본 필기체 폰트로 대체하도록 설정합니다.
'title' 식별자는 테일윈드의 유틸리티 클래스 내에서 사용할 수 있으며, 예를 들어 font-title 클래스를 통해 HTML 요소에 적용할 수 있습니다.
43. 테일윈드: 미디어 쿼리 & 가상 선택자
반응형 디자인 (Responsive Design)
테일윈드 CSS에서는 @media 쿼리와 유사한 방식으로 작동하는 접두사(prefix)를 사용하여 특정 화면 크기에 따른 스타일을 적용합니다.
예를 들어, md: 접두사를 사용하면, 중간 크기(medium size) 이상의 화면에서만 특정 스타일을 적용할 수 있습니다.
<header class="md:mb-8 mb-2">이 코드는 중간 크기의 화면에서는 mb-8 (마진 바텀 2rem)을 적용하고, 그보다 작은 화면에서는 mb-2 (마진 바텀 0.5rem)을 적용합니다.
상호작용적 스타일 (Interactive Styles)
테일윈드 CSS에서는 hover, focus 같은 상태에 따른 스타일을 적용할 수 있는 유틸리티 클래스도 제공합니다.
이를 통해 사용자가 요소에 마우스를 올렸을 때 변화하는 스타일을 쉽게 추가할 수 있습니다.
<button class="px-4 py-2 bg-amber-400 hover:bg-amber-500">이 코드는 버튼의 기본 배경색을 노란색(bg-amber-400)으로 설정하고, 마우스를 올렸을 때는 좀 더 어두운 오렌지색(bg-amber-500)으로 변경됩니다.
커스텀 스타일과 테일윈드의 통합
테일윈드 CSS는 유틸리티 클래스만을 사용하는 것이 아니라, 전통적인 CSS 규칙과 함께 사용할 수 있습니다.
예를 들어, 특별한 배경 이미지나 커스텀 폰트를 적용하고 싶다면, 테일윈드 설정 파일에 이를 추가하고 HTML 요소에 적용할 수 있습니다.
<link href="https://fonts.googleapis.com/css?family=YourFontName" rel="stylesheet">tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
title: ['"YourFontName"', 'cursive']
}
}
}
};구현 예시
44. 테일윈드의 동적 및 조건적 스타일링
45. tailwind css 사용 예시 및 연습
46. tailwind css 정리
tailwind css 의 특성
Tailwind CSS는 유틸리티 중심의 CSS 프레임워크로, 많은 CSS 클래스를 사용하여 웹 페이지의 요소를 스타일링합니다.
이는 JSX 코드에서 많은 클래스 이름을 추가할 필요가 있다는 것을 의미합니다.
일부 개발자들은 이런 방식을 선호하지 않을 수도 있지만, 재사용 가능한 컴포넌트를 만들 때는 매우 유용합니다.
React와의 통합
Tailwind CSS는 React와 함께 사용될 때 강력합니다.
개발자는 여러 CSS 클래스를 조합하여 React 컴포넌트를 만들 수 있고, 이렇게 함으로써 코드의 재사용성을 높일 수 있습니다.
예를 들어, input 요소에 필요한 모든 스타일을 포함하는 컴포넌트를 만들어 다른 곳에서 쉽게 재사용할 수 있습니다.
스타일과 코드의 통합
Tailwind CSS를 사용하면, CSS 스타일을 직접 코드 내에 적용하기 때문에 스타일과 JSX 코드 간에 강한 연결이 있습니다.
이는 스타일 변경이 JSX 코드 수정을 필요로 하기 때문입니다.
이러한 접근 방식은 일부 개발자들에게는 불편할 수 있지만, 코드의 일관성을 유지하고 유지 관리를 쉽게 할 수 있습니다.
개발 속도와 유지보수
Tailwind CSS는 개발 속도를 높일 수 있습니다.
많은 유틸리티 클래스들을 기억하는 데 오래 걸리지 않고, 자동 제안 기능을 통해 적절한 클래스를 빠르게 찾을 수 있습니다.
또한, 글로벌 CSS 규칙을 정의하지 않기 때문에 스타일의 겹침을 피할 수 있습니다.
47. 리액트 앱 디버깅하기
React의 Strict Mode
React의 StrictMode는 개발자가 애플리케이션에서 잠재적인 문제를 더 쉽게 식별할 수 있도록 도와주는 기능입니다.
이 모드를 활성화하면, React는 애플리케이션의 안정성을 해칠 수 있는 부분에 대해 경고를 보내거나, 부적절한 사용을 감지할 때 오류를 발생시킵니다.
ReactDevTools의 활용:
ReactDevTools는 React 애플리케이션을 디버깅하고 분석할 때 유용한 확장 도구입니다.
이 도구를 설치하고 활용하는 방법을 배우게 되며, 컴포넌트의 상태를 검사하고 성능 문제를 진단하는 등의 작업을 수행할 수 있습니다.ReactDevTools는 특히 복잡한 애플리케이션에서 문제를 좀 더 빠르고 정확하게 파악할 수 있게 도와줍니다.
48. 리액트의 StrictMode(엄격모드) 이해하기
Strict Mode는 컴포넌트의 잠재적 문제를 빠르게 식별할 수 있게 해 줍니다.
특히, Strict Mode는 개발 모드에서 컴포넌트 함수를 두 번 실행하여 사이드 이펙트와 같은 문제들을 드러내는 데 도움을 줍니다.
Strict Mode의 사용
StrictMode 컴포넌트는 다른 컴포넌트를 감싸는 형태로 사용됩니다.
이 예에서는 App 컴포넌트 전체 또는 특정 부분만 감싸서 사용할 수 있습니다.main.jsx 파일에서 StrictMode를 import하고, 사용하고자 하는 컴포넌트를 StrictMode 태그로 감싸면 됩니다.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App/>
</React.StrictMode>,
)49. 리액트 DevTools 사용하기
이 도구는 React 애플리케이션 개발에 큰 도움을 주며, 특히 컴포넌트 관리와 디버깅을 용이하게 합니다.
확장 프로그램 설치:
"React Developer Tools"는 크롬 웹 스토어에서 쉽게 찾을 수 있으며, 설치 버튼을 클릭하여 브라우저에 추가할 수 있습니다.
설치 후, 브라우저의 개발자 도구에서 두 개의 새로운 탭, "Components"와 "Profiler"를 확인할 수 있습니다.
Components 탭:
이 탭은 애플리케이션의 컴포넌트 구조를 시각적으로 보여줍니다.
여기서는 App 컴포넌트를 비롯해 그 자식 컴포넌트들을 볼 수 있습니다.
각 컴포넌트 위로 마우스를 올리면 해당 컴포넌트가 어느 UI 부분을 렌더링하는지 강조 표시로 확인할 수 있습니다.
또한, 각 컴포넌트의 props(속성)과 상태(state)를 검사할 수 있습니다.
이를 통해 컴포넌트가 어떤 데이터를 받고, 어떤 상태를 관리하는지 파악할 수 있습니다.
상태 값이나 props를 수정하여 UI에 어떤 영향을 미치는지 즉시 볼 수 있습니다.
Profiler 탭:
이 탭은 애플리케이션의 성능 문제를 분석하는 데 사용됩니다.
리액트 컴포넌트의 렌더링 타이밍과 성능을 프로파일링하며, 특히 성능 최적화를 위한 분석에 유용합니다.
이 탭은 성능 관련 문제를 진단하고 해결하는 데 중요한 역할을 합니다.
50. refs, portals
refs
- 목적:
refs는 리액트에서DOM요소나 컴포넌트 인스턴스에 직접 접근할 수 있게 해 줍니다.- 이를 통해 상태 관리 없이
DOM을 직접 조작하거나, 특정API를 다른 컴포넌트에서 사용할 수 있게 만드는 컴포넌트를 생성할 수 있습니다.
portals
- 목적:
portals는 부모 컴포넌트의DOM계층 외부에 있는DOM노드로 자식 컴포넌트를 렌더링할 수 있게 해 줍니다.- 이를 통해 모달, 팝업, 툴팁 등을 메인
DOM트리와 별도로 관리할 수 있습니다.
51. useRef
입력 필드 초기화
useRef와 입력 필드useRef는DOM요소에 직접 접근할 수 있는 참조(reference)를 제공합니다.- 이 참조를 통해 입력 필드(
input field)의 실제DOM노드에 접근할 수 있으며, 이를 사용하여 입력 필드의 값을 직접 조작할 수 있습니다.
입력 필드 값 초기화
- 사용자가 'Set Name' 버튼을 클릭했을 때 입력 필드의 값을 초기화하려면, 참조된
DOM요소의value속성을 직접 빈 문자열("")로 설정합니다. - 이는
React의 선언적인 패러다임과는 다소 다른 명령적인 접근법입니다.
- 사용자가 'Set Name' 버튼을 클릭했을 때 입력 필드의 값을 초기화하려면, 참조된
React의 선언적 패러다임과 명령적 접근
선언적 패러다임
React는 선언적 프로그래밍 패러다임을 따릅니다.- 즉, 개발자는
UI가 어떻게 보여져야 하는지를 선언하고, 데이터가 변경될 때React가DOM을 적절히 업데이트하도록 합니다.
명령적 접근
useRef를 사용하여DOM요소의value를 직접 수정하는 것은React의 선언적 패러다임과는 다른, 더 명령적인 접근입니다.- 이 방법은
React가 아닌 전통적인JavaScript에서 자주 보이는 패턴입니다.
이런 접근이 적합할 때
- 단순 초기화와 조작
- 입력 필드를 초기화하는 등 단순한
DOM조작이 필요할 때useRef를 사용하는 것은 코드를 간결하게 만들고 빠른 조작을 가능하게 합니다.
- 입력 필드를 초기화하는 등 단순한
- 편리성과 예외적 상황:
- 상태 관리 없이
DOM요소를 직접 조작해야 할 때, 특히 상태나props에 의존하지 않는 단일 동작을 수행할 때 유용할 수 있습니다.
- 상태 관리 없이
주의사항
React의 패러다임 준수:- 참조를 사용할 때는
React의 패러다임을 벗어나지 않도록 주의해야 합니다. - 데이터 흐름을 관리하고
UI업데이트를React에 맡기는 방식을 유지하는 것이 중요합니다.
- 참조를 사용할 때는
- 과도한 사용 지양:
- 페이지의 모든 값을 읽고 조정하기 위해 참조를 과도하게 사용하지 않도록 주의해야 합니다.
- 이는
React의 핵심 원칙과 상충될 수 있습니다.
52. refs(참조) VS. state(상태) 값
참조(refs)와 상태(state)의 차이점
참조(
refs)- 참조는
DOM요소나React컴포넌트 인스턴스에 "직접 접근"을 제공합니다.useRef훅을 사용하여 생성되며, 생성된 참조 객체는.current속성을 통해 참조된 요소에 접근합니다. - 참조는 컴포넌트의 렌더 사이클과 독립적으로 존재하므로, 참조 값이 변경되어도 컴포넌트가 재렌더링되지 않습니다.
- 참조를 사용하여 입력 필드의 값을 직접 조작하거나 읽을 수 있으나, 이는
React의 선언적 패러다임과 다소 어긋날 수 있습니다.
- 참조는
상태(
state)- 상태는
UI의 변경사항을 추적하며, 상태가 변경될 때마다React가 컴포넌트를 재렌더링하도록 합니다.useState훅을 통해 관리되며, 상태 업데이트는 비동기적으로 일어날 수 있습니다. - 상태를 업데이트하면
React가 컴포넌트를 자동으로 업데이트하므로UI와 데이터의 동기화를 유지하는 데 이상적입니다. - 상태는 주로
UI에 직접적으로 반영되어야 하는 데이터를 관리하는 데 사용됩니다.
- 상태는
예시 시나리오
- 입력 필드 초기화하기
- 사용자가 이름을 입력하고 '이름 설정하기' 버튼을 클릭한 후, 입력 필드를 자동으로 지우고자 할 때 참조를 사용할 수 있습니다.
하지만 이는React의 선언적 패러다임에서 권장되지 않는 방법입니다.
대신, 상태를 사용하여 입력 필드의 값을 관리하고, 버튼 클릭 이벤트에서 상태를 초기화하여 선언적 방식을 유지하는 것이 바람직합니다.
- 사용자가 이름을 입력하고 '이름 설정하기' 버튼을 클릭한 후, 입력 필드를 자동으로 지우고자 할 때 참조를 사용할 수 있습니다.
53. state 연습하기 (타이머 컴포넌트)
일반 변수의 잘못된 사용
위 코드에서 timerId는 렌더링마다 새로 초기화되므로 handleStop 함수에서 타이머를 정확히 참조할 수 없습니다.
54. state 연습하기 2 (타이머 컴포넌트) - feat. useRef
이전 예시 코드와 차이점은 일반 변수를 사용하는 대신 useRef를 사용했다는 것입니다.
변수를 사용하는 것과 useRef를 사용하는 것의 차이점을 설명하겠습니다.
변수만으로 타이머를 관리하는 것과 useRef를 사용하는 것에는 몇 가지 중요한 차이가 있습니다.
변수 사용과 useRef 사용의 차이점
컴포넌트의 렌더링과 상태 유지:
- 변수 사용:
- 컴포넌트 함수 내부에 선언된 변수는 컴포넌트가 다시 렌더링될 때마다 초기화됩니다.
- 즉, 상태를 유지할 수 없습니다.
useRef사용:useRef를 사용하면, 리액트 컴포넌트의 전체 생명주기 동안 값이 유지됩니다.- 컴포넌트가 리렌더링되더라도
useRef의 값은 초기화되지 않습니다.
- 변수 사용:
리렌더링과 퍼포먼스:
- 변수 사용:
- 리렌더링마다 변수를 다시 선언하고 초기화하는 작업이 반복됩니다.
- 또한, 상태를 관리하기 위해
useState를 사용하면 불필요한 리렌더링이 발생할 수 있습니다.
useRef사용:useRef는 값이 변경되어도 컴포넌트를 리렌더링하지 않습니다.- 따라서 퍼포먼스에 유리합니다.
- 변수 사용:
요약
- 변수:
- 렌더링마다 초기화되기 때문에 타이머 ID를 저장하는 데 적합하지 않습니다.
useRef:- 컴포넌트의 생명주기 동안 값이 유지되며, 리렌더링 시에도 초기화되지 않습니다.
- 따라서 타이머 ID와 같은 값의 저장 및 참조에 적합합니다.
이러한 이유로, 타이머 ID와 같은 값을 저장할 때는 useRef를 사용하는 것이 바람직합니다.
55. "DOM 요소 연결" 외 Refs(참조) 활용법
핵심 개념
타이머 관리:
setTimeout함수는 타이머를 설정하고 타이머 ID를 반환합니다.- 이 타이머 ID를 이용해 타이머를 취소할 수 있습니다 (
clearTimeout).
참조(
useRef) 사용:- 변수는 리렌더링 시 초기화되므로 컴포넌트 함수 외부에 선언해야 합니다.
- 하지만 변수는 모든 컴포넌트 인스턴스에서 공유되므로 독립적인 타이머 관리를 위해서는 적절하지 않습니다.
useRef를 사용하면 컴포넌트 인스턴스마다 독립적인 값을 유지할 수 있습니다. 이는 리렌더링 시에도 초기화되지 않습니다.
타이머 제어:
useRef를 사용하여 타이머 ID를 저장하고, 이를 통해 타이머를 시작하고 멈출 수 있습니다.timerRef.current에 타이머 ID를 저장하여 참조하고, 이를 통해 타이머를 정지시킵니다.
요약
useRef사용 이유:useRef는 컴포넌트의 생명주기 동안 값을 유지합니다.- 리렌더링 시 초기화되지 않으며, 독립적인 값을 저장할 수 있습니다.
이렇게 하면 각 TimerChallenge 컴포넌트 인스턴스는 독립적으로 타이머를 관리할 수 있습니다.
이는 여러 타이머가 동시에 작동하고 중지될 수 있도록 해줍니다.useRef를 사용하여 타이머 ID를 저장함으로써 리렌더링에 영향을 받지 않고 타이머를 제어할 수 있습니다.
56. 모달 컴포넌트 추가하기 / 커스텀 컴포넌트로 refs(참조) 전달
설명
ResultModal.tsx:ResultModal컴포넌트를forwardRef로 감싸서 외부에서ref를 전달받을 수 있도록 수정합니다.forwardRef를 사용하여ResultModal컴포넌트를 감싸고,ref와props를 함께 받아서dialog요소에ref를 설정합니다.
TimerChallenge.tsx:useRef를 사용하여dialogRef를 생성하고,ResultModal컴포넌트에ref로 전달합니다.useEffect를 사용하여timerExpired나timerStarted가 변경될 때dialogRef.current.showModal()을 호출하여 모달을 표시합니다.- 이렇게 하면 타이머가 만료되거나 사용자가 타이머를 중지했을 때 결과 팝업창이 나타나게 됩니다.
ResultModal컴포넌트는forwardRef를 사용하여 부모 컴포넌트에서 참조할 수 있게 했고,TimerChallenge컴포넌트에서 타이머 상태에 따라 팝업창을 조건적으로 열도록 구현하였습니다.
57. useImperativeHandle / forwardRef
forwardRef
forwardRef는 React에서 부모 컴포넌트가 자식 컴포넌트의 ref를 직접 전달할 수 있게 해주는 고급 기술입니다.
보통 React 컴포넌트는 자체 ref를 관리하거나 DOM 요소에 ref를 붙이는 방식으로 동작하지만, 때로는 부모 컴포넌트가 자식 컴포넌트 내부의 DOM 요소나 인스턴스를 직접 참조해야 할 때가 있습니다.
이럴 때 forwardRef를 사용하면 유용합니다.
사용 시나리오
- 자식 컴포넌트 내부의 DOM 요소를 부모 컴포넌트에서 직접 제어해야 할 때
- 자식 컴포넌트에 대한 접근이 필요한 경우 (예: 라이브러리 통합)
사용 방법
forwardRef의 기본 사용 방법은 다음과 같습니다:
forwardRef로 컴포넌트 감싸기ref전달 받기
import { forwardRef } from 'react';
// 자식 컴포넌트
const CustomButton = forwardRef((props, ref) => (
<button ref={ref} {...props}>
Click me
</button>
));
export default CustomButton;import {useRef} from "react";
import CustomButton from "./CustomButton";
// 부모 컴포넌트
const ParentComponent = () => {
const buttonRef = useRef();
const handleClick = () => {
buttonRef.current.focus(); // 버튼에 포커스 설정
};
return (
<div>
<CustomButton ref={buttonRef} />
<button onClick={handleClick}>Focus the custom button</button>
</div>
);
};
export default ParentComponent;useImperativeHandle
useImperativeHandle은 React에서 커스텀 ref 인스턴스를 노출할 때 사용하는 Hook입니다.
이 Hook은 부모 컴포넌트가 자식 컴포넌트의 인스턴스를 직접적으로 제어해야 하는 경우에 유용합니다.useImperativeHandle은 보통 forwardRef와 함께 사용됩니다.
사용 시나리오
- 부모 컴포넌트가 자식 컴포넌트의 특정 메서드나 속성을 호출해야 할 때
- 외부 라이브러리와의 통합이 필요할 때 (예: DOM 조작)
사용 방법
다음은 useImperativeHandle을 사용하는 예제입니다:
forwardRef로 자식 컴포넌트 생성useImperativeHandle로ref인스턴스 커스터마이징
// 자식 컴포넌트
import { useImperativeHandle, forwardRef, useRef } from 'react';
// 자식 컴포넌트
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} {...props} />;
});
export default CustomInput;// 부모 컴포넌트
import { useRef } from 'react';
import CustomInput from './CustomInput';
const ParentComponent = () => {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
<button onClick={() => inputRef.current.clear()}>Clear Input</button>
</div>
);
};
export default ParentComponent;설명
forwardRef:CustomInput컴포넌트는forwardRef를 사용하여 부모 컴포넌트로부터ref를 전달받습니다.useImperativeHandle:useImperativeHandleHook을 사용하여 부모 컴포넌트가 접근할 수 있는 메서드 (focus와clear)를 정의합니다.inputRef:useRef를 사용하여 실제DOM input 요소에 접근합니다.
이 예제에서 ParentComponent는 버튼 클릭 시 CustomInput 컴포넌트의 focus와 clear 메서드를 호출할 수 있습니다.
참고 사항
useImperativeHandle은 성능 최적화를 위해 필요한 경우에만 사용하는 것이 좋습니다.
대부분의 경우, props를 통해 데이터를 전달하고 상호작용을 처리하는 것이 React 철학에 더 적합합니다.
필요하지 않은 경우 컴포넌트의 내부 구현을 외부에 노출하지 않는 것이 좋습니다.
이 Hook은 고급 사용 사례에 주로 사용되며, 일반적인 애플리케이션 개발에서는 자주 사용되지 않습니다.
58. dialog esc 키 이벤트 설정하기
<dialog>요소를 사용하면 웹사이트 방문자가 키보드의 ESC(Escape) 키를 눌러 열린 대화창을 닫을 수 있습니다.
이 동작은 자칫하면 close 버튼을 클릭한 것과 다르게 동작할 수 있습니다.
close 버튼에 또 다른 핸들러가 달려있다면 말입니다.
ESC 키로 대화창을 닫을 때도 같은 로직이 발생하게 하려면 아래와 같이 하면 됩니다.
import {useRef, useImperativeHandle, forwardRef} from 'react';
interface ResultModalProps {
result: string;
targetTime: number;
timeLeft: number;
}
export interface ResultModalHandle {
open: () => void;
}
const ResultModal = forwardRef<ResultModalHandle, ResultModalProps>(
({result, targetTime, timeLeft}, ref) => {
const dialogRef = useRef<HTMLDialogElement | null>(null);
useImperativeHandle(ref, () => ({
open() {
if (dialogRef.current) {
dialogRef.current.showModal();
}
},
}));
const score = ((targetTime - timeLeft) * 100 / targetTime).toFixed(2);
const handleClose = () => {
console.log('close');
}
return (
// dialog 엘리면트에 onClose 이벤트를 등록하여 ESC 키를 눌렀을 때 handleClose 함수를 호출하도록 설정합니다.
// 이렇게하면 Close 버튼을 눌렀을 때와, ESC 키를 눌렀을 때 동일한 동작을 수행하게 됩니다.
<dialog className="result-modal" ref={dialogRef} onClose={handleClose}>
<h2>{result === 'lost' ? '실패했습니다.' : '성공했습니다~!'}</h2>
<p>
목표 시간은 <strong>{targetTime}</strong>초였습니다. <br/>
점수는 <strong>{score}</strong>점입니다.
</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
);
},
);
export default ResultModal;59. Portals(포탈) 소개 및 이해하기
포털(Portal)은 React에서 DOM 트리의 다른 부분으로 컴포넌트의 렌더링을 이동시키는 기능입니다.
이는 주로 모달, 툴팁, 드롭다운 메뉴와 같은 UI 요소가 페이지의 최상위에 렌더링되도록 할 때 사용됩니다.
이를 통해 구조적으로 더 나은 접근성을 제공하고, 특정 CSS 규칙의 충돌을 피할 수 있습니다.
포털을 사용하는 이유
포털을 사용하면 다음과 같은 장점을 얻을 수 있습니다:
DOM구조의 명확성: 모달과 같은 요소가 페이지의 최상위에 위치하게 되어, 시각적, 구조적으로 더 직관적입니다.- 접근성 향상: 특정 상황에서 깊게 중첩된 요소가 다른 요소들에 의해 숨겨지지 않도록 보장합니다.
- 스타일링 문제 해결: 모달 등의 오버레이 요소가 다른 콘텐츠와 스타일 충돌 없이 올바르게 렌더링됩니다.
포털 사용 방법
포털을 사용하기 위해 react-dom 라이브러리에서 createPortal 함수를 가져와야 합니다.
코드 예제
index.html수정먼저, 포털이 렌더링될
HTML요소를 추가합니다.public/index.html파일에 다음과 같이 추가합니다.<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React App</title> </head> <body> <div id="root"></div> <div id="modal"></div> <!-- 포털이 렌더링될 위치 --> </body> </html>
ResultModal컴포넌트 수정이제
ResultModal컴포넌트를 수정하여 포털을 사용하도록 합니다.import {useRef, useImperativeHandle, forwardRef} from 'react'; import {createPortal} from "react-dom"; interface ResultModalProps { result: string; targetTime: number; timeLeft: number; } export interface ResultModalHandle { open: () => void; } const ResultModal = forwardRef<ResultModalHandle, ResultModalProps>( ({result, targetTime, timeLeft}, ref) => { const dialogRef = useRef<HTMLDialogElement | null>(null); useImperativeHandle(ref, () => ({ open() { if (dialogRef.current) { dialogRef.current.showModal(); } }, })); const score = ((targetTime - timeLeft) * 100 / targetTime).toFixed(2); const handleClose = () => { console.log('close'); } const modalContent = ( <dialog className="result-modal" ref={dialogRef} onClose={handleClose}> <h2>{result === 'lost' ? '실패했습니다.' : '성공했습니다~!'}</h2> <p> 목표 시간은 <strong>{targetTime}</strong>초였습니다. <br/> 점수는 <strong>{score}</strong>점입니다. </p> <form method="dialog"> <button>Close</button> </form> </dialog> ) return createPortal( modalContent, document.getElementById('modal')!, ) }, ); export default ResultModal;
60. 연습
Prop Drilling이란?
Prop Drilling은 React에서 데이터를 한 컴포넌트에서 여러 자식 컴포넌트로 전달하는 과정입니다.
상위 컴포넌트의 상태를 하위 컴포넌트가 필요로 할 때, props를 통해 데이터를 전달하게 되는데, 이 과정에서 여러 중간 컴포넌트를 거쳐야 하는 경우가 생길 수 있습니다.Prop Drilling은 이러한 데이터를 전달하는 과정을 나타내는 용어입니다.
Proxy Props VS. Prop Drilling
Prop Drilling은 React에서 데이터를 한 컴포넌트에서 여러 자식 컴포넌트로 전달하는 과정을 말합니다.
상태나 함수가 필요하지 않은 중간 컴포넌트를 거쳐 최종적으로 필요한 자식 컴포넌트에 도달하는 경우가 생기는데, 이 과정에서 여러 단계의 컴포넌트로 props를 전달하게 됩니다.
// 최상위 컴포넌트
const App = () => {
const [state, setState] = useState('some state');
return (
<LevelOneComponent state={state} setState={setState} />
);
};
const LevelOneComponent = ({ state, setState }) => (
<LevelTwoComponent state={state} setState={setState} />
);
const LevelTwoComponent = ({ state, setState }) => (
<FinalComponent state={state} setState={setState} />
);
const FinalComponent = ({ state, setState }) => (
<div>
<p>{state}</p>
<button onClick={() => setState('new state')}>Change State</button>
</div>
);이 예시에서 state와 setState가 App 컴포넌트에서 FinalComponent까지 전달되는 과정을 Prop Drilling이라고 합니다.LevelOneComponent와 LevelTwoComponent는 실제로 state와 setState를 사용하지 않지만, props로 전달해야 합니다.
Proxy Prop
Proxy Prop은 prop drilling 문제를 완화하기 위해 사용되는 패턴 중 하나입니다.
이는 중간 컴포넌트에서 특정 props를 전달만 하는 역할을 맡도록 하여, 최종 컴포넌트로 필요한 props를 쉽게 전달하는 방법입니다.
이를 통해 중간 컴포넌트의 역할을 명확하게 하고 코드를 더 간결하게 만들 수 있습니다.
const App = () => {
const [state, setState] = useState('some state');
return (
<ProxyComponent state={state} setState={setState} />
);
};
const ProxyComponent = ({ state, setState }) => (
<LevelOneComponent>
<LevelTwoComponent>
<FinalComponent state={state} setState={setState} />
</LevelTwoComponent>
</LevelOneComponent>
);
const LevelOneComponent = ({ children }) => (
<div>
{children}
</div>
);
const LevelTwoComponent = ({ children }) => (
<div>
{children}
</div>
);
const FinalComponent = ({ state, setState }) => (
<div>
<p>{state}</p>
<button onClick={() => setState('new state')}>Change State</button>
</div>
);이 예시에서 ProxyComponent는 props를 직접 FinalComponent에 전달합니다.LevelOneComponent와 LevelTwoComponent는 children을 사용하여 FinalComponent를 래핑(wrapping)합니다.
이를 통해 prop drilling을 최소화하고, 필요한 props를 최종 컴포넌트로 쉽게 전달할 수 있습니다.
차이점 요약
Prop Drilling: 데이터를 여러 단계의 중간 컴포넌트를 거쳐 전달하는 과정.Proxy Prop:prop drilling문제를 완화하기 위해, 중간 컴포넌트에서 필요한props를 최종 컴포넌트로 전달하는 패턴.
두 개념은 유사한 문제를 해결하려고 하지만, Proxy Prop는 Prop Drilling을 해결하기 위한 패턴 중 하나입니다.Prop Drilling은 기본적으로 props를 깊은 컴포넌트 트리로 전달하는 방법이고, Proxy Prop는 이를 쉽게 하기 위한 방법입니다.
61. Context API & useReducer (상태 관리 심화)
상태 관리의 중요성
리액트 앱이 복잡해질수록 여러 컴포넌트들이 서로 상호작용하게 됩니다.
이때, 상태(state)는 데이터와 그 데이터의 변화 상황을 관리하는 중요한 요소입니다.
상태 관리가 제대로 이루어지지 않으면, 앱의 유지보수와 확장이 어려워지고 버그가 발생할 가능성도 높아집니다.
프로퍼티 내리꽂기(Prop Drilling) 문제
복잡한 리액트 앱에서는 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달하는 경우가 많습니다.
이 과정에서 프로퍼티 내리꽂기(prop drilling) 문제가 발생합니다.Prop drilling이란, 데이터를 여러 단계의 자식 컴포넌트에 전달하기 위해 중간 컴포넌트들을 거쳐야 하는 상황을 말합니다.
이는 다음과 같은 문제를 일으킬 수 있습니다:
- 중간 컴포넌트들이 불필요한 데이터 전달자로 변질됨
- 컴포넌트 구조가 복잡해지고 유지보수가 어려워짐
해결 방법
프로퍼티 내리꽂기 문제를 해결하기 위해 다양한 방법이 있습니다.
이번 섹션에서는 이러한 문제를 어떻게 해결할 수 있는지 자세히 알아보겠습니다.
리액트의 컨텍스트(Context) API
리액트의 컨텍스트(Context) API는 전역적으로 상태를 관리하고 여러 컴포넌트에 걸쳐 있는 상태를 쉽게 공유할 수 있도록 해줍니다.Context API를 사용하면 중간 컴포넌트를 거치지 않고도 필요한 데이터와 함수들을 필요한 컴포넌트에 직접 전달할 수 있습니다.
이것은 다음과 같은 이점을 제공합니다:
- 코드가 더 깔끔하고 직관적으로 변함
- 상태 관리가 더 쉬워짐
- 유지보수가 용이해짐
리듀서(Reducer)를 사용한 상태 관리
마지막으로, 이 섹션에서는 새로운 리액트 훅인 useReducer를 사용하여 더 복잡한 상태 데이터를 관리하는 방법에 대해 알아보겠습니다.useReducer 훅은 리듀서(reducer) 함수를 사용하여 상태 업데이트 로직을 정의할 수 있게 해줍니다.
이를 통해 얻을 수 있는 장점은 다음과 같습니다:
- 상태 변경 로직을 더 명확하게 분리하고 관리할 수 있음
- 복잡한 상태 업데이트를 더 쉽게 처리할 수 있음
- 코드의 가독성과 유지보수성을 높일 수 있음
리듀서(reducer)는 현재 상태와 액션(action)을 받아 새로운 상태를 반환하는 함수입니다.useReducer 훅을 사용하면 상태 업데이트 로직을 컴포넌트 외부로 분리하여 더 체계적으로 관리할 수 있습니다.
62. Context API
Prop Drilling 문제를 해결하기 위해 React에서는 Context API를 제공합니다.
컨텍스트(Context) API란?
컨텍스트 API는 리액트에서 제공하는 기능으로, 컴포넌트 트리에서 데이터를 전역적으로 관리하고 전달할 수 있게 합니다.
이를 통해 여러 컴포넌트 레이어를 거치지 않고 필요한 데이터를 원하는 컴포넌트에 직접 전달할 수 있습니다.
컨텍스트 API의 구성 요소
컨텍스트 API는 다음 세 가지 주요 구성 요소로 이루어져 있습니다:
- 컨텍스트 생성 (
Context Creation): 컨텍스트 객체를 생성합니다. - 컨텍스트 제공 (
Context Provider): 생성된 컨텍스트를 이용해 데이터를 제공하는 컴포넌트입니다. - 컨텍스트 소비 (
Context Consumer): 제공된 컨텍스트 데이터를 사용하는 컴포넌트입니다.
63. context api 예시 코드
예시: useCartContext 커스텀 훅
64. useContext VS. Consumer
useContext 훅은 최신 방식이며, Consumer 컴포넌트는 과거의 방식입니다.
두 가지 방법을 각각 설명하고 비교해 보겠습니다.
// useContext 훅 사용하기
import React from 'react';
import {useCartContext} from '../store/shopping-cart-context';
const Cart: React.FC = () => {
const {items} = useCartContext();
return (
<div className="p-4 bg-gray-100">
<h2 className="text-xl mb-4">Your Shopping Cart</h2>
{items.length === 0 ? (
<p>Your cart is empty!</p>
) : (
<ul>
{items.map((item) => (
<li key={item.id} className="mb-2">
{item.name}
</li>
))}
</ul>
)}
</div>
);
};
export default Cart;useContext훅- 간단하고 직관적: 함수형 컴포넌트 내에서 쉽게 사용할 수 있습니다.
- 코드가 깔끔: 코드의 양이 적고 가독성이 좋습니다.
- 현대적: 최신 리액트 프로젝트에서 많이 사용됩니다.
// Consumer 컴포넌트 사용하기
import React from 'react';
import CartContext from '../store/shopping-cart-context';
const Cart: React.FC = () => {
return (
<CartContext.Consumer>
{({items}) => (
<div className="p-4 bg-gray-100">
<h2 className="text-xl mb-4">Your Shopping Cart</h2>
{items.length === 0 ? (
<p>Your cart is empty!</p>
) : (
<ul>
{items.map((item) => (
<li key={item.id} className="mb-2">
{item.name}
</li>
))}
</ul>
)}
</div>
)}
</CartContext.Consumer>
);
};
export default Cart;Consumer컴포넌트- 구식 코드에서 유용: 기존의 코드베이스에서 많이 사용되었으며, 여전히 사용될 수 있습니다.
- 복잡하고 장황:
JSX내에 함수가 포함되어 코드가 길고 복잡해질 수 있습니다. - 비추천: 최신 리액트 프로젝트에서는 잘 사용되지 않습니다.
65. context 값이 바뀌면 생기는 일
리액트에서 컨텍스트 값의 처리와 컴포넌트 재실행
리액트에서 컴포넌트가 컨텍스트 값을 사용하는 경우, 리액트가 어떻게 동작하는지 이해하는 것이 중요합니다.
특히, 컨텍스트 값이 변경되었을 때 컴포넌트가 어떻게 재실행되는지 알아보겠습니다.
컴포넌트 재실행
- 리액트에서는 여러 상황에서 컴포넌트 함수가 재실행됩니다:
- 내부 상태 변경: 컴포넌트의 상태가 변경되면, 리액트는 컴포넌트를 다시 렌더링합니다.
- 부모 컴포넌트 재실행: 부모 컴포넌트가 재실행되면, 자식 컴포넌트도 다시 실행됩니다.
- 컨텍스트 값 변경: 컴포넌트가
useContext훅을 사용하고 있는 경우, 해당 컨텍스트 값이 변경되면 컴포넌트가 재실행됩니다.
- 리액트에서는 여러 상황에서 컴포넌트 함수가 재실행됩니다:
useContext훅 사용 시 컴포넌트 재실행- 컴포넌트가
useContext훅을 사용하여 컨텍스트 값을 사용하고 있다면, 그 컨텍스트 값이 변경될 때마다 컴포넌트가 재실행됩니다.
이는 리액트가 컴포넌트를 재실행하여 새로운 UI를 생성할 수 있도록 하기 위함입니다.
- 컴포넌트가
컴포넌트 재실행 이유
- 컨텍스트 값이 변경되면 컴포넌트가 재실행되는 이유는 간단합니다.
새로운 값을 반영한 새로운 UI를 렌더링하기 위해서입니다.
- 컨텍스트 값이 변경되면 컴포넌트가 재실행되는 이유는 간단합니다.
66. context api 예시 코드 2
67. useReducer
68. useEffect (feat. side effect)
- 부수 효과의 정의와 관리 방법
- 부수 효과란 무엇인지
- 부수 효과를 어떻게 관리해야 하는지
useEffect훅 사용useEffect훅을 언제 사용하는 것이 좋은지useEffect훅을 사용하는 방법- 의존성 배열의 정의와 관리 방법
useEffect를 사용하면 안 되는 상황에 대해
부수 효과(side effect)
부수 효과는 앱이 제대로 동작하기 위해 실행되어야 하지만, 현재 컴포넌트의 렌더링 과정에 직접적인 영향을 미치지 않는 작업을 의미합니다.
다시 말해, 이 작업은 필수적이지만 컴포넌트가 화면에 어떻게 보이는지와는 직접적인 연관이 없습니다.
예시: 사용자 위치 기반 장소 정렬
작업의 목표:
- 웹사이트 사용자로부터의 거리를 기준으로 장소들을 정렬합니다.
- 예를 들어, 사하라 사막 근처에 거주하는 사용자는 해당 지역의 장소가 먼저 보이게 합니다.
필요한 작업:
- 사용자의 현재 위치를 알아내야 합니다.
- 이를 위해 브라우저의
navigator객체와getCurrentPosition메소드를 사용합니다.
코드 설명
위치 파악:
- 브라우저에서 제공하는
navigator객체를 사용하여 사용자의 현재 위치를 얻습니다. getCurrentPosition메소드를 호출하여 사용자의 위치 정보를 받아옵니다.- 이 메소드는 콜백 함수를 사용하여 위치가 파악된 후 호출됩니다.
- 브라우저에서 제공하는
위치 기반 정렬:
loc.js파일에 있는sortPlacesByDistance함수를 사용하여 장소들을 정렬합니다.- 이 함수는 두 지점 간의 거리를 계산하여 장소들을 거리순으로 정렬합니다.
위치 정보 전달:
getCurrentPosition메소드를 통해 얻은 위치 정보를sortPlacesByDistance함수에 전달합니다.- 이 함수는 사용자의 위도(
latitude)와 경도(longitude)를 입력받아 장소들을 정렬합니다.
왜 부수 효과일까?
이 작업이 부수 효과인 이유는, 사용자 위치를 파악하는 코드는 앱이 제대로 동작하기 위해 필요하지만, 컴포넌트 함수의 주된 목적과는 직접적인 관련이 없기 때문입니다.
컴포넌트 함수의 주된 목적은 렌더링할 JSX 코드를 반환하는 것입니다.
그러나 사용자의 위치를 파악하는 코드는 렌더링 과정과 직접적으로 관련이 없기 때문에 부수 효과로 간주됩니다.
부수 효과의 특징:
- 렌더링과 직접적인 관련 없음: 부수 효과는 컴포넌트의 렌더링 과정과 직접적으로 관련이 없습니다.
- 비동기 작업 포함: 부수 효과는 비동기 작업을 포함할 수 있으며, 이는 나중에 완료될 수 있습니다.
- 컴포넌트 외부 영향: 부수 효과는 컴포넌트 외부의 상태나 환경에 영향을 미칠 수 있습니다.
부수 효과 관리 방법
리액트에서 부수 효과를 관리하기 위해 useEffect 훅을 사용합니다.useEffect 훅은 컴포넌트가 렌더링된 후에 실행되어야 하는 부수 효과를 정의하는 데 사용됩니다.
import React, { useEffect } from 'react';
import { sortPlacesByDistance } from './loc';
import placesData from './data';
const App = () => {
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
placesData,
position.coords.latitude,
position.coords.longitude
);
// 정렬된 장소 배열을 상태로 설정하는 코드 작성
});
}, []);
return (
<div>
{/* 앱의 JSX 코드 */}
</div>
);
};
export default App;정리
부수 효과는 앱이 제대로 동작하기 위해 필요하지만, 컴포넌트의 렌더링 과정과 직접적인 관련이 없는 작업입니다.
사용자의 위치 파악과 같은 작업은 부수 효과의 예입니다.
리액트에서는 useEffect 훅을 사용하여 부수 효과를 관리합니다.
69. side effect 잠재적 문제: 무한 루프
문제 상황
정렬된 장소 데이터를 화면에 보여주기:
- 사용자 위치를 기반으로 정렬된 장소 리스트를 보여주고자 합니다.
- 이 데이터를
Places컴포넌트에 전달해야 합니다.
위치 데이터를 얻는 과정:
- 사용자의 위치를 파악하는 작업에는 시간이 소요됩니다.
- 위치 데이터를 얻은 후에야 정렬된 장소 리스트를 사용할 수 있습니다.
상태 관리 필요성
- 상태(
state):- 초기에는 비어 있는 배열로 시작합니다.
- 위치 데이터를 얻으면 상태를 업데이트합니다.
- 업데이트된 상태를
Places컴포넌트에 전달합니다.
문제 발생: 무한 루프
이 방식은 무한 루프를 야기할 수 있습니다. 왜 그럴까?
- 상태 업데이트:
- 상태를 업데이트하면 리액트는 해당 컴포넌트 함수를 재실행합니다.
- 재실행되면 위치 파악 작업이 다시 시작됩니다.
- 위치 파악 후 상태를 다시 업데이트합니다.
- 이 과정이 계속 반복되면서 무한 루프가 발생합니다.
무한 루프 예시
const App = () => {
const [availablePlaces, setAvailablePlaces] = useState([]);
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
placesData,
position.coords.latitude,
position.coords.longitude
);
setAvailablePlaces(sortedPlaces);
});
return (
<div>
<Places places={availablePlaces} />
</div>
);
};위 코드에서는 상태를 업데이트할 때마다 App 컴포넌트가 다시 렌더링되고, 위치 파악 작업이 다시 시작되므로 무한 루프가 발생합니다.
문제 해결: useEffect 훅 사용
이 문제를 해결하기 위해 useEffect 훅을 사용합니다.useEffect는 부수 효과를 관리하는 데 도움을 줍니다.
import React, { useEffect, useState } from 'react';
import { sortPlacesByDistance } from './loc';
import placesData from './data';
import Places from './Places';
const App = () => {
const [availablePlaces, setAvailablePlaces] = useState([]);
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
const sortedPlaces = sortPlacesByDistance(
placesData,
position.coords.latitude,
position.coords.longitude
);
setAvailablePlaces(sortedPlaces);
});
}, []); // 빈 배열을 의존성으로 설정하여 첫 렌더링 시에만 실행되도록 합니다.
return (
<div>
<Places places={availablePlaces} />
</div>
);
};
export default App;useEffect의 역할
첫 렌더링 시에만 실행:
useEffect의 두 번째 인자로 빈 배열[]을 전달합니다.- 이 경우,
useEffect는 컴포넌트가 처음 렌더링될 때만 실행됩니다.
부수 효과 관리:
navigator.geolocation.getCurrentPosition메소드로 위치를 파악하고, 그 결과를 상태로 설정합니다.- 상태 업데이트 후 컴포넌트가 재실행되어도
useEffect는 다시 실행되지 않으므로 무한 루프가 발생하지 않습니다.
정리
- 문제: 부수 효과를 잘못 관리하면 무한 루프가 발생할 수 있습니다.
- 해결 방법:
useEffect훅을 사용하여 부수 효과를 첫 렌더링 시에만 실행되도록 합니다. - 이점: 부수 효과를 올바르게 관리하여 앱이 안정적으로 동작하게 합니다.
70. useEffect를 사용하는 side effect
의존성 배열
의존성 배열은 useEffect 훅의 두 번째 인수로 전달됩니다.
이 배열에 명시된 값이 변경될 때마다 useEffect 훅이 다시 실행됩니다.
빈 배열 []을 전달하면 컴포넌트가 처음 렌더링될 때만 useEffect가 실행됩니다.
무한 루프 방지
무한 루프를 방지하려면 의존성 배열을 올바르게 설정해야 합니다.
의존성 배열을 빈 배열로 설정하면 컴포넌트가 처음 렌더링된 후에만 useEffect 훅이 실행되므로, 상태 업데이트로 인해 컴포넌트가 다시 렌더링되더라도 useEffect 훅이 다시 실행되지 않습니다.
71. 모든 side effect가 useEffect를 사용하지 않는 이유
부수 효과를 이해하는 데 중요한 점은, 모든 부수 효과가 useEffect 훅을 필요로 하지 않는다는 것입니다.useEffect 훅은 무한 루프를 방지하거나, 컴포넌트가 렌더링된 후에 실행되어야 하는 작업이 있을 때 사용됩니다.
하지만, 컴포넌트 함수 내에서 직접 실행해도 문제가 없는 부수 효과도 있습니다.
여기서는 localStorage를 사용하는 예시를 통해 이를 설명하겠습니다.
예시: localStorage를 이용한 데이터 저장
기본 개념
localStorage는 브라우저가 제공하는 객체로, 데이터를 로컬에 저장하고 앱이 다시 로드되어도 해당 데이터를 유지할 수 있게 해줍니다.localStorage는 문자열 형태의 데이터를 저장합니다.
데이터 저장 예시
- 사용자가 장소를 선택하면:
- 선택한 장소와 선택된 장소들의 목록을
localStorage에 저장합니다. - 앱이 다시 로드되었을 때도 해당 장소들이 유지되도록 합니다.
- 선택한 장소와 선택된 장소들의 목록을
- 코드 구현:
- 사용자가 장소를 선택하면
handleSelectPlace함수가 실행됩니다. - 이 함수는 선택된 장소를 상태로 업데이트하고,
localStorage에 저장합니다.
- 사용자가 장소를 선택하면
- 사용자가 장소를 선택하면:
72. useEffect가 필요 없는 경우: 다른 예시 (LocalStorage)
목표
- 선택한 장소를
localStorage에 저장 - 선택한 장소를 삭제할 때
localStorage에서 삭제 - 앱이 시작될 때
localStorage에서 데이터를 로드
73. useEffect 활용 사례
이번에는 useEffect와 의존성 배열을 이해하기 위해 Modal 컴포넌트를 다뤄보겠습니다.Modal 컴포넌트를 상태 기반으로 변경하고, useEffect를 활용해 백드롭을 추가하는 방법을 살펴보겠습니다.
74. Effect Dependencis(의존성) 이해하기
useEffect 훅은 컴포넌트가 렌더링될 때 특정 작업을 수행하기 위해 사용됩니다.
이 훅은 의존성 배열을 통해 언제 실행될지를 제어할 수 있습니다.
의존성 배열에는 해당 useEffect 함수가 종속되는 값(속성 또는 상태)을 넣습니다.
이 값들이 변경될 때마다 useEffect 함수가 다시 실행됩니다.
75. dialog esc 키 버그 수정하기
<dialog> 요소는 기본적으로 ESC 키를 눌러 닫을 수 있습니다.
하지만 이 경우 모달은 닫히지만 modalIsOpen 상태가 false로 설정되지 않습니다.
따라서 모달을 다시 열 수 없습니다.
이 문제를 해결하기 위해 모달이 닫힐 때 onClose 이벤트를 청취하여 modalIsOpen 상태를 업데이트해야 합니다.
76. useEffect의 도움으로 고칠 수 있는 다른 문제들
DeleteConfirmation 컴포넌트는 모달의 내용을 렌더링하며, 3초 후에 자동으로 모달을 닫고 항목을 삭제하는 기능을 추가할 것입니다.
이를 위해 setTimeout 함수를 사용합니다.
그러나, 모달이 닫히거나 항목이 수동으로 삭제될 때 타이머가 취소되지 않으면 문제가 발생합니다.
이를 해결하기 위해 useEffect 훅을 사용하여 타이머를 설정하고, 컴포넌트가 언마운트될 때 타이머를 정리합니다.
확인 방법
Place 1을 선택한 후Open Modal을 클릭후 모달이 꺼질 때까지 대기- 자동으로 꺼지면서
Place 1삭제 - 자동으로 꺼지기 전에 사용자가 모달을 닫으면
setTimeout을 취소해줘야됨
77. useEffect의 CleanUp 함수 소개
이번 시간에는 컴포넌트가 사라질 때 타이머를 멈추는 방법을 다룹니다.
컴포넌트가 사라질 때 타이머를 정리하지 않으면, 컴포넌트가 사라진 후에도 타이머가 계속 실행될 수 있습니다.
이를 방지하기 위해 useEffect와 클린업 함수(cleanup function)를 사용합니다.
useEffect를 사용하여 타이머 설정 및 정리
타이머 설정:
setTimeout함수를 사용하여 타이머를 설정합니다.- 타이머가 설정되면 일정 시간 후에 지정된 함수가 실행됩니다.
타이머 정리:
useEffect의 클린업 함수를 사용하여 타이머를 정리합니다.- 클린업 함수는 컴포넌트가 언마운트되기 전에 실행됩니다.
- 클린업 함수에서
clearTimeout함수를 사용하여 타이머를 정리합니다.
// DeleteConfirmation.tsx
import React, { useEffect } from 'react';
interface DeleteConfirmationProps {
onCancel: () => void;
onConfirm: () => void;
}
const DeleteConfirmation: React.FC<DeleteConfirmationProps> = ({ onCancel, onConfirm }) => {
useEffect(() => {
const timer = setTimeout(() => {
onConfirm();
}, 3000);
return () => {
clearTimeout(timer);
console.log('타이머 내용 삭제 중');
};
}, [onConfirm]);
return (
<div>
<p>Are you sure you want to delete this item?</p>
<button onClick={onCancel} className="btn btn-secondary bg-gray-500 text-white px-4 py-2 rounded shadow-md hover:bg-gray-600 focus:outline-none">
Cancel
</button>
<button onClick={onConfirm} className="btn btn-danger bg-red-500 text-white px-4 py-2 rounded shadow-md hover:bg-red-600 focus:outline-none ml-2">
Confirm
</button>
</div>
);
};
export default DeleteConfirmation;78. 객체의 문제점 & 함수 의존성
이번 시간에는 useEffect 훅에서 함수 속성을 의존성으로 추가할 때 발생할 수 있는 문제를 다루겠습니다.
특히, 함수가 매번 재생성되면서 무한 루프를 일으킬 수 있는 문제에 대해 설명하고 해결 방법을 제시합니다.
useEffect에서 함수 속성을 의존성으로 추가할 때의 문제
의존성 배열에 함수 추가:
useEffect훅에서 속성이나 상태값을 사용한다면 이를 의존성 배열에 추가해야 합니다.- 함수도 값으로 간주되기 때문에 의존성 배열에 추가할 수 있습니다.
문제점:
- 함수는 자바스크립트에서 객체로 취급됩니다.
- 컴포넌트가 다시 렌더링될 때마다 함수는 새로 생성되기 때문에 매번 새로운 객체가 됩니다.
- 이로 인해
useEffect훅이 함수가 변경되었다고 판단하고 다시 실행됩니다.
무한 루프 발생 가능성:
- 함수가 매번 재생성되면,
useEffect훅이 계속해서 실행되면서 무한 루프가 발생할 수 있습니다. - 예를 들어, 상태 업데이트 함수가 실행되면서 컴포넌트가 다시 렌더링되고, 이로 인해 새로운 함수가 생성되어
useEffect가 다시 실행됩니다.
- 함수가 매번 재생성되면,
79. useCallback
위에서 useEffect에서 함수를 의존성으로 추가할 때 발생할 수 있는 문제를 다루었습니다.
특히, 함수가 매번 재생성되면서 무한 루프를 일으킬 수 있는 문제에 대해 설명했습니다.
이번엔 이러한 문제를 해결하기 위해 useCallback 훅을 사용하는 방법을 설명하겠습니다.
useCallback 훅
useCallback 훅은 React에서 제공하는 특수한 훅으로, 함수가 재생성되지 않도록 메모리에 저장하여 필요할 때마다 동일한 함수를 반환합니다.
이를 통해 함수가 불필요하게 재생성되는 것을 방지할 수 있습니다.
80. useEffect의 Cleanup 함수: 다른 예시
이번에는 삭제 확인 모달에 타이머를 사용자에게 알리는 진행 표시줄(progress bar)을 추가하는 방법을 설명하겠습니다.
이 진행 표시줄은 남은 시간을 표시하여 타이머가 설정되어 있다는 것과 타이머가 만료되면 장소가 삭제된다는 것을 사용자에게 알리는 역할을 합니다.
이를 위해 DeleteConfirmation 컴포넌트를 수정하고, setInterval과 useEffect를 사용하여 상태를 주기적으로 업데이트합니다.
81. State(상태) 업데이트 최적화
위에서 setInterval을 올바르게 사용하기 위해 useEffect가 필요한 이유와 인터벌을 제거하는 것이 앱의 성능에 미치는 영향을 방지하기 위함을 설명했습니다.
이번에는 최적화를 위해 상태 관리와 useEffect 훅을 새로운 ProgressBar 컴포넌트로 분리하는 방법을 설명합니다.
이를 통해 불필요한 재렌더링을 방지하고 성능을 최적화할 수 있습니다.
82. react app 예시 (quiz 앱)
React.StrictMode + useRef 활용 // setTimeout 로직 side effect 방지
83. 선택된 답변 강조 & 추가 State(상태) 관리
84. 리팩토링 및 버그 수정 및 결과 출력
85. 리액트 최적화 기술
이번 섹션에서는 리액트(React)의 심화 내용을 다룰 예정입니다.
이번 섹션에서는 리액트의 내부 작동 원리를 이해하여 더 정확하고 최적화된 코드를 작성하는 방법을 배우게 됩니다.
주요 내용은 다음과 같습니다:
리액트의
DOM업데이트 방법:- 리액트가 실제
DOM(Document Object Model)을 어떻게 업데이트하는지 이해합니다. - 효율적인
DOM업데이트를 통해 성능을 최적화하는 방법을 배웁니다.
- 리액트가 실제
컴포넌트 함수 실행 방법:
- 리액트가 컴포넌트 함수를 어떻게 실행하는지 알아봅니다.
- 불필요한 업데이트를 회피하기 위한 최적화 기법을 살펴봅니다.
키(
key)에 대해 자세히 알아보기:- 리액트에서 목록 항목에 키를 추가해야 하는 이유와 그 중요성을 이해합니다.
- 키를 사용하여 목록을 효율적으로 렌더링하는 방법을 배웁니다.
상태(
State)에 대한 심도 있는 이해:- 리액트의 상태 관리 방법과 상태 업데이트의 원리를 이해합니다.
- 리액트가 상태 업데이트를 어떻게 계획하고 조율하는지 알아봅니다.
- 여러 상태 업데이트를 함께 묶어서 처리하는 방법을 배웁니다.
세부 내용
리액트의 DOM 업데이트
리액트는 가상 DOM(Virtual DOM)을 사용하여 실제 DOM 조작을 최소화합니다.
가상 DOM은 리액트가 메모리 내에서 유지하는 가벼운 사본으로, 변경 사항을 적용하기 전에 비교(diffing) 작업을 수행하여 최소한의 업데이트만 실제 DOM에 반영합니다.
이렇게 하면 브라우저 성능이 크게 향상됩니다.
컴포넌트 함수 실행
리액트 컴포넌트는 함수형 컴포넌트와 클래스형 컴포넌트로 나뉩니다.
리액트는 상태(state)나 props(속성)가 변경되면 해당 컴포넌트를 다시 렌더링합니다.
이 과정에서 불필요한 재렌더링을 방지하기 위해 React.memo와 같은 최적화 기법을 사용할 수 있습니다.
키(key)의 중요성
키는 리액트가 각 목록 항목을 식별하는 데 사용됩니다.
키를 사용하면 목록이 변경될 때 어떤 항목이 추가, 삭제, 또는 변경되었는지 효율적으로 감지할 수 있습니다.
키가 없으면 리액트는 불필요한 업데이트를 수행하게 되어 성능이 저하될 수 있습니다.
상태 관리와 업데이트
리액트의 상태는 컴포넌트의 데이터나 UI를 제어합니다.
상태 업데이트는 비동기적으로 처리되며, 리액트는 여러 상태 업데이트를 하나로 묶어서 처리하여 성능을 최적화합니다.useState, useReducer와 같은 훅을 사용하여 상태를 관리하고, useEffect를 사용하여 상태 변화에 따른 부수 효과(side effects)를 처리합니다.
86. 리액트의 컴포넌트 트리 생성 / 리액트가 시스템 뒷편에서 동작하는 방식
87. memo()로 컴포넌트 함수 실행 방지
리액트 애플리케이션을 최적화하기 위해 memo 함수를 사용하여 불필요한 컴포넌트 렌더링을 방지하는 방법에 대해 설명하겠습니다.
문제 설명
현재 애플리케이션에서는 입력 필드에 문자를 입력할 때마다 App 컴포넌트의 상태가 업데이트됩니다.
이로 인해 App의 모든 자식 컴포넌트가 다시 렌더링됩니다.
이는 최적화되지 않은 상태로, 불필요한 컴포넌트 렌더링이 발생합니다.
문제 해결 방법: memo 함수 사용
리액트는 memo 함수를 제공하여 컴포넌트가 불필요하게 다시 렌더링되는 것을 방지할 수 있습니다.memo 함수는 컴포넌트의 속성을 비교하고, 속성이 변경되지 않았으면 컴포넌트를 다시 렌더링하지 않습니다.
88. 컴포넌트 함수 실행을 방지를 위한 구조
불필요한 렌더링을 방지하는 또 다른 강력한 방법은 컴포넌트 구조를 현명하게 구성하는 것입니다.
이제 ConfigureCounter라는 새로운 컴포넌트를 추가하여 App 컴포넌트의 상태 관리와 입력 처리 로직을 분리해보겠습니다.
이를 통해 App 컴포넌트의 불필요한 렌더링을 방지하고, 더 효율적인 컴포넌트 구조를 만들 수 있습니다.
문제 상황
현재 App 컴포넌트에서 입력 필드에 값을 입력할 때마다 상태가 업데이트되고, 이로 인해 App의 모든 자식 컴포넌트가 다시 렌더링됩니다.
이는 불필요한 렌더링을 초래합니다.
해결 방법: 컴포넌트 분리와 상태 끌어올리기
- 입력 필드와 상태 관리를
ConfigureCounter라는 새로운 컴포넌트로 분리합니다. ConfigureCounter컴포넌트는 입력된 숫자를App컴포넌트로 전달하여 상태를 업데이트합니다.App컴포넌트는 이 상태를Counter컴포넌트에 전달합니다.
89. useCallback() 훅 이해하기
memo와 useCallback을 사용하여 리액트 애플리케이션에서 불필요한 렌더링을 방지하는 방법을 단계별로 설명하겠습니다.
문제 설명
현재 Counter 컴포넌트에서 버튼을 클릭할 때마다 모든 자식 컴포넌트(CounterOutput, IconButton)가 다시 렌더링됩니다.
하지만, 버튼의 경우 클릭할 때마다 실제로 변화하는 것은 CounterOutput 컴포넌트뿐입니다.
따라서 IconButton 컴포넌트가 불필요하게 다시 렌더링되지 않도록 최적화할 수 있습니다.
해결 방법: memo와 useCallback 사용
IconButton컴포넌트를memo로 감싸서 불필요한 렌더링을 방지합니다.Counter컴포넌트 내에서 버튼 클릭 핸들러 함수를useCallback으로 감싸서 재생성을 방지합니다.
Component Tree
App
Header
ConfigureCounter // 상태값 // 상태값 변화
Counter // 상태값
CounterOutput
IconButton // Counter 컴포넌트 상태값 변화 // useCallback -> handleIncrement
IconButton // Counter 컴포넌트 상태값 변화 // useCallback -> handleDecrement90. useMemo() 훅 이해하기
리액트 애플리케이션에서 useMemo를 사용하여 컴포넌트 내의 특정 함수 실행을 최적화하는 방법을 설명하겠습니다.useMemo는 복잡한 계산을 수행하는 함수의 실행을 방지하여 성능을 최적화할 수 있습니다.
문제 설명
현재 Counter 컴포넌트는 isPrime이라는 함수를 사용하여 숫자가 소수인지 여부를 계산합니다.
이 함수는 Counter 컴포넌트가 다시 렌더링될 때마다 실행됩니다.
이는 숫자가 클 경우 성능에 영향을 줄 수 있습니다.
따라서 isPrime 함수의 불필요한 실행을 방지하기 위해 useMemo를 사용할 수 있습니다.
해결 방법: useMemo 사용
isPrime함수의 실행을useMemo로 감싸서, 입력값이 변경될 때만 실행되도록 합니다.useMemo는 의존성 배열을 사용하여, 특정 값이 변경될 때만 함수를 다시 실행합니다.
91. 리액트의 가상 DOM 사용 - 직접 살펴보기
리액트 컴포넌트 함수 실행 및 DOM 업데이트 과정
리액트에서 컴포넌트 함수가 실행되고 JSX 코드가 반환될 때, 이 코드가 어떻게 실제로 화면에 나타나는지 이해하는 것은 매우 중요합니다.
다음은 이 과정에 대한 자세한 설명입니다.
1. 컴포넌트 함수 실행과 JSX 코드 반환
리액트 컴포넌트 함수가 실행되면 JSX 코드를 반환합니다.
이 JSX 코드는 컴포넌트 트리를 형성하며, 최종적으로는 네이티브 HTML 요소들로 변환됩니다.
2. 초기 렌더링과 DOM 삽입
초기 렌더링:
- 리액트 애플리케이션이 처음 시작되면, 전체 컴포넌트 트리가 생성되고, 각 컴포넌트는
JSX코드를 반환합니다.
이JSX코드는 네이티브HTML요소로 변환됩니다.
- 리액트 애플리케이션이 처음 시작되면, 전체 컴포넌트 트리가 생성되고, 각 컴포넌트는
index.html파일:- 페이지를 새로고침하거나 처음 방문할 때,
index.html파일이 로드됩니다.
이 파일에는 기본적으로 빈<div>요소와 리액트 애플리케이션을 로드하는<script>태그만 포함되어 있습니다.
- 페이지를 새로고침하거나 처음 방문할 때,
컴포넌트 트리와
HTML코드 삽입:- 리액트는 컴포넌트 트리를 생성하고, 생성된
HTML코드를DOM에 삽입합니다.
이를 통해 완전한DOM트리가 생성됩니다.
- 리액트는 컴포넌트 트리를 생성하고, 생성된
3. 리액트의 가상 DOM
리액트는 실제 DOM 대신 가상 DOM을 사용하여 성능을 최적화합니다.가상 DOM은 메모리 내에 존재하며, 실제 DOM보다 빠르게 작동합니다.
가상
DOM스냅샷:- 컴포넌트 트리가 생성될 때, 리액트는
가상 DOM 스냅샷을 생성합니다.
이 스냅샷은 실제DOM이 어떻게 보여야 하는지를 가상으로 나타낸 것입니다.
- 컴포넌트 트리가 생성될 때, 리액트는
비교와 업데이트:
- 리액트는 새로운
가상 DOM 스냅샷을 이전의가상 DOM 스냅샷과 비교합니다.
이 비교를 통해 변경된 부분을 빠르게 찾아냅니다.
실제DOM에는 오직 변경된 부분만 업데이트합니다.
- 리액트는 새로운
4. 업데이트 과정
상태 변경과 재렌더링:
- 상태가 변경되면 해당 컴포넌트 함수가 다시 실행되어 새로운
JSX코드를 반환합니다. - 리액트는 새로운
가상 DOM 스냅샷을 생성합니다.
- 상태가 변경되면 해당 컴포넌트 함수가 다시 실행되어 새로운
가상 DOM비교:- 리액트는 새로운
가상 DOM 스냅샷을이전 스냅샷과 비교하여 변경된 부분을 찾습니다.
- 리액트는 새로운
실제
DOM업데이트:- 리액트는 변경된 부분만 실제
DOM에 적용합니다.
- 리액트는 변경된 부분만 실제
5. 효율적인 DOM 업데이트
리액트는 매번 전체 DOM을 업데이트하지 않고, 변경된 부분만을 업데이트합니다.
이는 성능을 최적화하고, 불필요한 작업을 줄이는 데 도움이 됩니다.
import React, { useState, useCallback, useMemo } from 'react';
import CounterOutput from './CounterOutput';
import IconButton from './IconButton';
interface CounterProps {
initialCount: number;
}
const isPrime = (num: number): boolean => {
console.log(`isPrime called with ${num}`);
if (num < 2) return false;
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
};
const Counter: React.FC<CounterProps> = ({ initialCount }) => {
const [count, setCount] = useState(initialCount);
const handleIncrement = useCallback(() => {
setCount(count + 1);
}, [count]);
const handleDecrement = useCallback(() => {
setCount(count - 1);
}, [count]);
const primeStatus = useMemo(() => {
return isPrime(count);
}, [count]);
return (
<div className="flex flex-col items-center space-y-4">
<CounterOutput count={count} />
<p>{count} is {primeStatus ? 'a prime number' : 'not a prime number'}</p>
<div className="flex space-x-2">
<IconButton onClick={handleIncrement} label="Increment" />
<IconButton onClick={handleDecrement} label="Decrement" />
</div>
</div>
);
};
export default Counter;- 이 예제에서:
Counter컴포넌트는isPrime함수를useMemo로 감싸서,count가 변경될 때만 실행되도록 합니다.IconButton컴포넌트는memo로 감싸서 불필요한 재렌더링을 방지합니다.
리액트는 변경된 부분만 실제 DOM에 업데이트하여 성능을 최적화합니다.
92. State(상태)를 관리할 때 Key(키)의 역할
리액트의 상태 관리와 키(key) 사용
리액트에서 상태(state)는 특정 컴포넌트 내에서만 존재하며, 컴포넌트 인스턴스마다 고유한 상태를 가집니다.
이를 이해하기 위해 Counter 컴포넌트를 확장하여 CounterHistory를 추가하고 상태가 어떻게 동작하는지 자세히 살펴보겠습니다.
상태는 컴포넌트에 종속적이다
각 컴포넌트 인스턴스는 고유한 상태를 가지며, 같은 컴포넌트라도 각각의 인스턴스는 독립적인 상태를 갖습니다.
예를 들어, 두 개의 Counter 컴포넌트가 있으면 각 컴포넌트는 자신의 count 상태를 갖습니다.
컴포넌트와 위치
리액트는 컴포넌트의 타입(type)과 위치(position)에 따라 상태를 추적합니다.
컴포넌트의 위치가 변경되면 상태도 함께 이동할 수 있습니다.
이를 방지하기 위해 목록에서 컴포넌트를 사용할 때는 고유한 키(key)를 설정해야 합니다.
예제: Counter와 CounterHistory 컴포넌트
93. Key(키)가 중요한 이유 부가 설명
리액트에서 목록을 렌더링할 때 키(key)를 사용하는 것은 매우 중요합니다.
올바른 키를 사용하면 상태가 건너뛰는 것을 방지할 뿐만 아니라, 리액트가 목록을 더 효율적으로 업데이트할 수 있게 도와줍니다.
이를 이해하기 위해, 인덱스를 키로 사용하는 경우와 고유한 아이디를 키로 사용하는 경우를 비교해 보겠습니다.
인덱스를 키로 사용하는 경우의 문제점
리액트는 키를 사용하여 각 컴포넌트 인스턴스를 식별하고 추적합니다.
인덱스를 키로 사용하면 다음과 같은 문제가 발생할 수 있습니다:
불필요한 재렌더링:
- 인덱스를 키로 사용하면, 목록에 새로운 항목이 추가될 때 모든 항목이 다시 렌더링됩니다.
- 이는 모든 항목이
DOM에서 깜빡이는 것으로 나타납니다. 이는 리액트가 모든 항목을 새로 생성하고 이전 항목을 버리기 때문입니다.
상태 유지 문제:
- 인덱스를 키로 사용하면, 상태가 올바르게 유지되지 않을 수 있습니다.
- 새로운 항목이 추가되면, 기존 항목의 인덱스가 변경되어 상태가 엉뚱한 항목에 할당될 수 있습니다.
고유한 아이디를 키로 사용하는 경우의 장점
고유한 아이디를 키로 사용하면 다음과 같은 장점이 있습니다:
효율적인 업데이트:
- 리액트는 키를 사용하여 항목을 정확하게 추적하고, 변경된 항목만 업데이트합니다.
- 이는 목록에서 변경된 부분만 업데이트되므로, 불필요한 재렌더링을 방지합니다.
정확한 상태 유지:
- 고유한 아이디를 사용하면, 각 항목의 상태가 정확하게 유지됩니다.
- 항목의 위치가 변경되더라도, 상태가 올바르게 유지됩니다.
예제: 인덱스를 키로 사용하는 경우
상태값이 이상함
예제: 고유한 아이디를 키로 사용하는 경우
상태값 제대로 유지됨
94. Key(키)를 사용한 컴포넌트 초기화
리액트에서 키(key) 사용의 중요성: 상태 재설정 방법
리액트에서 키(key)를 사용하는 것은 목록뿐만 아니라 개별 컴포넌트에서도 매우 중요한 역할을 합니다.
키를 사용하면 컴포넌트의 상태를 초기화하거나 재설정할 수 있습니다.
이를 이해하기 위해, Counter 컴포넌트를 수정하여 키를 사용하는 방법을 살펴보겠습니다.
문제 설명
현재 Counter 컴포넌트는 initialCount prop을 받아 상태를 설정하지만, initialCount가 변경되어도 상태가 자동으로 업데이트되지 않습니다.
(이 부분을 예시 코드에선 useEffect로 해결한 상태입)
이는 useState 훅이 처음 렌더링될 때만 초기 값을 설정하기 때문입니다.
해결 방법: 키를 사용하여 상태 재설정
useEffect를 사용하여 상태를 초기화할 수 있지만, 이는 추가적인 렌더링을 유발하여 비효율적입니다.- 키(
key)를 사용하면 컴포넌트가 재생성되도록 하여 상태를 초기화할 수 있습니다.
예제 코드
95. State(상태) 스케줄링 & 배칭
리액트에서 상태(state) 업데이트는 즉시 실행되지 않으며, 리액트가 업데이트 일정을 조정하여 효율적으로 컴포넌트를 렌더링합니다.
이를 이해하기 위해 리액트 상태 업데이트의 실행 일정을 관리하는 방법과 함수 형태의 상태 업데이트 사용법에 대해 설명하겠습니다.
상태 업데이트의 실행 일정
리액트에서 상태 업데이트 함수를 호출하면, 업데이트는 즉시 실행되지 않습니다.
대신, 리액트는 상태 업데이트를 스케줄에 따라 처리합니다.
이는 다음과 같은 이유로 중요합니다:
- 즉각적인 업데이트가 아님: 상태 업데이트 함수를 호출한 직후에 상태를 확인하면, 이전 상태가 반환됩니다.
- 새로운 상태 반영: 새로운 상태는 리액트가 컴포넌트 함수를 다시 실행할 때 반영됩니다.
- 최신 상태 보장: 여러 상태 업데이트가 동시에 발생하면, 리액트는 최신 상태를 보장합니다.
함수 형태의 상태 업데이트
이전 상태 값을 기반으로 새로운 상태를 설정해야 할 때는 함수 형태의 상태 업데이트를 사용하는 것이 좋습니다.
이 방법은 리액트가 최신 상태 스냅샷을 제공하므로 여러 상태 업데이트가 동시에 발생하더라도 올바른 결과를 보장합니다.
import React, { useState } from 'react';
const App: React.FC = () => {
const [chosenCount, setChosenCount] = useState(0);
const handleSetCount = (newCount: number) => {
setChosenCount(newCount);
};
const incrementChosenCount = () => {
// 상태 업데이트가 즉시 반영되지 않음
setChosenCount((prevCount) => prevCount + 1);
// 이 시점에서 chosenCount는 여전히 이전 값
console.log('Immediately after setChosenCount:', chosenCount);
};
return (
<div className="container mx-auto p-4">
<header className="text-center my-4">
<h1 className="text-3xl font-bold">My Counter App</h1>
</header>
<div className="mb-4">
<input
type="number"
value={chosenCount}
onChange={(e) => handleSetCount(parseInt(e.target.value) || 0)}
className="border p-2"
/>
<button
onClick={() => handleSetCount(chosenCount)}
className="ml-2 bg-blue-500 text-white px-4 py-2 rounded"
>
설정
</button>
<button
onClick={incrementChosenCount}
className="ml-2 bg-green-500 text-white px-4 py-2 rounded"
>
+1 증가
</button>
</div>
<Counter key={chosenCount} initialCount={chosenCount} />
</div>
);
};
export default App;설명
상태 업데이트 함수의 호출:
setChosenCount함수를 호출하면 상태 업데이트는 즉시 실행되지 않습니다.console.log를 통해 상태를 확인하면 이전 상태가 출력됩니다.
함수 형태의 상태 업데이트:
- 함수 형태의 상태 업데이트를 사용하여 이전 상태 값을 기반으로 새로운 상태를 설정합니다.
setChosenCount((prevCount) => prevCount + 1)과 같은 형태를 사용하면 리액트가 최신 상태 스냅샷을 제공하여 올바른 결과를 보장합니다.
상태 배칭(
Batching):- 리액트는 동일한 함수 내에서 여러 상태 업데이트가 있을 때 이를 배칭하여 처리합니다.
- 배칭을 통해 여러 상태 업데이트가 동시에 발생해도 컴포넌트가 한 번만 렌더링됩니다.
배칭의 예시
여러 상태 업데이트가 있을 때, 리액트는 이를 배칭하여 한 번의 컴포넌트 렌더링을 유도합니다.
이는 성능을 최적화하고 불필요한 렌더링을 방지합니다.
const handleMultipleUpdates = () => {
setChosenCount((prevCount) => prevCount + 1);
setChosenCount((prevCount) => prevCount + 2);
setChosenCount((prevCount) => prevCount + 3);
// 이 시점에서 chosenCount는 이전 값이지만, 리액트는 모든 업데이트를 배칭하여 한 번의 렌더링만 유도합니다.
};이와 같이 리액트는 상태 업데이트의 실행 일정을 조정하고, 배칭을 통해 효율적인 렌더링을 보장합니다.
이를 통해 컴포넌트는 최신 상태를 기반으로 올바르게 동작하며, 불필요한 렌더링을 방지하여 성능을 최적화할 수 있습니다.
96. MillionJS로 리액트 최적화하기
Million.js 패키지: 리액트 앱의 성능 최적화
리액트 애플리케이션의 성능을 최적화하는 한 가지 방법은 Million.js라는 패키지를 사용하는 것입니다.
이 패키지는 리액트의 가상 DOM 매커니즘을 대체하여 더 빠르고 효율적인 DOM 업데이트를 제공합니다.Million.js를 사용하면 리액트 앱의 성능을 최대 **70%**까지 개선할 수 있다고 합니다.
Million.js 설치 및 설정
Million.js설치:Million.js를 프로젝트에 추가하려면 개발 서버를 종료하고, 다음 명령어를 실행하여 패키지를 설치합니다.npm install million
Vite설정 파일 수정:Vite기반 프로젝트에서Million.js를 사용하려면vite.config.js파일을 수정해야 합니다.다음 코드를
vite.config.js파일에 추가합니다.import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import million from 'million/vite-plugin'; export default defineConfig({ plugins: [ million(), react(), ], });
개발 서버 재시작:
Vite설정 파일을 수정한 후, 개발 서버를 재시작합니다.npm run dev
에러 해결:
- 일부 컴포넌트에서
Million.js를 사용할 때 발생하는 에러를 무시할 수 있습니다. 예를 들어, 특정 컴포넌트에 대해Million.js를 제외하려면 주석을 추가합니다.
// @million-exclude const MyComponent = () => { // 컴포넌트 코드 };- 일부 컴포넌트에서
예시 코드
97. 컴포넌트를 구축하는 다른 방법 - 클래스 컴포넌트
리액트에서 클래스 기반 컴포넌트 이해하기
리액트에서 클래스 기반 컴포넌트를 만드는 방법에 대해 알아보겠습니다.
지금까지 우리는 함수형 컴포넌트를 주로 사용해왔지만, 클래스 기반 컴포넌트도 존재합니다.
이 방법도 한 번 알아두면 좋겠죠?
왜 클래스 기반 컴포넌트를 배울까요?
- 과거의 코드:
- 클래스 기반 컴포넌트는 리액트의 과거 버전에서 많이 사용되었습니다.
- 따라서, 여러분이 기존 프로젝트나 서드 파티 라이브러리를 다룰 때 이 방식을 볼 수 있습니다.
- 오류 경계(Error Boundary):
- 리액트에서 오류 경계를 만들기 위해서는 클래스 기반 컴포넌트가 필요합니다.
- 오류 경계는 UI에서 발생하는 예기치 못한 오류를 잡아내어 애플리케이션이 중단되지 않도록 합니다.
클래스 기반 컴포넌트란?
함수형 컴포넌트와 달리, 클래스 기반 컴포넌트는 ES6 클래스 문법을 사용하여 정의합니다.
리액트의 Component 클래스를 상속받아야 하며, render 메서드를 통해 JSX를 반환합니다.
클래스 기반 컴포넌트 예제
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
<div>
<h1>Hello, this is a class-based component!</h1>
</div>
);
}
}
export default MyComponent;클래스 기반 컴포넌트의 주요 요소
- 상속: 클래스는
React.Component를 상속받습니다. render메서드: 클래스 기반 컴포넌트는 반드시render메서드를 가지고 있어야 하며, 이 메서드는JSX를 반환합니다.- 상태 관리: 클래스 컴포넌트에서는
this.state를 통해 상태를 관리할 수 있습니다. - 생명주기 메서드: 컴포넌트가 마운트, 업데이트, 언마운트 될 때 특정 작업을 수행하기 위해 다양한 생명주기 메서드를 사용할 수 있습니다.
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<h1>Count: {this.state.count}</h1>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
export default MyComponent;생명주기 메서드
클래스 컴포넌트는 다양한 생명주기 메서드를 제공합니다.
예를 들어, 컴포넌트가 DOM에 처음 마운트될 때 componentDidMount 메서드를 사용할 수 있습니다.
import React, { Component } from 'react';
class MyComponent extends Component {
componentDidMount() {
console.log('Component has mounted');
}
render() {
return (
<div>
<h1>Hello, this is a class-based component with lifecycle methods!</h1>
</div>
);
}
}
export default MyComponent;오류 경계 (Error Boundaries)
오류 경계는 컴포넌트 트리에서 예기치 못한 오류가 발생했을 때 이를 잡아내는 역할을 합니다.
오류 경계는 클래스 기반 컴포넌트로만 만들 수 있습니다.
import React, { Component } from 'react'; // React와 Component를 import하여 클래스 기반 컴포넌트를 정의할 수 있게 합니다.
class ErrorBoundary extends Component { // ErrorBoundary라는 이름의 클래스 컴포넌트를 정의합니다. Component를 상속받습니다.
constructor(props) { // 컴포넌트의 생성자 함수입니다. props를 인자로 받습니다.
super(props); // 부모 클래스의 생성자를 호출하여 props를 초기화합니다.
this.state = { hasError: false }; // 컴포넌트의 상태를 초기화합니다. hasError는 오류 발생 여부를 나타냅니다.
}
static getDerivedStateFromError(error) { // 오류가 발생했을 때 상태를 업데이트하기 위한 정적 메서드입니다.
// 이 메서드는 컴포넌트가 오류를 만났을 때 호출됩니다.
return { hasError: true }; // hasError 상태를 true로 설정하여 오류가 발생했음을 나타냅니다.
}
componentDidCatch(error, errorInfo) { // 컴포넌트 트리에서 발생한 오류를 잡아내는 메서드입니다.
console.log('Error caught:', error, errorInfo); // 콘솔에 오류 정보와 추가 정보를 출력합니다.
}
render() { // 컴포넌트의 UI를 정의하는 메서드입니다.
if (this.state.hasError) { // hasError 상태가 true인 경우, 오류 메시지를 렌더링합니다.
return <h1>Something went wrong.</h1>; // 오류가 발생했을 때 보여줄 메시지입니다.
}
return this.props.children; // 오류가 발생하지 않았을 경우, 자식 컴포넌트를 렌더링합니다.
}
}
export default ErrorBoundary; // ErrorBoundary 컴포넌트를 다른 모듈에서 사용할 수 있도록 내보냅니다.getDerivedStateFromError, componentDidCatch
getDerivedStateFromError와 componentDidCatch는 리액트에서 자동으로 호출되는 메서드입니다.
리액트에서 컴포넌트 트리 내에서 오류가 발생하면 이 두 메서드가 자동으로 호출됩니다.
getDerivedStateFromError- 용도: 이 정적 메서드는 오류가 발생했을 때 컴포넌트의 상태를 업데이트하기 위해 사용됩니다.
- 호출 시점: 리액트 렌더링 과정 중에 오류가 발생하면 자동으로 호출됩니다.
- 역할: 이 메서드는 새로운 상태 객체를 반환하여 오류가 발생했음을 나타낼 수 있습니다. 주로 오류 표시를 위한 상태 업데이트에 사용됩니다.
componentDidCatch- 용도: 이 메서드는 컴포넌트 내에서 발생한 오류를 잡아내고 추가 작업을 수행하기 위해 사용됩니다.
- 호출 시점: 컴포넌트가 마운트된 후 렌더링 과정에서 오류가 발생하면 자동으로 호출됩니다.
- 역할: 이 메서드는 오류를 기록(
log)하거나 오류를 분석할 수 있는 곳으로 보내기 위해 사용됩니다.
getDerivedStateFromError, componentDidCatch 호출 순서
getDerivedStateFromError 메서드는 componentDidCatch 메서드보다 먼저 호출됩니다.
두 메서드의 호출 순서는 다음과 같습니다:
getDerivedStateFromError: 오류가 발생한 직후에 가장 먼저 호출됩니다. 이 메서드는 컴포넌트의 상태를 업데이트하여 오류가 발생했음을 나타내기 위해 사용됩니다.componentDidCatch:getDerivedStateFromError가 호출된 후에 호출됩니다. 이 메서드는 실제로 오류를 처리하고, 로깅하거나 오류 정보를 외부 서비스에 보낼 때 사용됩니다.
결론
클래스 기반 컴포넌트는 과거 리액트 코드에서 많이 사용되었고, 특정 기능 (예: 오류 경계)을 구현하기 위해 여전히 필요합니다.
함수형 컴포넌트가 주로 사용되는 요즘에도 클래스 기반 컴포넌트를 이해하고 있다면 기존 프로젝트를 다루거나 리액트의 동작 원리를 더 깊이 이해하는 데 도움이 될 것입니다.
98. 리액트 컴포넌트: 함수형 컴포넌트와 클래스 기반 컴포넌트
리액트 컴포넌트를 만들 때 두 가지 방법이 있습니다.
함수형 컴포넌트와 클래스 기반 컴포넌트입니다.
여기서는 이 두 가지 방법을 쉽게 설명하겠습니다.
함수형 컴포넌트
함수형 컴포넌트는 자바스크립트 함수입니다.
이 함수는 props를 받아 JSX를 반환합니다.
우리가 지금까지 사용해 온 방식입니다.
// 함수형 컴포넌트 예제
import React from 'react';
const MyComponent = (props) => {
return (
<div>
<h1>Hello, {props.name}!</h1>
</div>
);
};
export default MyComponent;- 특징: 함수형 컴포넌트는 단순히 자바스크립트 함수입니다.
- 렌더링: 이 함수는
JSX를 반환하여 리액트가 이를 렌더링합니다.
클래스 기반 컴포넌트
클래스 기반 컴포넌트는 ES6 클래스 문법을 사용하여 정의합니다.
리액트에서 제공하는 Component 클래스를 상속받아야 합니다.
// 클래스 기반 컴포넌트 예제
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
<div>
<h1>Hello, {this.props.name}!</h1>
</div>
);
}
}
export default MyComponent;- 특징: 클래스 기반 컴포넌트는
ES6클래스입니다. - 렌더 메서드:
render메서드를 정의하여JSX를 반환해야 합니다.
왜 두 가지 방법이 있을까요?
과거 리액트에서는 상태 관리와 사이드 이펙트를 다루기 위해 클래스 기반 컴포넌트를 사용해야 했습니다.
함수형 컴포넌트는 이러한 기능을 지원하지 않았습니다.
리액트 16.8 이전
- 상태 관리: 클래스 컴포넌트만 가능
- 사이드 이펙트: 클래스 컴포넌트만 가능
리액트 16.8 이후
리액트 훅이 도입되면서 함수형 컴포넌트에서도 상태 관리와 사이드 이펙트를 다룰 수 있게 되었습니다.
- 리액트 훅:
useState,useEffect등이 도입되었습니다. - 훅 사용 불가: 훅은 클래스 컴포넌트에서는 사용할 수 없습니다.
// 함수형 컴포넌트에서 훅 사용 예제
import React, { useState, useEffect } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};
export default MyComponent;함수형 컴포넌트와 클래스 기반 컴포넌트의 차이점
정의 방식
- 함수형 컴포넌트: 자바스크립트 함수
- 클래스 기반 컴포넌트: ES6 클래스
상태 관리
- 함수형 컴포넌트: 리액트 훅 사용 (
useState,useEffect등) - 클래스 기반 컴포넌트:
this.state와 생명주기 메서드 사용
- 함수형 컴포넌트: 리액트 훅 사용 (
리액트 훅
- 함수형 컴포넌트: 사용 가능
- 클래스 기반 컴포넌트: 사용 불가
클래스 기반 컴포넌트를 사용하는 이유
현재 대부분의 리액트 프로젝트에서는 함수형 컴포넌트를 사용합니다.
그러나, 다음과 같은 이유로 클래스 기반 컴포넌트를 볼 수 있습니다:
- 기존 코드베이스: 오래된 코드나 프로젝트에서는 클래스 기반 컴포넌트를 사용할 수 있습니다.
- 오류 경계: 오류 경계(
Error Boundary)는 클래스 컴포넌트로만 작성할 수 있습니다.
결론
- 함수형 컴포넌트는 최신 리액트에서 기본적으로 사용됩니다.
- 클래스 기반 컴포넌트는 특정 상황에서 여전히 필요할 수 있습니다.
- 다음 강의에서는 함수 컴포넌트를 클래스 컴포넌트로 변환하는 방법과 상태 관리 방법 등을 배울 것입니다.
추가 예제: 클래스 컴포넌트로 상태 관리 및 생명주기 메서드 사용
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>Click me</button>
</div>
);
}
}
export default MyComponent;이 예제에서는 클래스 컴포넌트를 사용하여 상태를 관리하고 생명주기 메서드를 사용하여 사이드 이펙트를 처리합니다.
이렇게 함수형 컴포넌트와 클래스 컴포넌트의 차이점을 명확히 이해할 수 있습니다.
예시 코드
99. 퍼스트 클래스 기반 컴포넌트 추가하기
클래스 기반 컴포넌트 이해하기
리액트에서 함수형 컴포넌트와 클래스 기반 컴포넌트 두 가지를 모두 사용할 수 있습니다.
이번에 우리는 함수형 컴포넌트를 클래스 기반 컴포넌트로 변환하는 방법을 자세히 알아보겠습니다.
기존 함수형 컴포넌트
먼저, 현재 함수형 컴포넌트로 작성된 코드입니다.
유저 목록을 토글할 수 있는 버튼과 유저 컴포넌트가 있습니다.
// App.tsx
import React, { useState } from 'react';
import User from './components/User';
const App: React.FC = () => {
const [showUsers, setShowUsers] = useState(true);
const toggleUserHandler = () => {
setShowUsers((prevState) => !prevState);
};
return (
<div className="text-center mt-20">
<button className="bg-blue-500 text-white px-4 py-2 rounded" onClick={toggleUserHandler}>
Toggle Users
</button>
{showUsers && <User name="John Doe" />}
</div>
);
};
export default App;// User.tsx
import React from 'react';
interface UserProps {
name: string;
}
const User: React.FC<UserProps> = (props) => {
return (
<div className="mt-4 p-4 border rounded">
<h2 className="text-lg font-bold">{props.name}</h2>
</div>
);
};
export default User;클래스 기반 컴포넌트로 변환하기
이제 User 컴포넌트를 클래스 기반 컴포넌트로 변환해보겠습니다.
import React, { Component } from 'react';
interface UserProps {
name: string;
}
class User extends Component<UserProps> {
render() {
return (
<div className="mt-4 p-4 border rounded">
<h2 className="text-lg font-bold">{this.props.name}</h2>
</div>
);
}
}
export default User;클래스 기반 컴포넌트 설명
- 클래스 정의:
class User extends Component<UserProps>는ES6클래스 문법을 사용하여 클래스를 정의하고, 리액트의Component를 확장합니다. render메서드: 클래스 기반 컴포넌트는render메서드를 가지고 있으며, 이 메서드는JSX를 반환합니다.props접근: 클래스 컴포넌트에서props는this.props를 통해 접근합니다.
함수형 컴포넌트와 클래스 기반 컴포넌트의 차이
함수형 컴포넌트
- 자바스크립트 함수
props를 매개변수로 받아 JSX 반환useState,useEffect같은 리액트 훅 사용 가능
클래스 기반 컴포넌트
ES6클래스render메서드를 통해JSX반환this.props를 통해props접근this.state를 통해 상태 관리- 생명주기 메서드 (
componentDidMount,componentDidUpdate등) 사용 가능
결론
- 클래스 기반 컴포넌트는 함수형 컴포넌트와 함께 사용할 수 있습니다.
- 기존 프로젝트에서 클래스 기반 컴포넌트를 함수형 컴포넌트로 변환하거나, 새로운 프로젝트에서 둘 중 원하는 방법을 선택할 수 있습니다.
- 클래스 기반 컴포넌트는 리액트 16.8 이전에 주로 사용되었으며, 현재는 함수형 컴포넌트와 리액트 훅을 더 많이 사용합니다.
예시 코드
100. 클래스 컴포넌트에 state 및 이벤트 작업하기
클래스 컴포넌트에서 상태값이 여러개일 때
import React, { Component } from 'react';
import User from './components/User';
interface AppState {
showUsers: boolean;
}
class App extends Component<{}, AppState> {
constructor(props: {}) {
super(props);
this.state = {
showUsers: true
};
}
toggleUserHandler = () => {
this.setState((prevState) => ({
showUsers: !prevState.showUsers
}));
};
render() {
return (
<div className="text-center mt-20">
<button className="bg-blue-500 text-white px-4 py-2 rounded" onClick={this.toggleUserHandler}>
Toggle Users
</button>
{this.state.showUsers && <User name="John Doe" />}
</div>
);
}
}
export default App;클래스 컴포넌트에서 상태값이 여러 개인 경우, 상태를 업데이트할 때 반드시 전체 상태 객체를 업데이트할 필요는 없습니다.
대신, setState 메소드는 자동으로 기존 상태와 병합(merge)되기 때문에 필요한 상태값만 업데이트하면 됩니다.
이는 함수형 컴포넌트의 상태 관리 방식과 약간 다릅니다.
예시: 여러 상태값 업데이트하기
다음은 상태값이 여러 개인 클래스 컴포넌트 예제입니다.showUsers 외에 userCount 상태값도 함께 업데이트하는 경우를 살펴보겠습니다.
import React, { Component } from 'react';
import User from './components/User';
interface AppState {
showUsers: boolean;
userCount: number;
}
class App extends Component<{}, AppState> {
constructor(props: {}) {
super(props);
this.state = {
showUsers: true,
userCount: 1
};
}
toggleUserHandler = () => {
this.setState((prevState) => ({
showUsers: !prevState.showUsers
}));
};
incrementUserCount = () => {
this.setState((prevState) => ({
userCount: prevState.userCount + 1
}));
};
render() {
return (
<div className="text-center mt-20">
<button className="bg-blue-500 text-white px-4 py-2 rounded" onClick={this.toggleUserHandler}>
Toggle Users
</button>
<button className="bg-green-500 text-white px-4 py-2 rounded ml-2" onClick={this.incrementUserCount}>
Increment User Count
</button>
{this.state.showUsers && <User name={`User ${this.state.userCount}`} />}
</div>
);
}
}
export default App;예시 코드
101. 컴포넌트 수명 주기(클래스 컴포넌트에만 해당!)
클래스 컴포넌트의 생명주기 메소드 이해하기
함수형 컴포넌트에서는 useEffect 훅을 사용하여 부작용을 처리하지만, 클래스 컴포넌트에서는 생명주기 메소드를 사용합니다.
가장 중요한 생명주기 메소드 세 가지는 componentDidMount, componentDidUpdate, componentWillUnmount입니다.
componentDidMount- 용도: 컴포넌트가 처음으로 마운트될 때(즉,
DOM에 추가될 때) 호출됩니다. - 유사 기능:
useEffect의 의존성 배열이 빈 경우와 같습니다.
- 용도: 컴포넌트가 처음으로 마운트될 때(즉,
componentDidUpdate용도: 컴포넌트가 업데이트될 때(즉, 상태나props가 변경되어 재렌더링될 때) 호출됩니다. 유사 기능: 의존성 배열이 특정 값을 가진useEffect와 같습니다.componentWillUnmount용도: 컴포넌트가DOM에서 제거되기 직전에 호출됩니다. 유사 기능:useEffect의 클린업 함수와 같습니다.
예시 코드
102. 사용 중인 수명 주기 방법
- 생성자와 상태 초기화: 생성자에서
super(props)를 호출하여 부모 클래스의 생성자를 호출하고, 초기 상태를 설정합니다. 상태에는filteredUsers와searchTerm이 포함됩니다. searchChangeHandler메소드: 이 메소드는 검색 입력 필드의 변경 이벤트에 바인딩되어 검색어를 상태에 업데이트합니다.componentDidUpdate메소드: 상태 또는props가 변경될 때마다 호출되며, 이전 검색어와 현재 검색어를 비교하여filteredUsers를 업데이트합니다.componentDidMount메소드: 컴포넌트가 처음 마운트될 때 호출되며, 초기 사용자 목록을 설정합니다.render메소드:JSX를 반환하여UI를 렌더링합니다. 입력 필드와 사용자 목록을 포함하며, 입력 필드의 변경 이벤트 핸들러를 설정합니다.
103. 클래스 컴포넌트 및 컨텍스트
컨텍스트와 클래스 컴포넌트
리액트에서 컨텍스트를 사용하면 어플리케이션 전역에서 데이터를 공유할 수 있습니다.
컨텍스트를 클래스 컴포넌트와 함께 사용하는 방법을 단계별로 설명하겠습니다.
104. 클래스 컴포넌트 대 함수형 컴포넌트: 요약
지금까지 클래스 컴포넌트에 대해 학습해 보았습니다.
이제 클래스 컴포넌트의 개념과 사용 방법을 정리해보겠습니다.
또한, 왜 함수형 컴포넌트가 최근에 더 많이 사용되는지에 대해 알아보겠습니다.
클래스 컴포넌트란?
클래스 컴포넌트는 React에서 컴포넌트를 정의하는 방법 중 하나입니다.
클래스를 기반으로 하고 있으며, 상태(state)와 생명 주기 메소드(lifecycle methods)를 사용할 수 있습니다.
클래스 컴포넌트의 주요 특징
정의 방법:
Component를 상속받아 클래스를 정의합니다.render()메소드 내에서JSX를 반환합니다.
props와state:props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달합니다.state는 컴포넌트 내부에서 관리하는 동적인 데이터를 저장합니다.
생명 주기 메소드:
componentDidMount(),componentDidUpdate(),componentWillUnmount()등 다양한 생명 주기 메소드를 통해 컴포넌트의 특정 시점에 코드를 실행할 수 있습니다.
컨텍스트:
- 컨텍스트를 사용하여 컴포넌트 트리 전체에 데이터를 전달할 수 있습니다.
클래스 컴포넌트의 사용 방법
컴포넌트 정의 및 상태 관리:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return <div>{this.state.count}</div>; } }생명 주기 메소드 사용:
class MyComponent extends React.Component { componentDidMount() { // 컴포넌트가 마운트된 직후 실행 } componentDidUpdate(prevProps, prevState) { // 컴포넌트가 업데이트된 직후 실행 } componentWillUnmount() { // 컴포넌트가 언마운트되기 직전에 실행 } render() { return <div>{this.state.count}</div>; } }컨텍스트 사용:
const MyContext = React.createContext(); class MyComponent extends React.Component { static contextType = MyContext; render() { const value = this.context; return <div>{value}</div>; } }
함수형 컴포넌트의 등장과 장점
최근에는 함수형 컴포넌트가 더 많이 사용됩니다.
그 이유는 함수형 컴포넌트가 배우기 쉽고 유연성이 좋기 때문입니다.
간단한 정의:
const MyComponent = () => { return <div>Hello, World!</div>; };React훅(hooks) 사용:useState로 상태 관리useEffect로 생명 주기 메소드 대체useContext로 컨텍스트 사용const MyComponent = () => { const [count, setCount] = useState(0); useEffect(() => { // 컴포넌트가 마운트된 직후 실행 }, []); return <div>{count}</div>; };
결론
클래스 컴포넌트:
- 여전히 사용 가능하며, 잘 작동합니다.
- 상태 관리, 생명 주기 메소드, 컨텍스트 등을 다루는 방법을 배웠습니다.
- 특정 상황(오류 경계 등)에서는 클래스 컴포넌트를 사용해야 할 수도 있습니다.
함수형 컴포넌트:
- 배우기 쉽고 유연성이 높습니다.
- 최근의
React애플리케이션에서는 주로 사용됩니다. useState,useEffect,useContext등 훅을 사용하여 상태와 생명 주기, 컨텍스트를 관리합니다.
선택 기준
- 함수형 컴포넌트: 주로 사용하는 컴포넌트 유형. 유연하고 간단하며, 대부분의 상황에서 충분히 강력합니다.
- 클래스 컴포넌트: 특정 상황(예: 오류 경계)에서 필요할 때 사용. 기존 코드베이스에서 많이 사용 중이라면 계속 사용할 수 있습니다.
105. 오류 경계 소개 (Error Boundaries)
개념 소개
애플리케이션에서는 예상치 못한 오류가 발생할 수 있습니다.
개발자가 이러한 오류를 미리 예방하고 처리할 수 있는 방법이 필요합니다.
특히, 사용자 경험을 저해하지 않기 위해 전체 애플리케이션이 중단되지 않도록 하는 것이 중요합니다.
오류 경계란?
오류 경계(Error Boundaries)는 React에서 도입된 개념으로, 자식 컴포넌트 트리에서 발생하는 JavaScript 오류를 처리하고, 이러한 오류로 인해 전체 애플리케이션이 중단되지 않도록 합니다.
오류 경계는 클래스 컴포넌트로만 작성할 수 있습니다.
사용 예제
ErrorBoundary컴포넌트 작성componentDidCatch생명 주기 메소드 사용- 오류 발생 시 대체
UI표시
오류 경계 컴포넌트 작성
먼저, ErrorBoundary라는 새로운 클래스를 작성합니다.
오류 경계 사용
ErrorBoundary 컴포넌트를 작성한 후, 이를 사용하여 오류를 처리할 수 있습니다.
예를 들어, UserFinder 컴포넌트를 감싸도록 합니다.
예제: 오류 발생 시 시나리오
UserFinder 컴포넌트에서 오류를 유발하는 코드를 추가해보겠습니다.
예를 들어, 빈 배열인 경우 오류를 발생시키도록 합니다.
설명
오류 경계 (
Error Boundary)란?- 오류 경계는 자식 컴포넌트 트리에서 발생하는
JavaScript오류를 잡아내고 처리할 수 있는React컴포넌트입니다. - 오류 경계는 클래스 컴포넌트로만 작성할 수 있습니다.
- 오류 경계는 자식 컴포넌트 트리에서 발생하는
오류 경계 작성
ErrorBoundary클래스 컴포넌트를 작성하고,componentDidCatch생명 주기 메소드를 사용하여 오류를 잡아냅니다.- 오류가 발생하면
hasError상태를 true로 설정하고, 대체UI를 렌더링합니다.
오류 경계 사용
ErrorBoundary컴포넌트를 사용하여UserFinder와 같은 컴포넌트를 감싸고, 오류 발생 시 대체 UI를 표시합니다.
오류 유발
UserFinder컴포넌트에서 특정 조건 (예: 필터링된 사용자 목록이 비어 있는 경우)에 오류를 발생시킵니다.componentDidUpdate에서 필터링된 사용자 목록을 확인하고, 오류를 던집니다.
106. HTTP 요청 보내기 (예: 데이터베이스 연결)
리액트 프로젝트에서 데이터를 다루는 방식과 중앙 서버와의 연결 방법에 대해 알아보겠습니다.
이제부터는 실제 앱이 서버와 통신하는 방식에 대해 배워보겠습니다.
이를 통해 단순히 로컬 데이터만 사용하는 것이 아니라, 여러 사용자가 동시에 접근하고 상호작용할 수 있는 앱을 만들 수 있습니다.
왜 중앙 서버가 필요한가?
- 분산된 데이터 관리 문제:
- 리액트 프로젝트 내에서 데이터를 관리하면, 각 사용자마다 자신의 브라우저에서 리액트 프로젝트가 실행됩니다.
- 즉, 각 사용자가 보는 데이터는 자신의 브라우저에 저장된 로컬 데이터입니다. 이렇게 하면 다른 사용자의 변경사항을 실시간으로 반영할 수 없습니다.
- 데이터 공유와 일관성:
- 중앙 서버를 사용하면 모든 사용자가 동일한 데이터베이스에 접근할 수 있습니다.
- 이를 통해 사용자 간에 데이터가 일관되게 유지되고, 실시간으로 데이터를 공유할 수 있습니다.
중앙 서버와의 통신 방법
리액트 앱이 중앙 서버와 통신하려면 HTTP 요청을 사용해야 합니다.
이를 통해 서버에서 데이터를 가져오고, 서버로 데이터를 보낼 수 있습니다.
리액트와 서버 통신의 이점
- 실시간 데이터 업데이트: 중앙 서버를 통해 데이터를 관리하면, 여러 사용자가 실시간으로 데이터를 업데이트하고 확인할 수 있습니다.
- 데이터 일관성 유지: 중앙 서버에 데이터를 저장하면 모든 사용자가 동일한 데이터에 접근할 수 있어 데이터 일관성을 유지할 수 있습니다.
- 확장성: 서버를 사용하면 데이터가 증가하더라도 서버 성능을 향상시키거나 데이터베이스를 확장하여 대응할 수 있습니다.
107. 데이터베이스 연결/해지하는 방법
리액트 앱을 중앙 서버와 연결하는 방법에 대해 알아보겠습니다.
리액트 앱이 여러 사용자와 데이터를 공유하고 상호작용할 수 있게 하기 위해 중앙 서버나 데이터베이스가 필요합니다.
이를 통해 전 세계의 사용자가 동일한 데이터에 접근하고, 데이터를 보내고, 주문을 처리할 수 있습니다.
왜 중앙 서버가 필요한가?
중앙 서버나 데이터베이스는 여러 사용자들이 데이터를 공유하고 상호작용할 수 있게 합니다.
예를 들어, 온라인 스토어를 구축할 때 전 세계의 사용자가 동일한 상품 목록을 보고, 주문을 넣고, 가게 주인이 그 주문을 처리할 수 있어야 합니다.
이를 위해서는 중앙 서버가 필요합니다.
리액트 앱과 중앙 서버 연결
리액트 앱을 데이터베이스에 직접 연결하는 것은 좋지 않습니다.
그 이유는 보안 문제 때문입니다.
리액트 앱은 방문자의 브라우저에서 실행되기 때문에, 사용자가 코드를 볼 수 있고, 만약 그 코드에 데이터베이스 접근 정보가 포함되어 있다면, 사용자가 데이터베이스에 불법으로 접근할 수 있습니다.
안전한 연결 방법
리액트 앱과 데이터베이스를 안전하게 연결하기 위해서는 다음과 같은 방법을 사용해야 합니다:
백엔드 서버 사용:
- 백엔드 서버는 리액트 앱과 데이터베이스 사이의 중개자 역할을 합니다.
리액트 앱은 백엔드 서버에HTTP요청을 보내고, 백엔드 서버는 데이터베이스와 통신합니다.
이렇게 하면 데이터베이스 접근 정보가 사용자에게 노출되지 않습니다.
- 백엔드 서버는 리액트 앱과 데이터베이스 사이의 중개자 역할을 합니다.
HTTP요청:- 리액트 앱은 백엔드 서버에
HTTP요청을 보내 데이터를 요청하거나 저장합니다.
백엔드 서버는 이러한 요청을 처리하고, 필요한 데이터를 리액트 앱에 전달합니다.
- 리액트 앱은 백엔드 서버에
HTTP 요청 예시
데이터 가져오기 (
Fetching Data):useEffect(() => { fetch('https://api.example.com/places') .then(response => response.json()) .then(data => setPlaces(data)) .catch(error => console.error('Error fetching data:', error)); }, []);위 코드에서는 리액트 앱이 백엔드 서버에 요청을 보내 장소 데이터를 가져옵니다.
데이터 보내기 (
Sending Data):const handleSubmit = (event) => { event.preventDefault(); const newPlace = { name: placeName, location: placeLocation }; fetch('https://api.example.com/places', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newPlace), }) .then(response => response.json()) .then(data => console.log('Success:', data)) .catch(error => console.error('Error:', error)); };위 코드에서는 사용자가 입력한 데이터를 백엔드 서버로 보내어 저장합니다.
보안 관리
- 백엔드 서버에서 요청 검증: 백엔드 서버는 어떤 요청을 허용할지, 거부할지를 결정합니다. 이를 통해 사용자가 할 수 있는 것과 할 수 없는 것을 통제할 수 있습니다.
- API 엔드포인트 사용: 특정한 작업을 수행하는 URL을 통해 백엔드 서버와 통신합니다. 예를 들어, 데이터를 가져오거나 저장하는 요청을 특정 엔드포인트로 보냅니다.
기술 선택
리액트 앱은 주로 자바스크립트를 사용하여 프론트엔드를 개발하지만, 백엔드는 다양한 언어와 프레임워크를 사용할 수 있습니다.
예를 들어, PHP, C#, Node.js 등이 있습니다.
또한, Next.js와 같은 프레임워크를 사용하여 프론트엔드와 백엔드를 함께 개발할 수도 있습니다.
결론
리액트 앱을 데이터베이스와 안전하게 연결하기 위해서는 백엔드 서버를 사용해야 합니다.
백엔드 서버는 리액트 앱과 데이터베이스 사이의 중개자 역할을 하며, HTTP 요청을 통해 통신합니다.
이를 통해 데이터베이스 접근 정보를 보호하고, 사용자들이 안전하게 데이터를 주고받을 수 있게 합니다
108. 초기 프로젝트 & 임시 백엔드 API
109. 앱의 데이터 Fetching을 위한 준비
110. HTTP 요청을 보내지 않는 방법 (잘못된 이유 부가 설명)
리액트 앱에서 백엔드 서버로 데이터를 가져오기 위해 HTTP 요청을 보내는 방법을 자세히 설명해드리겠습니다.
이 과정에서는 fetch 함수를 사용하여 데이터를 가져오는 방법과, 무한 루프를 방지하기 위해 useEffect 훅을 사용하는 방법을 배웁니다.
기본적인 fetch 사용법
fetch('http://localhost:3000/places')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch places');
}
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('Error:', error);
});URL설정: 데이터를 가져올 서버의URL을 설정합니다. 예를 들어,http://localhost:3000/places는 실행 중인 백엔드 서버의places엔드포인트입니다.fetch함수 호출:fetch함수는HTTP요청을 보내고, 프로미스(Promise)를 반환합니다. 프로미스는 나중에 값을 사용할 수 있도록 보장하는 객체입니다.then메소드 사용: 프로미스가 해결되면,then메소드를 사용하여 응답을 처리할 수 있습니다. 첫 번째then메소드는 응답(Response) 객체를 받고, 두 번째then메소드는JSON데이터를 파싱하여 사용할 수 있습니다.
리액트에서 fetch 사용
리액트에서는 fetch를 사용하여 데이터를 가져올 때, 컴포넌트가 렌더링될 때마다 요청이 반복되지 않도록 해야 합니다.
이를 위해 useEffect 훅을 사용합니다.
useEffect사용:useEffect훅을 사용하여 컴포넌트가 마운트될 때 한 번만 데이터를 가져오도록 설정합니다.- 상태 관리:
useState훅을 사용하여 데이터를 상태로 관리합니다. 데이터가 로드 중일 때와 에러가 발생했을 때의 상태도 관리합니다.
리액트에서 fetch 사용 예시
import React, { useState, useEffect } from 'react';
import { Place } from '../types';
const AvailablePlaces: React.FC = () => {
const [places, setPlaces] = useState<Place[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch('/api/places')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch places');
}
return response.json();
})
.then((data) => {
setPlaces(data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div className="grid grid-cols-1 gap-4">
{places.map((place) => (
<div key={place.id} className="p-4 border rounded-md">
<h2 className="text-lg font-semibold">{place.name}</h2>
<p>{place.description}</p>
</div>
))}
</div>
);
};
export default AvailablePlaces;설명
useState:places,loading,error상태를 관리합니다. 초기 상태로places는 빈 배열,loading은true,error는null로 설정합니다.useEffect: 컴포넌트가 마운트될 때 한 번 실행됩니다.fetch를 사용하여 데이터를 가져오고, 응답을 처리합니다. 데이터를 가져오면places상태를 업데이트하고,loading상태를false로 설정합니다. 에러가 발생하면error상태를 업데이트하고,loading상태를false로 설정합니다.- 렌더링:
loading상태일 때는 "Loading..." 메시지를 표시하고,error상태일 때는 에러 메시지를 표시합니다. 데이터가 로드되면places데이터를 렌더링합니다.
요약
fetch함수로 데이터를 가져옵니다.useEffect훅을 사용하여 컴포넌트가 마운트될 때 한 번만 데이터를 가져옵니다.useState훅을 사용하여 데이터를 상태로 관리하고, 로딩 상태와 에러 상태를 처리합니다.
111. useEffect로 HTTP 요청 (GET 요청) 전송하기 + async / await 사용하기 + 로딩 State(상태) 다루기
리액트에서 useEffect를 사용하여 데이터를 가져오고 무한 루프 문제를 해결하는 방법을 단계별로 설명하겠습니다.
또한, 백엔드에서 이미지를 올바르게 가져오는 방법도 포함하겠습니다.
112. HTTP 에러 다루기
113. Fetch된 데이터 변환
114. HTTP 요청 관련 코드 추출 및 코드 구조 개선
115. POST 요청으로 데이터 전송
리액트 앱에서 장소를 선택하고, 선택한 장소를 백엔드 서버에 저장하는 과정을 자세히 설명하겠습니다.
이 과정에서는 http.ts 파일을 사용하여 장소 데이터를 가져오고 저장하는 기능을 구현하며, AvailablePlaces 컴포넌트에서 이러한 기능을 활용합니다.
116. 최적의 업데이트 방법 (낙관적 업데이트)
117. 데이터 삭제 (DELETE HTTP 요청)
118. 코드 리팩토링
119. 커스텀 리액트 훅
리액트 훅에 대해 이미 익숙하시다면, 이번 섹션에서는 사용자 정의 커스텀 훅(Custom Hooks)을 만드는 법에 대해 배워볼 차례입니다.
커스텀 훅은 리액트에서 코드 재사용성을 높이기 위해 매우 중요한 개념입니다.
시작하기 전에, 훅의 규칙을 복습하고 커스텀 훅이 왜 필요한지, 어떻게 만드는지에 대해 알아보겠습니다.
훅의 규칙
- 훅은 최상위 레벨에서만 호출해야 한다:
- 훅을 루프, 조건문, 중첩된 함수 내에서 호출하지 말고, 항상 컴포넌트의 최상위 레벨에서 호출해야 합니다.
- 훅은 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출해야 한다:
- 훅은 일반 자바스크립트 함수에서 호출할 수 없으며, 반드시 리액트 함수 컴포넌트나 다른 커스텀 훅에서만 호출해야 합니다.
커스텀 훅이 필요한 이유
커스텀 훅을 사용하면 여러 컴포넌트에서 동일한 로직을 반복하지 않고, 코드의 재사용성과 가독성을 높일 수 있습니다.
예를 들어, 데이터 fetching, 폼 핸들링, 애니메이션 등의 로직을 커스텀 훅으로 분리하여 여러 컴포넌트에서 쉽게 재사용할 수 있습니다.
커스텀 훅 만드는 방법
커스텀 훅은 일반 자바스크립트 함수처럼 작성되며, 내부에서 다른 훅을 사용할 수 있습니다.
훅의 이름은 항상 "use"로 시작해야 합니다.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
}위 예시에서 useFetch라는 커스텀 훅을 만들었습니다.
이 훅은 URL을 입력받아 데이터를 fetch하고, 데이터를 상태로 관리하며, 로딩 상태도 관리합니다.
이제 이 훅을 여러 컴포넌트에서 사용할 수 있습니다.
120. "Hooks(훅)의 규칙" 복습 & 훅을 사용하는 이유
리액트 훅의 규칙
- 리액트 컴포넌트 함수 또는 다른 훅 안에서만 훅을 사용할 수 있다:
- 훅은 리액트 컴포넌트 함수나 다른 커스텀 훅 안에서만 사용할 수 있습니다.
일반 자바스크립트 함수에서는 훅을 사용할 수 없습니다.
- 훅은 리액트 컴포넌트 함수나 다른 커스텀 훅 안에서만 사용할 수 있습니다.
- 훅은 조건문이나 루프, 중첩 함수 안에서 호출되지 않아야 한다:
- 훅은 항상 최상위 레벨에서 호출해야 합니다.
조건문, 루프, 중첩 함수 등에서 훅을 호출하면 안 됩니다.
- 훅은 항상 최상위 레벨에서 호출해야 합니다.
이 두 가지 규칙은 훅이 제대로 동작하고 상태 관리가 올바르게 이루어지기 위해 반드시 지켜져야 합니다.
커스텀 훅이란 무엇인가요?
커스텀 훅은 위의 규칙을 지키면서도 여러 컴포넌트에서 재사용 가능한 로직을 만들어내는 방법입니다.
커스텀 훅을 사용하면 컴포넌트 함수에 들어가는 코드를 보다 깔끔하고 재사용 가능하게 만들 수 있습니다.
커스텀 훅이 필요한 이유
예를 들어, HTTP 요청을 보내고 로딩 상태와 에러 상태를 관리하는 코드를 생각해 봅시다.
만약 여러 컴포넌트에서 이와 같은 로직이 필요하다면, 같은 코드를 여러 번 작성하게 됩니다.
하지만, 커스텀 훅을 사용하면 이 로직을 하나의 함수로 만들어서 재사용할 수 있습니다.
커스텀 훅 만들기
이제 실제로 커스텀 훅을 만들어보겠습니다.
예시로, 데이터를 fetch하는 커스텀 훅을 만들어보겠습니다.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}121. 커스텀 Hooks(훅) 생성하기
122. 커스텀 Hooks(훅): State(상태) 관리 & 상태 값 반환
123. 커스텀 Hooks(훅)에서 상태값 수정 함수 노출시키기
124. 다중 컴포넌트에서 커스텀 Hook(훅) 사용하기
125. 유동성 있는 커스텀 Hooks(훅) 생성하기
126. form 작업
웹 애플리케이션에서 양식(form)과 사용자 입력을 다루는 방법에 대해 배워보겠습니다.
웹 애플리케이션을 제작할 때, 특히 사용자와 상호작용하는 경우, 양식 처리가 중요한 역할을 합니다.
이번 시간엔 양식을 다루는 방법과 사용자 입력을 검증하는 방법을 자세히 알아보겠습니다.
1. 양식 제출 관리 및 사용자 입력 검증
먼저, 양식 제출을 어떻게 관리하고 사용자 입력을 검증할 수 있는지 살펴보겠습니다.
양식 제출과 검증은 다음과 같은 과정을 포함합니다:
- 양식 데이터 수집: 사용자가 입력한 데이터를 수집합니다.
- 입력 검증: 사용자가 입력한 데이터가 올바른지 확인합니다. 예를 들어, 이메일 형식이 맞는지, 비밀번호가 특정 조건을 충족하는지 등을 검증합니다.
- 오류 처리: 입력 데이터에 오류가 있을 경우 사용자에게 알려줍니다.
- 데이터 제출: 올바른 데이터는 서버로 제출합니다.
이 과정이 처음에는 복잡해 보일 수 있지만, 실제로는 논리적인 순서로 이루어집니다.
2. 브라우저의 양식 및 검증 기능 활용
브라우저에는 양식 처리와 검증을 도와주는 다양한 기능이 내장되어 있습니다.
이러한 기능들은 개발자의 업무 부담을 줄여주는데요, 예를 들어:
- HTML5 검증 속성:
<input>요소에required,type="email"등의 속성을 사용하여 기본적인 검증을 수행할 수 있습니다. - 브라우저 기본 검증: 브라우저는 사용자가 잘못된 형식의 데이터를 입력했을 때 자동으로 경고 메시지를 표시합니다.
이러한 기능들을 활용하면 기본적인 검증 작업을 쉽게 처리할 수 있습니다.
3. 리액트에서 사용자 입력과 검증 처리
리액트에서는 사용자 입력과 검증을 보다 유연하게 처리할 수 있습니다.
리액트에서는 다음과 같은 방식으로 사용자 입력을 관리할 수 있습니다:
- 상태 관리: 컴포넌트 상태를 사용하여 입력 값을 관리합니다.
- 이벤트 처리:
onChange이벤트 핸들러를 사용하여 입력이 변경될 때 상태를 업데이트합니다. - 커스텀 검증 로직: 필요한 경우 커스텀 검증 로직을 작성하여 입력 값을 검증할 수 있습니다.
127. forms 소개 및 까다로운 이유
양식을 다루는 것이 어려운 이유와 그 중요성에 대해 더 자세히 설명하도록 하겠습니다.
1. 양식의 정의와 예시
양식(form)이란 입력 필드의 집합입니다.
예를 들어, 로그인 양식에는 사용자 이름과 비밀번호를 입력하는 필드가 있습니다.
더 복잡한 예로는 가입 양식을 들 수 있습니다.
가입 양식에는 이름, 이메일, 비밀번호, 주소 등 여러 입력 필드가 포함됩니다.
양식은 입력 필드 외에도 레이블(label)과 함께 사용됩니다.
레이블은 입력 필드의 설명 역할을 합니다.
예를 들어, '이메일'이라는 레이블은 이메일을 입력하는 필드에 붙습니다.
2. 양식의 용도
양식은 주로 두 가지 용도로 사용됩니다:
- 양식 제출 관리: 사용자가 입력한 데이터를 서버로 제출합니다.
- 데이터 검증: 사용자가 제공한 데이터가 올바른지 확인합니다.
만약 사용자가 잘못된 데이터를 입력하면, 이를 검증하여 사용자에게 오류 메시지를 보여줍니다.
3. 양식 제출과 데이터 추출
양식 제출과 데이터 추출은 상대적으로 쉬운 편입니다. 몇 가지 방법이 있습니다:
- 양방향 바인딩: 입력 필드의 값을 상태(
state)로 관리합니다. 상태가 변경되면 입력 필드도 자동으로 업데이트됩니다. 이전 강의에서 배운 내용이기도 합니다. - 참조(
ref): 참조를 사용하여 입력 필드의 값을 직접 추출할 수 있습니다. - 브라우저 내장 함수: 브라우저가 제공하는 함수들을 사용하여
formData객체를 통해 입력 데이터를 추출할 수 있습니다.
4. 데이터 검증의 어려움
데이터 검증이 어려운 이유는 좋은 사용자 경험을 제공하기 위해 다양한 시나리오를 고려해야 하기 때문입니다. 다음과 같은 검증 시점을 생각해볼 수 있습니다:
- 입력 중 실시간 검증: 사용자가 키를 누를 때마다 데이터를 검증합니다. 이 방법은 실시간으로 오류를 보여줄 수 있지만, 너무 일찍 오류를 표시할 수 있다는 단점이 있습니다.
- 입력 완료 후 검증: 사용자가 입력을 마치고 다음 필드로 이동할 때 검증을 수행합니다. 이 방법은 적절한 시점에 오류를 보여줄 수 있지만, 오류가 오랫동안 남아 있을 수 있습니다.
- 양식 제출 시 검증: 양식을 제출할 때 전체 데이터를 검증합니다. 이 방법은 오류를 가장 늦게 표시할 수 있지만, 사용자 경험에 따라 적절할 수 있습니다.
각 방법에는 장단점이 있으며, 어떤 방법을 선택할지는 앱의 요구사항과 제공하고 싶은 사용자 경험에 따라 달라집니다.
5. 적절한 해결책 찾기
이 강의에서는 다양한 검증 방법을 배워보고, 각 방법의 장단점을 이해한 후, 여러분이 개발하는 앱과 사용자를 위한 적절한 해결책을 찾는 데 중점을 둘 것입니다.
이제 데모 프로젝트를 통해 이러한 개념들을 실습하면서 양식을 다루는 방법을 익혀보세요. 데모 프로젝트는 로컬 버전과 코드샌드박스 버전으로 제공되므로, 원하는 방법으로 프로젝트를 시작할 수 있습니다. 로컬 버전의 경우, 파일을 다운로드한 후 npm install과 npm run dev 명령어를 사용하여 개발 서버를 시작하면 됩니다.
이러한 과정을 통해 양식과 사용자 입력을 효과적으로 다루는 방법을 익히고, 더 나은 사용자 경험을 제공할 수 있을 것입니다.
128. form 제출 다루기
양식을 다루는 방법에 대해 자세히 설명해드리겠습니다.
이번 예제에서는 로그인 양식(Login.jsx)을 사용하여 작업할 것입니다.
먼저, 이 양식의 제출을 관리하고 각각의 값을 추출하는 방법을 배워보겠습니다.
1. 양식 제출 관리 및 값 추출
양식 제출을 관리하는 방법에는 여러 가지가 있습니다.
각각을 설명드리겠습니다.
여러분은 선호하는 방법을 선택하여 앱 개발에 사용할 수 있습니다.
2. 양식의 기본 설정
양식 요소에는 <label>과 같은 HTML 요소가 포함됩니다.
리액트에서는 HTML의 for 속성을 htmlFor로 변경해야 합니다.
이는 class 속성을 className으로 사용하는 것과 같은 이유입니다.for와 class는 자바스크립트에서 사용되는 예약어이기 때문에 속성 이름으로 사용할 수 없습니다.
3. 양식 제출 관리
로그인 양식의 제출을 관리하기 위해 로그인 버튼에 대한 처리가 필요합니다.
로그인 버튼이 눌렸을 때 실행될 함수를 정의해보겠습니다.
function Login() {
const handleSubmit = (event) => {
event.preventDefault();
console.log('Submitted!');
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input id="username" type="text" name="username" />
<label htmlFor="password">Password:</label>
<input id="password" type="password" name="password" />
<button type="submit">Login</button>
</form>
);
}위 코드에서 handleSubmit 함수는 양식이 제출될 때 호출됩니다.event.preventDefault()를 호출하여 브라우저의 기본 동작인 페이지 새로고침을 막습니다.
이로 인해 양식 제출 시 페이지가 새로고침되지 않고, console.log('Submitted!') 메시지가 로그로 출력됩니다.
4. 입력 값 추출
양식 제출을 관리하는 것 외에도, 사용자가 입력한 값을 추출하는 방법이 중요합니다.
여기서는 두 가지 방법을 소개합니다: 상태(state)와 참조(ref)를 사용하는 방법입니다.
4.1 상태(State) 사용
상태를 사용하여 입력 값을 관리할 수 있습니다.
import { useState } from 'react';
function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
console.log('Username:', username);
console.log('Password:', password);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input id="username" type="text" name="username" value={username} onChange={(e) => setUsername(e.target.value)} />
<label htmlFor="password">Password:</label>
<input id="password" type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Login</button>
</form>
);
}위 코드에서는 useState를 사용하여 입력 값을 상태로 관리합니다. 입력 필드의 값이 변경될 때마다 상태가 업데이트되고, 양식이 제출될 때 현재 상태 값을 로그로 출력합니다.
4.2 참조(Ref) 사용
참조를 사용하여 입력 값을 직접 추출할 수도 있습니다.
import { useRef } from 'react';
function Login() {
const usernameRef = useRef();
const passwordRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
console.log('Username:', usernameRef.current.value);
console.log('Password:', passwordRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input id="username" type="text" name="username" ref={usernameRef} />
<label htmlFor="password">Password:</label>
<input id="password" type="password" name="password" ref={passwordRef} />
<button type="submit">Login</button>
</form>
);
}위 코드에서는 useRef를 사용하여 입력 필드의 값을 참조로 가져옵니다. 양식이 제출될 때 참조된 입력 필드의 값을 로그로 출력합니다.
5. 양식 검증
양식 검증은 좋은 사용자 경험을 제공하기 위해 중요한 요소입니다. 검증 시점은 다음과 같습니다:
- 입력 중 실시간 검증: 사용자가 입력할 때마다 검증.
- 입력 완료 후 검증: 사용자가 입력을 마치고 다음 필드로 이동할 때 검증.
- 양식 제출 시 검증: 양식을 제출할 때 전체 데이터를 검증.
각 방법의 장단점을 이해하고, 앱의 요구사항에 맞는 방법을 선택하는 것이 중요합니다.
6. 결론
이번 강의에서는 양식 제출을 관리하고 값을 추출하는 방법을 배웠습니다.
이를 통해 리액트에서 양식을 다루는 기본적인 방법을 이해할 수 있습니다.
다음 단계에서는 입력 값을 검증하는 방법과 HTTP 요청을 통해 백엔드로 데이터를 전송하는 방법을 배워보겠습니다.
예시 코드
129. State(상태) & 일반 Handler로 사용자 입력 수집 및 관리
위 내용을 바탕으로 리액트의 useState 훅을 사용하여 양식 데이터를 관리하고, 입력된 값을 처리하는 방법을 자세하게 설명해드리겠습니다.
1. useState를 이용한 상태 관리
리액트의 useState 훅을 사용하여 입력값을 상태로 관리할 수 있습니다.
상태를 여러 개 사용하는 방법과 객체 하나로 상태를 결합하는 방법이 있습니다.
여기서는 두 가지 방법을 모두 설명하겠습니다.
2. 상태를 여러 개 사용하는 방법
각 입력 필드마다 상태를 설정하여 관리하는 방법입니다.
예를 들어, 이메일과 비밀번호 입력 필드가 있는 로그인 폼을 생각해봅시다.
Login.tsx
import React, { useState } from 'react';
const Login: React.FC = () => {
const [enteredEmail, setEnteredEmail] = useState('');
const [enteredPassword, setEnteredPassword] = useState('');
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEnteredEmail(event.target.value);
};
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEnteredPassword(event.target.value);
};
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
console.log('User email:', enteredEmail);
console.log('User password:', enteredPassword);
};
return (
<div className="w-full max-w-xs">
<form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
value={enteredEmail}
onChange={handleEmailChange}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
value={enteredPassword}
onChange={handlePasswordChange}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
>
Login
</button>
<button
type="button"
onClick={() => {
setEnteredEmail('');
setEnteredPassword('');
}}
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Reset
</button>
</div>
</form>
</div>
);
};
export default Login;3. 상태를 하나의 객체로 결합하는 방법
입력 필드가 많은 경우, 각각의 필드에 대해 별도의 상태와 핸들러를 설정하는 대신 하나의 객체 상태를 사용하는 방법입니다.
Login.tsx
import React, { useState } from 'react';
const Login: React.FC = () => {
const [enteredValues, setEnteredValues] = useState({ email: '', password: '' });
const handleInputChange = (identifier: string, value: string) => {
setEnteredValues((prevValues) => ({
...prevValues,
[identifier]: value,
}));
};
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
console.log('User email:', enteredValues.email);
console.log('User password:', enteredValues.password);
};
return (
<div className="w-full max-w-xs">
<form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
value={enteredValues.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
value={enteredValues.password}
onChange={(e) => handleInputChange('password', e.target.value)}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
>
Login
</button>
<button
type="button"
onClick={() => setEnteredValues({ email: '', password: '' })}
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Reset
</button>
</div>
</form>
</div>
);
};
export default Login;예시 코드
130. refs(참조)로 사용자 입력 수집
131. FormData & 네이티브 브라우저 API로 값 채취
132. form 초기화하기
여기서는 리액트에서 양식을 리셋하는 방법과 입력값을 초기화하는 다양한 방법을 알아보겠습니다.
이 과정에서 버튼의 reset 속성과 useState 또는 useRef 훅을 사용한 프로그래밍적 초기화 방법을 다룰 것입니다.
양식 리셋 방법
HTML의reset버튼 사용가장 간단한 방법은
HTML의 내장된reset버튼을 사용하는 것입니다.
이 버튼의 유형(type)을reset으로 설정하면 양식의 모든 입력값이 기본값으로 초기화됩니다.import React from 'react'; const Signup: React.FC = () => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const formData = new FormData(event.target as HTMLFormElement); const data: { [key: string]: FormDataEntryValue | FormDataEntryValue[] } = Object.fromEntries(formData.entries()); // Get checkboxes with the same name const acquisitionChannels = formData.getAll('acquisition') as string[]; data.acquisition = acquisitionChannels; console.log(data); // Optionally reset the form after submission (event.target as HTMLFormElement).reset(); }; return ( <div className="w-full max-w-xs"> <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="firstName"> First Name </label> <input id="firstName" name="firstName" type="text" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="lastName"> Last Name </label> <input id="lastName" name="lastName" type="text" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email"> Email </label> <input id="email" name="email" type="email" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password"> Password </label> <input id="password" name="password" type="password" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-bold mb-2"> How did you hear about us? </label> <div className="flex flex-col"> <label className="block text-gray-700 text-sm"> <input type="checkbox" name="acquisition" value="friends" className="mr-2 leading-tight" /> Friends </label> <label className="block text-gray-700 text-sm"> <input type="checkbox" name="acquisition" value="search" className="mr-2 leading-tight" /> Search Engine </label> <label className="block text-gray-700 text-sm"> <input type="checkbox" name="acquisition" value="advertisement" className="mr-2 leading-tight" /> Advertisement </label> </div> </div> <div className="flex items-center justify-between"> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" > Signup </button> <button type="reset" className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" > Reset </button> </div> </form> </div> ); }; export default Signup;위 코드에서
type="reset"버튼은 양식의 모든 입력값을 초기화합니다.
상태(
state)로 관리하는 경우입력값을
useState로 관리하는 경우, 상태를 초기 상태로 재설정하여 양식을 초기화할 수 있습니다.import React, { useState } from 'react'; const StateLogin: React.FC = () => { const [enteredValues, setEnteredValues] = useState({ email: '', password: '' }); const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = event.target; setEnteredValues((prevValues) => ({ ...prevValues, [name]: value, })); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); console.log('User email:', enteredValues.email); console.log('User password:', enteredValues.password); // Reset the state to initial values setEnteredValues({ email: '', password: '' }); }; return ( <div className="w-full max-w-xs"> <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email"> Email </label> <input id="email" type="email" name="email" value={enteredValues.email} onChange={handleInputChange} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password"> Password </label> <input id="password" type="password" name="password" value={enteredValues.password} onChange={handleInputChange} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="flex items-center justify-between"> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" > Login </button> <button type="button" onClick={() => setEnteredValues({ email: '', password: '' })} className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" > Reset </button> </div> </form> </div> ); }; export default StateLogin;여기서는
setEnteredValues를 사용하여 상태를 초기 상태로 되돌림으로써 양식을 초기화합니다.
useRef를 사용하는 경우입력값을
useRef로 관리하는 경우, 참조된 요소의 값을 직접 변경하여 초기화할 수 있습니다.
이 방법은 직접DOM을 조작하기 때문에 신중히 사용해야 합니다.import React, { useRef } from 'react'; const StateLogin: React.FC = () => { const emailRef = useRef<HTMLInputElement>(null); const passwordRef = useRef<HTMLInputElement>(null); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const enteredEmail = emailRef.current?.value || ''; const enteredPassword = passwordRef.current?.value || ''; console.log('User email:', enteredEmail); console.log('User password:', enteredPassword); // Reset the form fields if (emailRef.current) emailRef.current.value = ''; if (passwordRef.current) passwordRef.current.value = ''; }; return ( <div className="w-full max-w-xs"> <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email"> Email </label> <input id="email" type="email" ref={emailRef} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password"> Password </label> <input id="password" type="password" ref={passwordRef} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" /> </div> <div className="flex items-center justify-between"> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" > Login </button> <button type="button" onClick={() => { if (emailRef.current) emailRef.current.value = ''; if (passwordRef.current) passwordRef.current.value = ''; }} className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" > Reset </button> </div> </form> </div> ); }; export default StateLogin;여기서는 useRef를 사용하여 양식 필드의 값을 직접 변경함으로써 양식을 초기화합니다.
요약
양식을 초기화하는 방법에는 다음 세 가지가 있습니다:
HTML의reset버튼 사용: 가장 간단한 방법으로, 양식의 모든 입력값을 기본값으로 초기화합니다.- 상태(
state)로 관리하는 경우: 상태를 초기 상태로 재설정하여 양식을 초기화할 수 있습니다. useRef를 사용하는 경우: 참조된 요소의 값을 직접 변경하여 양식을 초기화할 수 있습니다.
각 방법은 상황에 따라 적절히 선택하여 사용할 수 있습니다.
이러한 방법을 통해 양식을 초기화하는 방법을 이해하고, 필요에 따라 적용할 수 있습니다.
예시 코드
Signup.tsx: HTML의 reset 버튼 사용StateLogin.tsx: 상태(state) +useRef로 관리하는 경우
133. State(상태)로 매 키보드 입력마다 유효성 검사하기
리액트의 상태 기반 접근법을 사용하여 양식의 유효성을 검증하는 방법을 설명합니다.
사용자가 입력창에 무언가를 입력할 때마다 검증이 진행되도록 하여 즉각적인 피드백을 제공할 수 있습니다.
여기서는 StateLogin 컴포넌트를 사용하여 이메일 입력값을 검증하는 예제를 설명합니다.
134. Blur 상태시 입력 유효성 검사
포커스 해제 상태에서 입력값을 검증하는 방법은 사용자가 입력창을 클릭해 값을 입력하거나 수정한 뒤, 다른 곳을 클릭하여 입력창의 포커스를 잃었을 때 검증을 실행하는 것입니다.
이 방법은 입력 중에 실시간으로 검증 메시지를 표시하지 않으며, 사용자가 입력을 완료한 후 검증 메시지를 표시합니다.
135. form 제출시 입력 유효성 검사
유효성 검증은 사용자가 입력한 값이 우리가 기대하는 형식이나 조건을 충족하는지 확인하는 과정입니다.
이 과정은 사용자가 타이핑할 때마다 실시간으로 수행할 수도 있고, 입력창에서 포커스가 해제될 때 또는 양식이 제출될 때 수행할 수도 있습니다.
이 설명에서는 참조(ref)를 사용한 양식에서 제출 시 유효성 검증을 하는 방법을 다룹니다.
input type이 email인 경우 작성한 유효성 로직이 실행되기 전에 브라우저 로직이 먼저 실행됩니다.
136. 브라우저 내장 유효성 검증 속성 사용하기
리액트에서 양식 유효성 검증을 더 쉽게 구현하는 방법 중 하나는 브라우저가 제공하는 내장 유효성 검증 속성을 사용하는 것입니다.
이러한 내장 속성을 사용하면 별도의 커스텀 코드를 작성할 필요 없이 간단히 HTML 속성을 추가하여 검증을 수행할 수 있습니다.
주요 내장 유효성 검증 속성
required: 필수 입력 필드로 지정합니다.type: 입력 유형을 지정합니다. 예:email,password,text등.minLength: 입력 값의 최소 길이를 지정합니다.maxLength: 입력 값의 최대 길이를 지정합니다.pattern: 입력 값이 정규 표현식과 일치하는지 검증합니다.
137. 브라우저 내장 유효성 검사 속성과 커스텀 유효성 검사 결합하기
브라우저 내장 유효성 검사 속성은 별도의 추가 코드 작성 없이 간단히 입력값을 검증할 수 있는 훌륭한 방법입니다.
그러나 특정 요구 사항에 맞춰 추가적인 유효성 검사가 필요한 경우, 커스텀 유효성 검사를 추가하여 더 복잡한 검증을 수행할 수 있습니다.
여기서는 브라우저 내장 유효성 검사와 커스텀 유효성 검사를 결합하는 방법을 설명합니다.
주요 내장 유효성 검사 속성
required: 필수 입력 필드로 지정.type: 입력 유형을 지정. 예:email,password,text등.minLength: 입력 값의 최소 길이를 지정.maxLength: 입력 값의 최대 길이를 지정.pattern: 입력 값이 정규 표현식과 일치하는지 검증.
커스텀 유효성 검사 예제
예를 들어, confirm-password 필드가 password 필드와 일치하는지 확인해야 하는 경우, 내장 유효성 검사만으로는 해결할 수 없습니다.
이때 커스텀 유효성 검사를 추가하여 검증을 수행할 수 있습니다.
138. 재사용 가능한 입력 컴포넌트 만들기
복잡한 양식을 다루면서 코드의 반복을 줄이고, 보다 관리가 쉬운 구조를 만들기 위해 재사용 가능한 커스텀 입력 컴포넌트를 만드는 방법을 설명합니다.
이를 통해 코드를 간결하게 유지하고 유지보수를 쉽게 할 수 있습니다.
139. 유효성 검사(검증) 로직 아웃소싱
유효성 검사 로직을 재사용 가능한 형태로 만들면, 코드를 보다 간결하게 유지할 수 있으며 유지보수가 쉬워집니다.
여러 컴포넌트에서 공통적으로 사용되는 유효성 검사 로직을 유틸리티 함수로 분리하여 프로젝트 전반에서 활용할 수 있습니다.
140. 커스텀 useInput Hook(훅) 생성
커스텀 훅을 사용한 입력값과 유효성 검증 로직의 재사용
입력값과 유효성 검증 로직을 커스텀 훅으로 분리하여 재사용성을 높이고, 코드의 가독성을 개선할 수 있습니다.
여기서는 useInput이라는 커스텀 훅을 만들어서 입력값과 유효성 검증 상태를 관리하는 방법을 설명합니다.
141. 서드 파티 Form 라이브러리 사용하기
리액트 앱에서 양식을 다루고 입력값을 처리하는 방법에 대해 다양한 기술과 도구를 배웠습니다.
이번 섹션에서는 이러한 기술을 활용하여 양식을 만들고, 입력값을 얻고, 유효성을 검증하는 방법을 익혔습니다.
하지만 개발자로서의 삶을 더욱 편리하게 만들어줄 수 있는 서드파티 라이브러리들도 있습니다.
이러한 라이브러리들은 양식과 유효성 검증 작업을 보다 쉽게 처리할 수 있도록 도와줍니다.
주요 내용 요약
양식 다루기:
- 리액트에서 양식을 만드는 방법을 배웠습니다.
- 입력값을 얻기 위해
useState훅을 사용했습니다. useInput같은 커스텀 훅을 만들어 입력값과 유효성 검증을 관리했습니다.
유효성 검증:
- 입력값을 실시간으로 또는 제출 시점에 유효성 검증하는 방법을 배웠습니다.
- 브라우저의 기본 유효성 검증 속성 (
required,minLength등)을 사용하는 방법을 배웠습니다. - 커스텀 유효성 검증 로직을 추가하여 더 복잡한 검증을 수행했습니다.
서드파티 라이브러리:
React Hook Form과Formik같은 라이브러리는 양식 관리와 유효성 검증을 더 쉽게 해줍니다.- 이러한 라이브러리는 많은 기능을 제공하며, 입력값 처리와 유효성 검증을 더 간단하게 만들어줍니다.
- 예를 들어,
React Hook Form은 적은 코드로도 강력한 기능을 제공하여, 양식 처리 작업을 더 효율적으로 만들 수 있습니다.
왜 서드파티 라이브러리를 사용할까요?
- 서드파티 라이브러리를 사용하면 다음과 같은 장점이 있습니다:
- 코드 간소화: 복잡한 로직을 간단한 함수 호출로 대체할 수 있습니다.
- 유지보수 용이: 검증 로직과 상태 관리를 라이브러리에 맡김으로써, 코드 유지보수가 더 쉬워집니다.
- 다양한 기능: 커스텀 유효성 검증, 파일 업로드, 다중 단계 양식 등 다양한 기능을 쉽게 구현할 수 있습니다.
142. 연습: 음식 주문 앱 개발
--268부터--- 음식 클릭하면 카트에 담기는 기능 만들어야됨