React 폼
HTML 폼 엘리먼트는 폼 엘리먼트 자체가 내부 상태(state)를 가지기 때문에, React의 다른 DOM 엘리먼트와 다르게 동작합니다. -React Document
예를 들어 <input> 폼은 특정한 정보를 입력받은 뒤 폼을 제출하면 새로운 페이지로 이동한다. 그러나 대부분의 경우 JavaScript 함수로 제출을 처리하고 폼에 입력된 데이터에 접근하는 것이 편리하다. 이를 위한 표준 방식은 "제어 컴포넌트(Controlled components)"라고 불리는 기술을 이용하는 것이다.
제어 컴포넌트(Controlled Component)
<input>, <textarea>, <select>와 같은 HTML 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트한다. React에서는 state와 setState()를 이용해 컴포넌트의 state를 관리하고 업데이트한다.
리액트에서는 이 state들을 '신뢰 가능한 단일 출처(single source of truth)'로 만들어 두 요소를 결합할 수 있다. 그러면 리액트 컴포넌트가 직접 폼에 발생하는 사용자 입력값을 제어한다. 이렇게 React에 의해 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트라고 한다.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
value 어트리뷰트(attribute)는 폼 엘리먼트에 항상 설정(event.target.value)되기 때문에 React state는 신뢰 가능한 단일 출처가 된다. 모든 키 입력에서 handleChange 함수가 동작하기 때문에 사용자가 입력할 때 보이는 값이 바로 업데이트된다. 코드를 조금 더 작성하는 대신 다른 UI 엘리먼트에 input 값을 전달하거나 다른 이벤트 핸들러에서 임의로 값(value)을 재설정할 수 있다.
textarea 태그
HTML textarea 엘리먼트는 텍스트를 자식으로 정의한다.
<textarea>
Hello there, this is some text in a text area
</textarea>
React에서의 <textarea>는 value 어트리뷰트를 자식 요소 대신 사용한다. 이렇게 하면 textarea를 한 줄 입력을 사용하는 폼과 비슷하게 작성할 수 있다.
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
select 태그
HTML에서 <select>는 드롭 다운 목록을 만들 수 있다. 이때 선택된 값은 selected라는 어트리뷰트를 이용한다.
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
React에서는 selected 어트리뷰트 대신 최상단 select 태그에 value 어트리뷰트를 사용한다. 이는 select 한 곳에서만 업데이트하면 되기 때문에 사용하기 편리하다.
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
//!!여기는 select에 value가 있다!!
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
* multiple 옵션을 허용한 select 태그라면 value 어트리뷰트에 배열을 전달할 수 있다.
<select multiple={true} value={['B', 'C']}>
비제어 컴포넌트
모든 입력 상태에 대해 setState를 이용해서 관리하는 것이 번거로울 경우가 있다. 이럴 때는 ref를 이용해 비제어 컴포넌트라는 기술을 사용할 수 있다. 짧게 공부한 게 있으나 추후에 ref와 함께 다시 다룰 예정이다.
State 끌어올리기
애플리케이션을 구성하다 보면 컴포넌트 데이터에 대한 변경사항을 다른 형제(또는 사촌..?) 요소에 반영해야 할 때가 있다. 이럴 때는 가장 가까운 공통 조상 요소로 state를 끌어올려야 한다. 이렇게 끌어올려진 조상 컴포넌트를 "진리의 원천(source of truth)"라고 한다(공식 리액트 문서 번역이다...). 이를 필요한 컴포넌트 들에 내려보내 주면 해당 컴포넌트 들은 일관된 값을 유지할 수 있다.
공식 문서에는 긴 예시와 함께 방법이 자세하게 정리되어 있었지만 결국 핵심은 간단하다.
- 자신이 state로 관리하던 것을 부모(또는 조상) 요소에게로부터 props로 전달받는다.
- 자신이 setState로 state를 조작하던 것은 마찬가지로 부모(또는 조상) 요소로부터 Handling Function을 props로 전달받는다.
위 2가지를 이용하면 State를 부모 요소로 끌어올릴 수 있다!
React 상속(Inheritance) vs 합성(Composition)
React는 강력한 합성 모델을 가지고 있으며, 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋습니다.
간혹 컴포넌트에 어떤 자식 엘리먼트가 들어올지 예상할 수 없는 경우가 있다. 이럴 경우 컴포넌트에 children prop을 사용하여 자식 엘리먼트를 출력에 그대로 전달할 수 있다.
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
또한 어트리뷰트에 props로 컴포넌트를 전달할 수도 있다!
특수화
어떤 컴포넌트의 "특수한 경우" 역시 상속 대신 합성을 이용하여 해결할 수 있다.
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
상속은?
Facebook에서는 수천 개의 React 컴포넌트를 사용하지만, 컴포넌트를 상속 계층 구조로 작성을 권장할만한 사례를 아직 찾지 못했습니다.
props와 합성은 명시적이고 안전한 방법으로 컴포넌트의 모양과 동작을 커스터마이징 하는데 필요한 모든 유연성을 제공합니다. 컴포넌트가 원시 타입의 값, React 엘리먼트 혹은 함수 등 어떠한 props도 받을 수 있다는 것을 기억하세요.
UI가 아닌 기능을 여러 컴포넌트에서 재사용하기를 원한다면, 별도의 JavaScript 모듈로 분리하는 것이 좋습니다. 컴포넌트에서 해당 함수, 객체, 클래스 등을 import 하여 사용할 수 있습니다. 상속받을 필요 없이 말이죠. - React Document
Hooks
위에서 살펴본 setState()와 생명주기 관리는 컴포넌트를 class의 형태로 만들어 별도의 메서드를 정의해서 사용해야 했다. React 팀에서는 이러한 문제를 해결하기 위해 최신 React에 Hooks라는 아주 강력한 요소를 추가했다.
useState
useState를 이용하면 함수형 컴포넌트에서도 state, setState를 사용할 수 있다. useState는 다음과 같이 ¡¡¡사용한다.
import React, { useState } from 'react';
function Example() {
// 새로운 state 변수를 선언하고, count라 부르겠습니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
useEffect를 사용하면 클래스 내부에 따로 메서드를 선언해 관리해야 했던 생명주기와 관련된 메서드(componentDidMount, componentDidUpdate, componentWillUnmount)를 함수형 컴포넌트에서도 사용할 수 있다.
Hooks와 관련하여서는 더 많은 것을 공부하였으나 조금 더 공부하여 Hooks는 따로 글을 써볼 생각이다.
전역 상태 관리와 Redux, CSS in JS, CSS Module에 관해서도 맛만 봤으나 추가 공부가 필요하다.
이 정도면 정복했나..?
저번 글과 이번 글까지의 내용이 내가 저번 한 주동안 공부한 리액트의 내용이다. 추가로 노마드 코더의 영화 정보 관리 사이트 클론 코딩도 무리 없이 진행하였으니 이 정도면 기본적인 사이트 구축은 가능하게 되었다고 할 수 있을 것 같다. 일주일 만에 이 정도면 나쁘지 않게 공부했다고 생각한다😁
'Dev > JS Family, HTML, CSS' 카테고리의 다른 글
[TypeScript] 타입스크립트 입문 (4) - 제네릭, 고급 타입 다루기 (5) | 2022.09.14 |
---|---|
[TypeScript] 타입스크립트 입문(3) - 인터페이스, 타입 가드, 열거형, 유니온 타입 (0) | 2022.09.12 |
[TypeScript] 타입스크립트 입문 (2) - 기본 타입, 클래스 (0) | 2022.09.09 |
[TypeScript] 타입스크립트 입문(1) (2) | 2022.09.04 |
[React] 일주일만에 리액트 정복하기! (1) (1) | 2022.08.02 |