[React.js 1] p1. 카운터 앱 만들기, 6장. 라이프 사이클과 리액트 개발자 도구
카운터 앱 만들기
요구사항 분석하기
- 현재의 카운트를 표시: 따라서 이 영역의 이름을 뷰어(Viewer) 라고 하겠습니다.
- 카운트를 늘리거나 줄일 수 있는 6개의 버튼: 카운트를 제어하는 영역이라는 의미에서 컨트롤러(Controller)라고 이름 붙이겠습니다.
컴포넌트 단위로 생각하기
리액트에서 앱을 구현할 때는 컴포넌트 단위로 생각하는 게 필요합니다. 앞에서 살 펴본 Viewer, Controller 영역을 일종의 컴포넌트라고 생각하는 겁니다.
- App 컴포넌트: Viewer와 Controller 컴포넌트를 감싸는 템플릿
- Viewer 컴포넌트: 현재의 카운트를 표시함
- Controller 컴포넌트: 카운트를 제어할 수 있는 기능을 제공함
“하나의 컴포넌트는 단 하나의 역할만 수행한다.”
리액트 앱 만들기
npx create-react-app .
- src/App.test.js
- src/logo.svg
- src/reportWebVitals.js
- src/setupTest.js
사용하지 않는 코드 역시 삭제합니다.
UI 구현하기
UI는 사용자 인터페이스라는 뜻으로, 웹 페이지에서 사용자와 상호작용하는 요소를 말합니다. 이 요소들의 사용성을 높이기 위해 기능을 추가하기도 하고, 특별한 형태나 색상 등의 스타일을 적용하기도 합니다.
Viewer 컴포넌트 만들기
const Viewer = () => {
return (
<div>
<div>현재 카운트: </div>
<h1>0</h1>
</div>
);
};
export default Viewer;
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;
① component 폴더에 있는 Viewer 컴포넌트를 불러옵니다. ② 제목 ‘Simple Counter’를 <h1> 태그로 감싸 페이지에서 렌더링합니다. ③ Viewer 컴포넌트를 불러와 <section> 태그로 감싸 렌더링합니다. <section>은 영역을 분리하기 위한 태그로 <div>와 동일한 기능을 수행합니다.
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;
App 컴포넌트를 다음과 같이 수정합니다.
import "./App.css";
import Controller from "./component/Controller"; ①
import Viewer from "./component/Viewer";
function App() {
return (
<div className="App">
<h1>Simple Counter</h1>
<section>
<Viewer />
</section>
<section>
<Controller /> ②
</section>
</div>
);
}
export default App;
컴포넌트 스타일링하기
자신이 원하는 스타일을 적용해도 좋습니다.
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;
}
기능 구현하기
State는 반드시 컴포넌트 함수 안에 만들어야 합니다. 현재 여러분과 함께 만들고 있는 [카운터] 앱에는 App, Viewer, Controller 3개의 컴포넌트가 있습니다. 그렇다면 어떤 컴포넌트에서 [카운터] 앱의 State를 만들어야 할까요?
⇒ App 컴포넌트
오답1: Viewer 컴포넌트=> Viewer 컴포넌트가 Controller 컴포넌트에 setCount를 전달할 방법이 없다.
오답2: Controller 컴포넌트=> 변경된 State 값을 Viewer 컴포넌트에 전달할 방법이 없기 때문입니다.
정답: 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} /> ①
</section>
<section>
<Controller handleSetCount={handleSetCount} /> ②
</section>
</div>
);
}
export default App;
① Viewer 컴포넌트에 State 변수 count의 값을 Props로 전달합니다
② Controller 컴포넌트에 State 값을 변경하는 함수 setCount를 Props로 전달합니다
Viewer 컴포넌트에서 App에서 받은 Props를 페이지에 렌더링합니다.
const Viewer = ({ count }) => {
return (
<div>
<div>현재 카운트 : </div>
<h1>{count}</h1>
</div>
);
};
export default Viewer;
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;
리액트답게 설계하기


