리액트에서 앱을 구현할 때는 컴포넌트 단위로 생각하는 게 필요합니다. 앞에서 살 펴본 Viewer, Controller 영역을 일종의 컴포넌트라고 생각하는 겁니다.
“하나의 컴포넌트는 단 하나의 역할만 수행한다.”
npx create-react-app .
사용하지 않는 코드 역시 삭제합니다.
UI는 사용자 인터페이스라는 뜻으로, 웹 페이지에서 사용자와 상호작용하는 요소를 말합니다. 이 요소들의 사용성을 높이기 위해 기능을 추가하기도 하고, 특별한 형태나 색상 등의 스타일을 적용하기도 합니다.
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>와 동일한 기능을 수행합니다.
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단계 라이프 사이클을 도식화한 것입니다.
리액트 훅의 하나인 함수 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으로 설정한 다음, <세부정보> 버튼을 클릭합니다.
마지막으로 브라우저 우측 상단에서 퍼즐 모양으로 생긴 ‘확장 프로그램’ 아이콘을 클릭해 React Developer Tools의 핀을 고정합니다.
1.
다음 요구사항에 따라 Counter 컴포넌트를 작성하세요.
답안:
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
[React.js 1] p2. 할 일 관리 앱 만들기 (0) | 2023.12.01 |
---|---|
[React.js 1] 8장. Hooks (0) | 2023.11.24 |
[React.js 1] 5장. 리액트의 기본 기능 다루기(2) (0) | 2023.11.10 |
[React.js 1] 5장. 리액트의 기본 기능 다루기(1) (0) | 2023.11.03 |
[React.js 1]3장, 4장 Node.js, 리액트 시작하기 (0) | 2023.10.13 |