5장 리액트의 기본 기능 다루기
Updated:
컴포넌트
페이지의 모든 요소들은 컴포넌트 단위로 꼬개어 개발하고, 완성된 컴포넌트를 마치 레고 조립하듯이 하나로 합처 페이지를 구성한다. 리액트로 웹 서비스를 개발할 때는 컴포넌트 여러 개를 만들어 적절히 조합해서 만든다.
실습 환경 설정하기
src 폴더를 다음과 같이 만들어 준다.
src폴더의 index.js와 App.js를 다음과 같이 수정한다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
import './App.css';
function App() {
return <div className="App"> </div>;
}
export default App;
첫 컴포넌트 만들기
리액트 컴포넌트는 주로 자바스크립트의 클래스나 함수를 이용해 만든다. 클래스로 컴포넌트를 만드는 방식은 단점이 많아 다루지 않는다.
함수 컴포넌트 만들기
App.js를 다음과 같이 수정한다.
import './App.css';
const Header = () => {
return (
<header>
<h1>header</h1>
</header>
);
};
function App() {
return <div className="App"> </div>;
}
export default App;
컴포넌트를 페이지레 헨더링하기
Header 컴포넌트를 페이지에 렌더링하려면 App에서 이 컴포넌트를 자식 요소로 배치해야한다. App 컴포넌트를 다음과 같이 수정한다.
import './App.css';
const Header = () => {
return (
<header>
<h1>header</h1>
</header>
);
};
function App() {
return (
<div className="App">
<Header />
</div>);
}
export default App;
리액트는 다른 컴포넌트를 태그로 감싸 사용한다. 이때 App처럼 다른 컴포넌트를 return 문 내부에 포함하는 컴포넌트를 ‘부모 컴포넌트’라고 한다. 반대로 Header처럼 App의 return 문에 포함된 컴포넌트를 ‘자식 컴포넌트’라고 한다. 이렇게 부모의 return 문에 자식을 포함하는 행위를 “자식 컴포넌트를 배치한다.”라고 표현한다. Header 컴포넌트를 페이지에 렌더링하면 다음과 같다.
컴포넌트의 계층 구조
리액트 컴포넌트는 다음과 같은 부모-자식 관계라는 계층 구조를 형성한다.
컴포넌트의 계층 구조를 다른 말로 ‘컴포넌트 트리’라고 한다. 그리고 컴포넌트 트리에서 App은 항상 최상위에 존재하므로 이를 ‘루트 컴포넌트’라고 부른다.
컴포넌트별로 파일 분리하기
리액트에서는 하나의 파일에 여러 컴포넌트를 만들면 코드의 가독성이 떨어지기 떄문에 보통 하나의 파일에 하나의 컴포넌트를 만든다. 컴포넌트를 여러 파일로 나누고, App.js에서 불러와 App 컴포넌트의 자식으로 배치한다. 다음과 같이 폴더를 만들자.
이후 Header.js와 App.js를 다음과 같이 작성한다.
function Header() {
return (
<header>
<h1>header</h1>
</header>
);
}
export default Header;
import './App.css';
import Header from "./component/Header";
function App() {
return (
<div className="App">
<Header />
</div>);
}
export default App;
Header 컴포넌트를 잘 렌더링했는지 결과를 확인하면 다음과 같다.
다음으로 페이지의 몸통 역할을 수행할 Body와 페이지 정보를 표시할 Footer 컴포넌트를 만든다. component 폴더에 Body.js와 Footer.js를 각각 만들고 다음과 같이 코드를 작성한다.
function Body() {
return (
<div>
<h1>body</h1>
</div>
);
}
export default Body;
function Footer() {
return (
<div>
<h1>footer</h1>
</div>
);
}
export default Footer;
Body와 Footer을 페이지에 렌더링하려면 App.js에서 두 컴포넌트를 불러와 App의 자시그로 배치해야 하므로 App.js를 다음과 같이 수정한다.
import './App.css';
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from './component/Footer';
function App() {
return (
<div className="App">
<Header />
<Body />
<Footer />
</div>);
}
export default App;
페이지에서 Header, Body, Footer 컴포넌트를 잘 렌더링하는지 확인면 다음과 같다.
JSX
컴포넌트를 만들 때 사용하는 JSX 문법을 살펴본다.
JSX란?
자바스트립트와 HTML 태그를 섞어 사용하는 문법을 JSX(자바스크립트 XML)라고 한다. JSX는 공식 자바스크립트 문법은 아니지만 대다수 리액트 개발자가 사용하는 문법이다. component 폴더의 Body.js를 다음과 같이 수정해본다.
function Body() {
const number = 1;
return (
<div>
<h1>body</h1>
<h2>{number}</h2>
</div>
);
}
export default Body;
JSX와 자바스크립트 표현식
표현식이란 값으로 평가되는 식이다. JSX는 자바스크립트 표현식을 HTML 태그와 함께 사용할 수 있어 가독성 있는 코드를 작성할 수 있다.
산술 표현식
산술 표현식이란 숫자로 표현되는 식을 말한다. component 폴더의 Body.js를 다음과 같이 수정한다.
function Body() {
const numA = 1;
const numB = 2;
return (
<div>
<h1>body</h1>
<h2>{numA + numB}</h2>
</div>
);
}
export default Body;
문자열 표현식
문자열 표현식이란 문자열 또는 문자열로 평가되는 식을 말한다. component 폴더의 Body.js를 다음과 같이 수정한다.
function Body() {
const strA = "안녕";
const strB = "리액트";
return (
<div>
<h1>body</h1>
<h2>{strA + strB}</h2>
</div>
);
}
export default Body;
논리 표현식
논리 표현식이란 참이나 거짓으로 평가되는 식을 말한다. component 폴더의 Body.js를 다음과 같이 수정한다.
function Body() {
const boolA = true;
const boolB = false;
return (
<div>
<h1>body</h1>
<h2>{boolA || boolB}</h2>
</div>
);
}
export default Body;
만일 불리언 값을 페이지에 렌더링하고 싶다면, 다음과 같이 현 변환 함수를 이용해 문자열로 바꿔주어야 한다.
function Body() {
const boolA = true;
const boolB = false;
return (
<div>
<h1>body</h1>
<h2>{String(boolA || boolB)}</h2>
</div>
);
}
export default Body;
사용할 수 없는 값
JSX는 값을 반환하는 자바스크립트 표현식을 사용할 수 있다. 그러나 모든 값을 사용할 수 있는 것은 아니다. 원시 자료형에 해당하는 숫자, 문자열, 불리언, null, undefined를 제외한 값을 사용하면 오류가 발생한다. 예를들어 객체 자료형 값을 반환하는 표현식을 작성하면 페이지에는 아무것도 나타나지 않는다. 만일 객체 자료형의 값을 페이지에 렌더링하고 싶다면, 프로퍼티 접근 표기법으로 값을 원시 자료형으로 바꿔 주어야 한다.
function Body() {
const objA = {
a: 1,
b: 2,
};
return (
<div>
<h1>body</h1>
<h2>a: {objA.a}</h2>
<h2>b: {objA.b}</h2>
</div>
);
}
export default Body;
JSX 문법에서 지켜야 할 것들
JSX를 사용하는 리액트 컴포넌트를 생성할 때 반드시 지켜야 할 문법들이 있다.
- 닫힘 규칙: 닫힘 규칙은 아주 간단한 규칙이다. 즉, JSX의 모든 테그는 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다는 규칙이다.
- 최상위 태그 규칙: JSX가 반환하는 모든 태그는 반드시 최상위 태그로 감싸야 한다.
조건부 렌더링
리액트 컴포넌트가 조건식의 결과에 따라 각기 다른 값을 페이징 렌더링하는 것을 조건부 렌더링이라고 한다. 조건에 따라 페이지 요소의 모습이나 종류를 다르게 표현하고 싶을 때 조건부 렌더링을 사용한다.
삼항 연산자를 활용한 조건부 렌더링
삼항 연산자를 활용하는 JSX 문법으로 조건부 렌더링을 구현할 수 있다. component 폴더의 Boby.js를 다음과 같이 수정한다.
import React from "react";
function Body() {
const num = 19;
return (
<>
<h2>{num}은(는) {num % 2 === 0 ? "짝수" : "홀수"}입니다.</h2>
</>
);
}
export default Body;
조건문을 이용한 조건부 렌더링
조건문은 자바스크립트의 표현식이 아니기 때문에 JSX와 함께 사용할 수 없지만, 다음과 깉이 조건에 따라 컴포넌트가 반환하는 값을 다르게 표시하도록 만들 수 있다.
import React from "react";
function Body() {
const num = 200;
if(num % 2 === 0) {
return <div>{num}은(는) 짝수입니다.</div>;
} else {
return <div>{num}은(는) 홀수입니다.</div>;
}
}
export default Body;
JSX 스타일링
JSX로 리액트 컴포넌트를 스타일링하는 방법을 살펴본다. 스타일링이란 CSS와 같은 스타일 규칙을 이용해 요소의 크기, 색상 등을 결정하는 일이다.
인라인 스타일링
인라인 스타일링이란 JSX 문법 중 하나로 HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법이다. component 폴더의 Body.js를 다음과 같이 작성한다.
import React from "react";
function Body() {
return (
<div style={ backgroundColor: "red", color: "blue"}>
<h1>body</h1>
</div>
)
}
export default Body;
인라인 스타일링은 하나의 파일 안에서 UI 표현을 위한 HTML과 스타일을 위한 CSS 규칙을 함께 작성할 수 있다는 장점이 있다. 그러나 페이지가 스타일을 계산할 때 불필요한 연산을 수행할 가능성이 있고, 스타일 규칙이 많으면 코드가 복잡해져 가독성이 떨어진다.
스타일 파일 분리
HTML에서는 스타일을 정의한 CSS 파일을 따로 작성한 다음, 형식으로 불러와 사용한다. 리액트의 JSX도 마찬가지로 별도의 CSS 스타일 파일을 만들고 이를 불러와 스타일을 적용할 수 있다. Body.css를 component 폴더에 만들고 다음과 같이 작성한다.
.body {
background-color: green;
color: blue;
}
Body.js에서 className이 body인 요소의 배경색을 초록색으로, 글자 색은 파란색으로 지정하는 스타일 규칙을 Body.css에 작성했다. Body.css에 작성한 스타일 규칙을 컴포넌트에 적용하기 위해 Body.js를 다음과 같이 수정한다.
import "./Body.css";
function Body() {
return (
<div className="body">
<h1>body</h1>
</div>
)
}
export default Body;
컴포넌트에 값 전달하기
리액트 앱을 만들다 보면 컴포넌트가 다른 컴포넌트에 값을 전달해야 하는 상황이 생긴다.
Props란?
리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있다. 이 객체를 리액트에서는 Props(Properties)라고 한다.
Props로 값 전달하기
Body 컴포넌트에 있는 변수 name을 Props로 전달한다. 여기서 주의할 점은 Props는 부모만이 자식 컴포넌트에 전달할 수 있다. 그 역은 성립하지 않는다. 따라서 Body 컴포넌트에 Props를 전달하려면 부모인 App 컴포넌트에서 전달해야 한다.
Props로 하나의 값 전달하기
App.js를 다음과 같이 수정한다.
import './App.css';
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from './component/Footer';
function App() {
const name = "이장환";
return (
<div className="App">
<Header />
<Body name={name}/>
<Footer />
</div>);
}
export default App;
전달하는 Props는 단일 객체이다. 따라서 객체 Props에는 name 프로퍼티가 추가된다.
App에서 전달한 Props를 Body 컴포넌트에서 사용해보자. Body.js에서 기존 내용을 모두 지우고 다음과 같이 작성한다.
function Body(props) {
console.log(props);
return <div className="body">{props.name}</div>;
}
export default Body;
Props로 여러 개의 값 전달하기
App에서 Body 컴포넌트에 객체 Props로 여러 개의 값을 담아 전달해보다. App 컴포넌트를 다음과 같이 수정한다.
import './App.css';
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from './component/Footer';
function App() {
const name = "이장환";
return (
<div className="App">
<Header />
<Body name={name} location={"부천시"} />
<Footer />
</div>
);
}
export default App;
Body.js를 다음과 같이 수정해 Props로 전달된 2개의 값을 사용한다.
function Body(props) {
console.log(props);
return (
<div className="body">
{props.name}은 {props.location}에 거주합니다.
</div>
);
}
export default Body;
구조 분해 할당으로 여러 개의 값 사용하기
Props로 전달된 값이 많으면, 이 값을 사용할 때마다 객체의 점 표기법을 사용해야해서 불편하다. 이는 Props는 객체이므로 구조 분해 할당하면 간편하게 사용할 수 있다. 다음과 같이 Body 컴포넌트를 수정한다.
function Body(props) {
const { name, location } = props;
console.log(name, location);
return (
<div className="body">
{props.name}은 {props.location}에 거주합니다.
</div>
);
}
export default Body;
Body 컴포넌트의 매개변수에서 구조 분해 할당하면 더 간결한 코드를 작성할 수 있다.
function Body({name, location}) {
console.log(name, location);
return (
<div className="body">
{name}은 {location}에 거주합니다.
</div>
);
}
export default Body;
스프레드 연산자로 여러 개의 값 쉽게 전달하기
반대로 부모 컴포넌트에서 Props로 전달할 값이 많으면, 값을 일일이 명시해야 하므로 불편할 뿐만 아니라 가독성도 떨어진다. 이때 Props로 값을 하나의 객체로 만든 다음, 스프레드 연산자를 활용해 전달하면 훨씬 간결하게 코드를 작성할 수 있다. 다음과 같이 App에서 Body 컴포넌트에 전달할 값을 객체로 만든 다음, 스프레드 연산자를 이용해 객체의 프로퍼티를 각각 Props 값으로 전달한다.
import './App.css';
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from './component/Footer';
function App() {
const BodyProps = {
name: "이정환",
location: "부천시",
};
return (
<div className="App">
<Header />
<Body {... BodyProps} />
<Footer />
</div>
);
}
export default App;
이벤트 처리하기
이벤트란 웹 페이지에서 일어나는 사용자의 행위다. 버튼 클릭, 페이지 스크롤, 새로고침 등이 이런 행위에 해당한다.
이벤트 핸들링과 이벤트 핸들러
이벤트 핸들링은 이벤트가 발생하면 특정 코드가 동작하도록 만드는 작업이다. 버튼을 클릭했을 때 경고 대화상자를 브라우저에 표시하는 동작이 이벤트 핸들링의 대표적인 예이다.
리액트를 사용하지 않고 HTML과 자바스크립트만으로 이벤트 핸들링을 하는 예는 페이지의 버튼을 클릭하는 이벤트가 발생하면, 함수 handleOnClick을 실행해 경고 대화상자를 페이지에 표시한다. 이떄 함수 handleOnClick을 이벤트를 처리하는 함수라는 의미에서 ‘이벤트 핸들러’라고 한다.
리액트의 이벤트 핸들링
Body 컴포넌트에 버튼을 하나 만들고, 버튼을 클릭하는 이벤트가 발생하면 실행되는 이벤트 헨들러를 만들어보자. Body 컴포넌트를 다음과 같이 수정한다.
function Body() {
function handleOnClick() {
alert("버튼을 클릭하셨군요!");
}
return (
<div className="body">
<button onClick={handleOnClick}>클릭하세요</button>
</div>
);
}
export default Body;
리액트의 이벤트 핸들링은 HTML의 이벤트 핸들링과 흡사하지만, 차이점이 몇가지 있다. 먼저 이벤트 핸들러 표기에서 HTML은 onclick이지만 리액트는 카멜 케이스 문법에 따라 onClick으로 표기한다. 그리고 Props로 전달할 값을 지정할 때처럼 onClick={} 문법으로 이벤트 핸들러를 설정한다. 또한 이벤트 핸들러를 설정할 때는 함수 호출의 결괏값을 전달하는 것이 아니라 콜백 함수처럼 함수 그 자체를 전달한다.
HTML, 자바스크립트에서는 이벤트 핸들러를 성정할 때 함수를 호출하듯 소괄호를 붙여준다. 그러나 리액트에서는 Props의 값을 설정할 때처럼 소괄호 없이 함수 이름만 명시한다.
이벤트 객체 사용하기
리액트에서는 이벤트가 발생하면 이벤트 핸들러에게 이벤트 객체를 매개변수로 전달한다. 이벤트 객체에는 이벤트가 어떤 요소에서 어떻게 발생했는지에 관한 정보가 상세히 담겨있다.
Body 컴포넌트에 2개의 버튼을 만들고, 이벤트가 발생하면 클릭한 버튼의 이름을 콘솔에 출력해보자. Body 컴포넌트를 다음과 같이 수정한다.
function Body() {
function handleOnClick(e) {
console.log(e.target.name);
}
return (
<div className="body">
<button name="A버튼" onClick={handleOnClick}>
A 버튼
</button>
<button name="B버튼" onClick={handleOnClick}>
B 버튼
</button>
</div>
);
}
export default Body;
컴포넌트와 상태
사용자의 행위나 시간 변동에 따라 값이 변하는 동적인 리액트 컴포넌트를 만들어본다. 이를 위해서 리액트의 핵심 기능 중 하나인 State를 알아야 한다.
State 이해하기
State는 상태라는 뜻이다. 리액트 컴포넌트는 State값에 따라 다른 결과를 렌더링한다.
State의 기본 사용법
useState로 State 생성하기
리액트에서는 함수 useState로 State를 생성한다. useState 문법은 다음과 같다.
const [light(State 변수), setLight](set 함수) = useState('off')(생성자(초깃값));
useState를 호출하면 2개의 요소가 담긴 배열을 반환한다. 이때 배열의 첫 번째 요소 light는 현재 상태의 값을 저장하고 있는 변수이다. 이 변수를 ‘State 변수’라고 부른다. 두 번째 요소인 setLight는 State 변수의 값을 변경하는, 즉 상태를 업데이트하는 함수이다. 이 함수를 ‘set 함수’라고 부른다. useState를 호출할 때 인수로 값을 전달하면 이 값이 State의 초깃값이 된다. 위 코드에서는 ‘off’를 전달했으므로 State 변수 light의 초깃값은 off가 된다.
Body 컴포넌트에서 숫자를 카운트할 수 있는 State 변수 count를 생성해본다. Body.js를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [count, setCount] = useState(0);
return (
<div>
<h2>{count}</h2>
</div>
);
}
export default Body;
State 변수 count를 만들 때, 함수 useState에서 인수로 0을 전달했기 떄문에 페이지에서는 0을 렌더링한다.
set함수로 State값 변경하기
컴포넌트에서 버튼을 하나 만들고, 버튼을 클릭할 때마다 State(count) 값을 1씩 늘리도록 하자. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
}
return (
<div>
<h2>{count}</h2>
<button onClick={onIncrease}>+</button>
</div>
);
}
export default Body;
set함수를 호출해 State 값을 변경하면, 변경값을 페이지에 반영하기 위해 컴포넌트를 다시 렌더링한다. 리액트에서는 이것을 ‘컴포넌트의 업데이트’라고 표현한다. 컴포넌트가 페이지에 렌더링하는 값을 컴포넌트 함수의 반환값이다. 따라서 컴포넌트를 다시 렌더링한다고 함은 컴포넌트 함수를 다시 호출한다는 의미와 같다.
State로 사용자 입력 관리하기
웹 사이트에서는 다양한 입력 폼을 제공하는데, 사용자는 이 입력 폼을 이용해 텍스트, 숫자, 날짜 등의 정보를 입력한다. HTML에서 입력 폼을 만드는 태그로는 다양한 형식의 정보를 입력할 수 있는 input 태그, 여러 옵션에서 하나를 선택하도록 드롭다운(DropDown) 목록을 보여주는 select 태그, 여러 줄의 텍스트를 입력할 수 있는 textarea 태그 등이 있다.
입력 폼은 로그인, 회원 가입, 게시판, 댓글 등이 필요한 페이지에서 자주 활용되는 웹 개발의 필수 요소이다. 리액트에서 State를 이용하면 다양한 입력 폼에서 제공되는 사용자 정보를 효과적으로 처리할 수 있다.
input태그로 텍스트 입력하기
텍스트, 전화번호, 날짜, 체크박스 등 여러 형식의 정보를 입력할 수 있는 input 태그가 만드는 폼을 다뤄본다. input 태그로 텍스트를 입력하는 폼을 하나 만들고, 사용자가 텍스트를 입력할 때마다 콘솔에 출력하는 이벤트 핸들러를 구현한다. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const handleOnChange = (e) => {
console.log(e.target.value);
};
return (
<div>
<input onChange={handleOnChange} />
</div>
);
}
export default Body;
onChage 이벤트는 사용자가 입력 폼에서 텍스트를 입력하면 바로 동작한다.
이 상태로도 텍스트 입력 폼을 이용해 사용자에서 입력을 받을 수 있다. 그러나 지금은 사용자가 입력한 텍스트가 리액트 컴포넌트가 관리하는 State에 저장되어 있지는 않다. 따라서 만약 버튼을 클릭했을 때 사용자가 입력한 텍스트를 콘솔에 출력하는 등의 동작을 수행하게 하려면 돔 API를 이용하는 등 번거로운 작업이 별도로 요구된다.
State를 하나 만들고 사용자가 폼에서 입력할 때마다 텍스트를 State 값으로 저장해본다. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
setText(e.targer.value);
};
return (
<div>
<input value={text} onChange={handleOnChange} />
<div>{text}</div>
</div>
);
}
export default Body;
input태그로 날짜 입력하기
input 태그에서 type 속성을 “date”로 설정하면 날짜 형식의 데이터를 입력할 수 있다. 이번에는 State를 이용해 날짜 형식의 데이터를 입력 정보로 받아본다. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [date, setDate] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setDate(e.target.value);
};
return (
<div>
<input type="date" value={date} onChange={handleOnChange} />
</div>
);
}
export default Body;
드롭다운 상자로 여러 옵션 중에 하나 고르기
select태그는 option태그와 함께 사용한다. 이 태그를 사용하면 드롭다운(DropDown) 메뉴는 여러 목록을 나열해 보여 주는 입력 폼이 만들어진다. 이 폼 목록에 하나를 선택하면 해당 항목을 입력할 수 있다. 드롭다운 입력 폼은 쇼핑몰 사이트에서 여러 옵션을 선택할 때 자주 활용된다. 드롭다운 입력 폼에서 입력한 값을 State로 어떨게 처리하는지 알아보자. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [option, setOption] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setOption(e.target.value);
};
return (
<div>
<select value={option} onChange={handleOnChange}>
<option key={"1번"}>1번</option>
<option key={"2번"}>2번</option>
<option key={"3번"}>3번</option>
</select>
</div>
);
}
export default Body;
글상자로 여러 줄의 텍스트 입력하기
textarea 태그는 사용자가 여러 줄의 텍스트를 입력할 때 사용하는 폼을 만든다. 이 폼은 웹 페이지에서 사용자가 자기소개와 같이 여러 줄의 내용을 입력할 때 주로 활용된다. 이 폼을 편히상 글상자라고 한다. 이번에는 리액트에서 글상자에 입력한 내용을 State로 어떻게 처리하는지 살펴본다. Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
console.log("변경된 값: ", e.target.value);
setText(e.target.value);
};
return (
<div>
<textarea value={text} onChange={handleOnChange} />
</div>
);
}
export default Body;
여러 개의 사용자 입력 관리하기
여러 개의 사용자 입력을 State로 관리하는 방법을 살펴본다. 이름, 성별, 출생 연도, 자기소개 등을 한 번에 입력할 수 있도록 Body 컴포넌트를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
const [gender, setGender] = useState("");
const [birth, setBirth] = useState("");
const [bio, setBio] = useState("");
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeGender = (e) => {
setGender(e.target.value);
};
const onChangeBirth = (e) => {
setBirth(e.target.value);
};
const onChangeBio = (e) => {
setBio(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} placeholder="이름" />
</div>
<div>
<select value={gender} onChange={onChangeGender}>
<option key={""}></option>
<option key={"남성"}>남성</option>
<option key={"여성"}>여성</option>
</select>
</div>
<div>
<input type="date" value={birth} onChangeBirth={onChangeBirth} />
</div>
<div>
<textarea value={bio} onChange={onChangeBio} />
</div>
</div>
);
}
export default Body;
Props와 State
동적으로 변하는 값인 리액트의 State 역시 일종의 값이므로 Props로 전달할 수 있다. Body에 자식 컴포넌트를 만들고, Body의 State를 Props로 전달한다. 다음과 같이 Body.js를 수정한다.
import "./Body.css";
import { useState } from "react";
function Viewer({ number }) {
return <div>{number % 2 === 0 ? <h3>짝수</h3> : <h3>홀수</h3>}</div>;
}
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number -1 );
};
return (
<div>
<h2>{number}</h2>
<Viewer number={number} />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
State와 자식 컴포넌트
부모의 State 값이 변하면 해당 State를 Props로 받은 자식 컴포넌트 역시 리엔더된다는 것을 알 수 있다. 그렇다면 부모 컴포넌트가 자식에게 State를 Props로 전달하지 않는 경우를 살펴보자. 다음과 같이 Body.js를 수정한다.
import { useState } from "react";
function Viewer() {
console.log("viewer componet update!");
return <div>Viewer</div>;
}
function Body() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(number + 1);
};
const onDecrease = () => {
setNumber(number -1 );
};
return (
<div>
<h2>{number}</h2>
<Viewer />
<div>
<button onClick={onDecrease}>-</button>
<button onClick={onIncrease}>+</button>
</div>
</div>
);
}
export default Body;
Ref
리액트의 Ref를 이용하면 돔(DOM) 요소들을 직접 조작할 수 있다. Ref는 Reference의 줄임말로 참조라는 뜻이다.
useRef 사용하기
리액트에서는 useRef라는 리액트 함수를 이용해 Ref 객체를 생성한다. 먼저 함수 useRef로 Ref를 생성하기 전에 Body.js를 다음과 같이 수정한다.
import { useState } from "react";
function Body() {
const [text, setText] = useState("");
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text);
};
return (
<div>
<input value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성완료</button>
</div>
);
}
export default Body;
useRef로 입력 폼 초기화하기
웹 서비스의 로그인 페이지는 대부분 사용자가 ID와 패스워드를 입력하고, 로그인 버튼을 클릭하면 패스워드가 올바른지 점검한다. 그런 다음 패스워드 입력 폼에서 작성한 값을 초기화한다. 리액트에서 Ref를 이용하면 이런 동작을 수행할 수 있다.
useRef를 이용해 텍스트 입력 폼을 초기화하는 법을 알아본다. Body 컴포넌트를 다음과 같이 수정한다.
import { useRef, useState } from "react";
function Body() {
const [text, setText] = useState("");
const textRef = useRef();
const handleOnChange = (e) => {
setText(e.target.value);
};
const handleOnClick = () => {
alert(text);
textRef.current.value ="";
};
return (
<div>
<input ref={textRef} value={text} onChange={handleOnChange} />
<button onClick={handleOnClick}>작성완료</button>
</div>
);
}
export default Body;
useRef로 포커스하기
웹 서비스에서는 사용자가 특정 폼에 내용을 입력하지 않거나 내용이 정한 길이보다 짧으면 해당 폼을 포커스(Focus)하여 사용자의 추가 입력을 유도한다. 리액트의 Ref 기능을 이용하면 특정 요소에 포커스 기능을 지정할 수 있다.
댓글남기기