라이프 사이클과 리액트 개발자 도구
리액트 컴포넌트의 라이프 사이클은 크게 3단계로 구분합니다. 다음 그림은 리액트 컴포넌트의 3단계 라이프 사이클을 도식화한 것입니다.
- 마운트(Mount): 컴포넌트를 페이지에 처음 렌더링할 때
- 업데이트(Update): State나 Props의 값이 바뀌거나 부모 컴포넌트가 리렌더해 자신도 리렌더될 때
- 언마운트(Unmount): 더 이상 페이지에 컴포넌트를 렌더링하지 않을 때
리액트 훅의 하나인 함수 useEffect를 이용하면 이 사이클을 쉽게 제어할 수 있습니다.
useEffect
첫 번째 프로젝트로 만든 카운터 앱을 수정하면서 함수 useEffect를 어떻게 사용하는지 알아보겠습니다.
하나의 값 검사하기
import { useState, useEffect } from "react"; ①
(...)
function App() {
const [count, setCount] = useState(0);
const handleSetCount = (value) => {
setCount(count + value);
};
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의 용법
useEffect(callback, [deps])
콜백 함수 의존성 배열
버튼을 클릭할 때마다 useEffect에 인수로 전달한 콜백 함수가 실행되어 변경된 State 값을 콘솔에 출력합니다. 이렇듯 useEffect를 이용하면 특정 값이 바뀔 때마다 여러분이 원하는 코드를 실행하도록 만들 수 있습니다.
여러 개의 값 검사하기
(...)
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState(""); ①
b
const handleSetCount = (value) => {
setCount(count + value);
};
const handleChangeText = (e) => { ②
setText(e.target.value);
};b
useEffect(() => {
console.log("count 업데이트: ", count);
}, [count]);
return (
<div className="App">
<h1>Simple Counter</h1>
<section>
<input value={text} onChange={handleChangeText} /> ③
</section>b
<section>
<Viewer count={count} />
</section>
<section>
<Controller handleSetCount={handleSetCount} />
</section>
</div>
);
}
export default App;
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성 완료</button>
</div>
);
}
export default Body;
① useState를 이용해 State 변수 text를 만듭니다. ② onChange 이벤트 핸들러 함수 handleTextChange를 만듭니다. ③ 새로운 <section> 태그로 페이지의 영역을 나누고, 텍스트 입력 폼을 생성합니다. 사용자 입력을 처리하기 위한 value 속성에 변수 text를 전달하고, onChange 이벤트 핸들러로 함수 handleChangeText를 지정합니다.
이제 text 값이 변경되어도 useEffect가 콜백 함수를 실행해야 합니다. App 컴포넌트를 다음과 같이 수정합니다
(...)
function App() {
(..)
useEffect(() => {
console.log("업데이트: ", text, count);
}, [count, text]);
(..)
}
export default App;
① useEffect의 콜백 함수가 실행되면 State 변수 text와 count 값을 콘솔에 출력합니다. ② useEffect에 전달하는 의존성 배열에 변수 text를 요소로 추가합니다.
두 개의 State 값이 변할 때마다 콘솔에 값을 출력합니다.
컴포넌트의 마운트 제어하기
(...)
function App() {
(...)
const didMountRef = useRef(false);
useEffect(() => {
if (!didMountRef.current) {
didMountRef.current = true;
return;
} else {
console.log("컴포넌트 업데이트!");
}
});
useEffect(() => { ①
console.log("컴포넌트 마운트");
}, []);
return (
(...)
);
}
export default App;
컴포넌트 언마운트 제어하기
클린업
(...)
function App() {
(...)
useEffect(() => {
const intervalID = setInterval(() => { ①
console.log("깜빡");
}, 1000);
return () => { ②
console.log("클린업");
clearInterval(intervalID); ③
};
});
(...)
}
export default App;
① 함수 setInterval은 새 인터벌을 생성하면 인터벌 식별자(id)를 반환합니다. 이 id를 변수 intervalID에 저장합니다. ② useEffect에 인수로 전달한 콜백 함수가 새 함수를 반환하도록 합니다. 이 함수는 클린업 함수로 서 useEffect의 콜백 함수가 실행되기 전이나 컴포넌트가 언마운트하는 시점에 실행됩니다. ③ 클린업 함수는 clearInterval을 호출합니다. 인수로 ①에서 생성한 인터벌 식별자를 전달해 앞서 생성한 인터벌을 삭제합니다.
정리하면 useEffect의 콜백 함수가 반환하는 함수를 클린업 함수라고 합니다. 이 함수는 콜백 함수를 다시 호출하기 전에 실행됩니다. 따라서 컴포넌트를 렌더링할 때 마다 새 인터벌을 생성하고 기존 인터벌은 삭제합니다.
리액트 개발자 도구
리액트 앱을 개발할 때 매우 유용하게 사용하는 리액트 개발자 도구(React Developer Tools)를 소개하려 합니다.
[Chrome 웹 스토어] 페이지 ‘확장 프로그램 검색’ 폼에서 ‘React Developer Tools’라고 입력하고 <Enter>키를 누릅니다. [chrome 웹 스토어] 확장 프로그램 페이지가 나옵니다.
모든 설치를 완료했으면 확장 프로그램의 설정을 확인해야 합니다. 크롬 브라우저 우측 상단의 확장 아이콘을 선택하면 나오는 메뉴에서 [도구 더보기]-[확장 프로그램]을 차례로 클릭합니다.
방금 설치한 React Developer Tools를 찾아 스위치를 On으로 설정한 다음, <세부정보> 버튼을 클릭합니다.
- 사용: ON
- 사이트 엑세스: 모든 사이트에서
- 시크릿 모드에서 허용: 시크릿 모드에서 리액트 개발자 도구를 사용하려면 ON, 그렇지 않으면 자유롭게 설정합니다. 잘 모르겠다면 ON으로 설정합니다.
- 파일 URL에 대한 엑세스 허용: ON
마지막으로 브라우저 우측 상단에서 퍼즐 모양으로 생긴 ‘확장 프로그램’ 아이콘을 클릭해 React Developer Tools의 핀을 고정합니다.
리액트 개발자 도구의 기능 사용하기
컴포넌트 트리 살펴보기
리렌더 하이라이트 기능 사용하기

