프로젝트를 구현하기 전, 앱을 어떤 설계와 기능으로 구현할지 살펴보는 단계인 요구사항 분석 과정을 거쳐야 한다. 리액트 앱을 구현할 때는 컴포넌트 단위로 생각하는 게 필요하다.
[카운터] 앱의 요구사항 분석을 하자면
재사용 가능한 수준에서 기능 별로 컴포넌트를 분리하면 프로젝트를 관리하기 쉬워진다.
UI란 사용자 인터페이스라는 뜻으로, 웹 페이지에서 사용자와 상호작용하는 요소를 말한다. 요소의 외양(껍데기)라고 생각하면 된다.
2-(1). Viewer 컴포넌트 만들기
src에 component 폴더를 만들고, 이 폴더에 Viewer.js 파일을 생성한다.
const Viewer = () => {
return (
<div>
<div>현재 카운트: </div>
<h1>0</h1>
</div>
);
};
export default Viewer;
Viewer.js 코드는 위와 같이 작성한다.
import "./App.css";
import Viewer from "./component/Viewer"; ①
function App() {
return (
<div className="App">
<h1>Simple Counter</h1> ②
<section>
<Viewer /> ③
</section>
</div>
);
}
export default App;
App.js 파일도 위와 같이 작성하여 Viewer 컴포넌트를 App의 자식으로 배치한다.
이때, <section> 태그로 Viewer 컴포넌트를 감싸 영역을 분리해준다.
2-(2). Controller 컴포넌트 만들기
const Controller = () => {
return (
<div>
<button>-1</button>
<button>-10</button>
<button>-100</button>
<button>+100</button>
<button>+10</button>
<button>+1</button>
</div>
);
};
export default Controller;
component 폴더 안에 Controller.js 파일을 추가하고 위와 같이 6개의 버튼을 한 줄로 렌더링하는 코드를 작성한다.
(...)
<section>
<Controller /> ②
</section>
(...)
App.js의 Viewer 컴포넌트를 감싼 section 아래에 위와 같은 section을 추가한다.
2-(3). 컴포넌트 스타일링하기
src 폴더 App.css에서 기존 코드를 모두 삭제하고 다음과 같이 작성한다.
body {
padding: 20px;
}
.App {
margin: 0 auto;
width: 500px;
}
.App > section { ①
padding: 20px;
background-color: rgb(245, 245, 245);
border: 1px solid rgb(240, 240, 240);
border-radius: 5px;
margin-bottom: 10px;
}
3-(1). State를 이용해 카운터 기능 구현하기
Controller 컴포넌트에 있는 버튼을 클릭하면, Viewer 컴포넌트에 있는 카운트가 증가하거나 감소해야 한다.
이 기능을 구현하기 위해 버튼 클릭 이벤트가 발생했을 때 컴포넌트 값을 동적으로 렌더링시키는 State를 사용한다.
이 과정을 도해화하면 위와 같다. 카운트를 관리할 State를 만들고 초깃값을 0으로 설정한 후, 버튼을 클릭하면 현재 State 값을 버튼이 전달하는 값과 계산하여 변경한다. 그 후 변경된 State 값을 Viewer 컴포넌트에 전달하여 페이지의 카운트 값을 업데이트한다.
3-(2). State는 어떤 컴포넌트에 만들까?
정답: App 컴포넌트
App.js 파일을 다음과 같이 수정한다.
import "./App.css";
import { useState } from "react";
import Controller from "./component/Controller";
import Viewer from "./component/Viewer";
function App() {
const [count, setCount] = useState(0);
const handleSetCount = (value) => {
setCount(count + value);
};
return (
<div className="App">
<h1>Simple Counter</h1>
<section>
<Viewer count={count} /> ① Viewer 컴포넌트에 State 변수 count의 값을 Props로 전달
</section>
<section>
<Controller handleSetCount={handleSetCount} /> ② Controller 컴포넌트에 State 값을 변경하는 함수 setCount를 Props로 전달
</section>
</div>
);
}
export default App;
다음으로 Viewer.js를 수정하여 Viewer 컴포넌트에서 App에서 받은 Props를 페이지에 렌더링한다.
const Viewer = ({ count }) => {
return (
<div>
<div>현재 카운트 : </div>
<h1>{count}</h1>
</div>
);
};
export default Viewer;
리액트에서는 부모가 리렌더되거나 전달된 Props가 변경되면 자식 컴포넌트도 자동으로 리렌더하므로, Viewer 컴포넌트는 Props로 받은 State 값이 변경될 때마다 리렌더되어 실시간으로 이 값을 페이지에 렌더링한다.
Controller.js를 다음과 같이 수정하자.
const Controller = ({ handleSetCount }) => {
return (
<div>
<button onClick={() => handleSetCount(-1)}>-1</button>
<button onClick={() => handleSetCount(-10)}>-10</button>
<button onClick={() => handleSetCount(-100)}>-100</button>
<button onClick={() => handleSetCount(100)}>+100</button>
<button onClick={() => handleSetCount(10)}>+10</button>
<button onClick={() => handleSetCount(1)}>+1</button>
</div>
);
};
export default Controller;
App 컴포넌트에서 함수 handleSetCount를 받아 버튼의 이벤트 핸들러로 사용한다. 버튼을 클릭하면 함수 handleSetCount를 호출하는데, 이 함수는 App 컴포넌트의 State 값을 업데이트한다.
리액트는 State 값이나 set함수를 여러 컴포넌트에서 사용하는 경우, 이들을 상위 컴포넌트에서 관리한다. 리액트에서는 이 기능을 State 끌어올리기(State Lifting)라고 한다.
3-(3). 리액트답게 설계하기
라이프 사이클을 이용하여 컴포턴트가 처음 렌더링 될 때, 업데이트 할 때, 페이지에서 사라질 때 특정 동작을 추가할 수 있는데, 이를 라이프 사이클 제어(Lifecylcle Control)라고 한다.
라이프 사이클 제어는 어떤 값이 변경될 때마다 특정 코드를 실행하는 리액트 훅인 함수 useEffect를 통해 실현 가능하다.
2-(1). 하나의 값 검사하기
useEffect(callback, [deps])
콜백 함수 의존성 배열
import { useState, useEffect } from "react"; ① 함수 useEffect를 사용하기 위해 react 라이브러리 불러오기.
(...)
function App() {
const [count, setCount] = useState(0);
const handleSetCount = (value) => {
setCount(count + value);
};
useEffect(() => { ② useEffect를 호출하고 두 개의 인수(콜백 함수, 배열)를 전달.
console.log("count 업데이트: ", count);
}, [count]);
return (
<div className="App">
<h1>Simple Counter</h1>
<section>
<Viewer count={count} />
</section>
<section>
<Controller handleSetCount={handleSetCount} />
</section>
</div>
);
}
export default App;
예를 들어, 위 코드에서 useEffect의 의존성 배열 요소로 State 변수 count가 있으므로, 이 값이 바뀌면 콜백 함수가 실행된다.
2-(2). 여러 개의 값 검사하기
의존성 배열 요소 중 하나가 변경되어도 useEffect는 콜백 함수를 실행한다.
(...)
function App() {
(..)
useEffect(() => {
console.log("업데이트: ", text, count);
}, [count, text]);
(..)
}
export default App;
useEffect의 의존성 배열 요소로 State 변수 count와 text가 있으므로, 이 값이 바뀌면 콜백 함수가 실행된다.
2-(3). useEffect로 라이프 사이클 제어하기
useEffect는 컴포넌트를 렌더링할 때마다 콜백 함수를 실행한다. 즉, 컴포넌트를 처음 페이지에 렌더링하는 마운트 시점과 컴포넌트를 리렌더하는 업데이트 시점에 콜백 함수가 실행된다.
이번에는 useEffect에서 마운트 시점은 제외하고 업데이트 시점에만 콜백 함수를 실행해보겠다. 이를 위해 함수 useRef를 이용한다.
import { useRef, useState, useEffect } from "react"; ①
(...)
function App() {
(...)
const didMountRef = useRef(false); ②
useEffect(() => { ③
if (!didMountRef.current) {
didMountRef.current = true;
return;
} else {
console.log("컴포넌트 업데이트!");
}
});
(...)
}
export default App;
콜백 함수 내부에서 조건문과 Ref 객체로 특정 시점에만 코드를 실행하게 만든 것이다. 마운트 시점(didMountRef=false)에 호출하면 아무것도 출력하지 않고 함수를 종료하고, 업데이트 시점(didMountRef=true)에 호출하면 문자열을 콘솔에 출력한다.
2-(4). 컴포넌트의 마운트 제어하기
(...)
function App() {
(...)
const didMountRef = useRef(false);
useEffect(() => {
if (!didMountRef.current) {
didMountRef.current = true;
return;
} else {
console.log("컴포넌트 업데이트!");
}
});
useEffect(() => { ①
console.log("컴포넌트 마운트");
}, []);
return (
(...)
);
}
export default App;
위 코드의 첫번째 useEffect 함수는 코드 내에서 조건문을 사용해 제어하여 업데이트 시에만 동작하고, 두번째 useEffect 함수는 의존성 배열에 빈 배열을 사용해 마운트 시에만 동작한다.
2-(5). 컴포넌트 언마운트 제어하기
프로그래밍에서 특정 함수가 실행되고 종료된 후에, 미처 정리하지 못한 사항을 처리하는 일을 클린업(Cleanup)이라 한다.
(...)
function App() {
(...)
useEffect(() => { ①
setInterval(() => { ②
console.log("깜빡");
}, 1000);
});
(...)
}
export default App;
저장한 직후에는 개발자 도구의 콘솔을 확인하면 1초마다 문자열 깜빡이 출력되지만, App 컴포넌트를 여러 번 리렌더 한 후에는 함수 setInterval에서 정한 인터벌(1초)이 아닌 매우 빠른 속도로 ‘깜빡’ 문자열이 콘솔에 출력되는 현상을 볼 수 있다.
이는 2가지 이유 때문인데,
(...)
function App() {
(...)
useEffect(() => {
const intervalID = setInterval(() => { ① 함수 setInterval은 새 인터벌을 생성하면 인터벌 식별자(id)를 반환합니다. 이 id를 변수 intervalID에 저장.
console.log("깜빡");
}, 1000);
return () => { ② useEffect에 인수로 전달한 콜백 함수가 새 함수를 반환. (클린업 함수)
console.log("클린업");
clearInterval(intervalID); ③ 클린업 함수는 clearInterval을 호출.
};
});
(...)
}
export default App;
useEffect의 콜백 함수가 반환하는 함수를 클린업 함수라고 한다. 이 함수는 useEffect의 콜백 함수가 실행되기 전이나 컴포넌트가 언마운트하는 시점에 실행된다. 따라서 위 코드는 컴포넌트를 렌더링할 때 마다 새 인터벌을 생성하고 함수 clearInterval을 사용해 기존 인터벌은 삭제한다.
function Even() {
return <div>현재 카운트는 짝수입니다</div>;
}
export default Even;
(...)
import Even from "./component/Even"; ①
function App() {
(...)
return (
<div className="App">
(...)
<section>
<Viewer count={count} />
{count % 2 === 0 && <Even />} ② AND 단축 평가: 앞의 식이 참이면 뒤의 컴포넌트 반환
</section>
(...)
</div>
);
}
export default App;
코드를 위처럼 구성하면 짝수일 때 Even 컴포넌트를 페이지에 렌더링한다.
import { useEffect } from "react";
function Even() {
useEffect(() => { ①
return () => {
console.log("Even 컴포넌트 언마운트");
};
}, []);
return <div>현재 카운트는 짝수입니다</div>;
}
export default Even;
Even 컴포넌트에 useEffect를 사용하여, 이 컴포넌트가 언마운트 될 때 콘솔에 특정 문자열을 출력하도록 할 수 있다.
3-(1). 리액트 개발자 도구 설치하기
Chrome Web Store에서 [React Developer Tools] 검색 후 설치
아이콘이 잘 나타난다면 개발자 도구를 열고, 탭 메뉴에서 >> 모양의 더 보기 아이콘을 클릭하여 메뉴에서 [Components]와 [Profiler] 탭이 추가 되었는지 확인한다. 이 두 탭에 컴포넌트 계층 구조의 확인이나 성능 측정 등 개발에 필요한 유용한 기능들이 있다.
3-(2). 리액트 개발자 도구의 기능 사용하기
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).
Corner React.js1
Editor: Mingging
[React.js 1팀] Project2 [할 일 관리] 앱 만들기 (0) | 2024.12.27 |
---|---|
[React.js 1팀] 8장. Hooks (1) | 2024.11.29 |
[React.js 1팀] 5장. 리액트의 기본 기능 다루기 (2) (0) | 2024.11.15 |
[React.js 1팀] 5장 리액트의 기본 기능 다루기 (1) (1) | 2024.11.08 |
[React.js 1팀] 03장. Node.js (0) | 2024.10.11 |