Quiz
- ( UI )는 사용자 인터페이스라는 뜻으로, 웹 페이지에서 사용자와 상호작용하는 요소를 말합니다.
- 리액트 컴포넌트의 언마운트 시점을 제어하기 위해 사용하는, 특정 함수가 실행되고 종료된 후에, 미처 정리하지 못한 사항을 처리하는 일을 (클린업 ) 이라고 부릅니다.
- 리액트는 State 값이나 set함수를 여러 컴포넌트에서 사용하는 경우, 이들을 (상위 컴포넌트 )에서 관리합니다. 리액트에서는 이 기능을 다른 말로 ‘State 끌어올리기(State Lifting)’라고 합니다.
- 리액트 컴포넌트의 라이프 사이클 중 컴포넌트를 페이지에 처음 렌더링할 때 : ( 마운트(Mount) )
- 리액트 컴포넌트의 라이프 사이클 중 State나 Props의 값이 바뀌거나 부모 컴포넌트가 리렌더해 자신도 리렌더될 때 : ( 업데이트(Update) )
- 리액트 컴포넌트의 라이프 사이클 중 더 이상 페이지에 컴포넌트를 렌더링하지 않을 때 : ( 언마운트(Unmount) )
- 리액트 훅의 하나인 함수 ( useEffect )를 이용하면 리액트의 라이프 사이클을 쉽게 제어할 수 있습니다.
코드작성
1.
다음 요구사항에 따라 Counter 컴포넌트를 작성하세요.
- 초기값이 0인 카운터를 표시합니다.
- "Increment" 버튼을 누를 때마다 카운터가 1씩 증가합니다.
- "Decrement" 버튼을 누를 때마다 카운터가 1씩 감소합니다.
답안:
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
handleDecrement = () => {
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleIncrement}>Increment</button>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
export default Counter;
2. 위 코드에 라이프 사이클 메서드를 추가하세요.
답안:
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleDecrement = () => {
setCount(count - 1);
};
// componentDidMount와 componentWillUnmount 역할
useEffect(() => {
console.log('Component Did Mount');
// componentWillUnmount 역할
return () => {
console.log('Component Will Unmount');
};
}, []); // 빈 배열을 전달하여 componentDidMount에서만 실행되도록 설정
return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
};
export default Counter;
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)
Corner React.js 1
Editor: dalpaeng4