[React] 리스트와 key / 폼과 이벤트 제어하기

컴포넌트 반복하기

리액트 코드를 짜다보면 컴포넌트를 반복해서 보여주어야 하는 일이 굉장히 많다.

아래의 예시를 보자.

웹툰마다 같은 디자인의 컴포넌트가 반복해서 보여지고 있다.

그렇다면, 하나의 컴포넌트로 구현할 수 있다.

 

이러한 컴포넌트를 앞으로 PreviewCard라고 부르겠다.

그렇다면 PreviewCard 컴포넌트를 렌더링하는 부모 컴포넌트의 코드를 잠시 떠올려보자.

export default function PreviewCardParent(){
	return(
    	<>
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
            <PreviewCard imgSrc={""} title={""} subtitle={""} author={""} />
        </>
    )
}

여기서 좀 더 많은 PreviewCard를 보여주자! 라는 요구사항이 발생한다면 어떻게 할 수 있을까?

PreviewCard를 하나하나 렌더링 하지 말고, 서버에서 내려주는 정보의 개수만큼 렌더링을 해보자.

➡ Array.prototype.map을 활용해서 여러 개의 컴포넌트를 렌더링!

 

React 사이트에서 확인할 수 있다.

https://ko.reactjs.org/docs/lists-and-keys.html

 

리스트와 Key – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

여러개의 컴포넌트 렌더링 하기

엘리먼트 모음을 만들고 중괄호 {}를 이용하여 JSX에 포함 시킬 수 있다.

아래의 JavaScript map() 함수를 사용하여 numbers 배열을 반복 실행한다. 각 항목에 대해 <li> 엘리먼트를 반환하고 엘리먼트 배열의 결과를 listItems에 저장한다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>  <li>{number}</li>);

listItems 배열을 <ul>엘리먼트 안에 포함하고 DOM에 렌더링한다.

ReactDOM.render(
  <ul>{listItems}</ul>,  document.getElementById('root')
);

 

이제 Array.prototype.map 함수를 활용하는 방법을 배웠으니 웹툰을 렌더링 하는 방법을 변경해보자.

아래와 같은 정보를 서버에서 받는다고 가정하자.

export default function PreviewCardParent(){
const cardInfos = [
    {
    	imgSrc: "",
        title: "",
        subtitle: "",
        author: "",
    },
    {
    	imgSrc: "",
        title: "",
        subtitle: "",
        author: "",
    },
    {
    	imgSrc: "",
        title: "",
        subtitle: "",
        author: "",
    },
    {
    	imgSrc: "",
        title: "",
        subtitle: "",
        author: "",
    },
];
}

그렇다면 PreviewCardParent 코드는 다음과 같이 변경할 수 있다.

return (
	<>
    	{cardInfos.map((info) => (
        	<PreviewCard
            imgSrc={info.imgSrc}
            title={info.title}
            subtitle={info.subtitle}
            author={info.author}
            />
        ))}
    </>
);

 

 

key

React props를 사용하다보면 아래와 같은 Warning message를 만날 수 있다.

자세히 살펴보면 각 child는 unique "key" prop을 가지고 있어야 한다고 명시되어 있다.

이런 Warning message를 해결하기 위해서는, 반복하여 보여주는 컴포넌트에 key값을 설정하면 된다.

const result = pickedCards.map((pickedCard) => {
	<BusinessCard info={pickedCard} key={} />
});

여기서 key는 어떤 역할을 하고, 어떤 값을 넣어주어야 하는지 살펴보자.

 

key는 왜 필요할까?

예를 들어, 다음과 같은 상황이 발생했다고 가정해보자.

return (
	{ map을 사용해서 100개의 컴포넌트를 렌더링 }
)

리액트는 컴포넌트가 리렌더링 될 때마다 map을 다시 돌며, 

이전에 렌더링 된 요소들과 비교 ➡ 어떤 요소가 변경 되었는지 파악한다.

이 때, key를 통해 요소들을 식별한다!

 

공식 문서를 살펴보자.

https://ko.reactjs.org/docs/lists-and-keys.html

 

리스트와 Key – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

Key

Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다. key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>    {number}
  </li>
);

Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분의 경우 데이터의 ID를 key로 사용한다.

const todoItems = todos.map((todo) =>
  <li key={todo.id}>    {todo.text}
  </li>
);

렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있다.

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs  <li key={index}>    {todo.text}
  </li>
);

항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다. 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있다.

Robin Pokorny’s가 작성한 글인 인덱스를 key로 사용할 경우 부정적인 영향에 대한 상세 설명을 참고.

리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용한다.

 

중요한 내용이니 리액트 사이트를 참고하자.

https://ko.reactjs.org/docs/reconciliation.html

 

재조정 (Reconciliation) – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

자식에 대한 재귀적 처리

DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.

예를 들어, 자식의 끝에 엘리먼트를 추가하면, 두 트리 사이의 변경은 잘 작동할 것이다.

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React는 두 트리에서 <li>first</li>가 일치하는 것을 확인하고, <li>second</li>가 일치하는 것을 확인한다. 그리고 마지막으로 <li>third</li>를 트리에 추가한다.

 

하지만 위와 같이 단순하게 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지 않다.

예를 들어, 아래의 두 트리 변환은 형편없이 작동한다.

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React는 <li>Duke</li>와 <li>Villanova</li> 종속 트리를 그대로 유지하는 대신 모든 자식을 변경한다.

이러한 비효율은 문제가 될 수 있다.

➡ Duke와 Villanova가 같다고 React에게 '보장'할 수 있다면 새로 rendering 할 필요가 없다. 

✨ '보장할 수 있는 방법이 key이다!

 

이러한 문제를 해결하기 위해, React는 key 속성을 지원한다.

자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.

예를 들어, 위 비효율적인 예시에 key를 추가하여 트리의 변환 작업이 효율적으로 수행되도록 수정할 수 있다.

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

이제 React는 '2014' key를 가진 엘리먼트가 새로 추가되었고, '2015'와 '2016' key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.

 

🤯 인덱스를 key로 사용 중 배열이 재배열되면 컴포넌트의 state와 관련된 문제가 발생할 수 있다.

컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용된다. 인덱스를 key로 사용하면, 항목의 순서가 바뀌었을 때 key 또한 바뀔 것이다. 그 결과로, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수도 있다.

➡ 컴포넌트 중 무언가 뒤바뀌는 게 있다면 인덱스를 key로 사용해선 안된다!

 

 

Key로 컴포넌트 추출하기

키는 주변 배열의 context에서만 의미가 있다.

예를 들어 ListItem 컴포넌트를 추출 한 경우 ListItem 안에 있는 <li> 엘리먼트가 아니라 배열의 <ListItem /> 엘리먼트가 key를 가져야 한다.

예시: 잘못된 Key 사용법

function ListItem(props) {
  const value = props.value;
  return (
    // 틀렸습니다! 여기에는 key를 지정할 필요가 없습니다.    <li key={value.toString()}>      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 틀렸습니다! 여기에 key를 지정해야 합니다.    <ListItem value={number} />  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

예시: 올바른 Key 사용법

function ListItem(props) {
  // 맞습니다! 여기에는 key를 지정할 필요가 없습니다.  return <li>{props.value}</li>;}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 맞습니다! 배열 안에 key를 지정해야 합니다.    <ListItem key={number.toString()} value={number} />  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

 

Key는 형제 사이에서만 고유한 값이어야 한다.

Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없다. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.

function Blog(props) {
  const sidebar = (    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>    <div key={post.id}>      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}      <hr />
      {content}    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

 

React에서 key는 힌트를 제공하지만 컴포넌트로 전달하지는 않다. 컴포넌트에서 key와 동일한 값이 필요하면 다른 이름의 prop으로 명시적으로 전달한다.

const content = posts.map((post) =>
  <Post
    key={post.id}    id={post.id}    title={post.title} />
);

위 예시에서 Post 컴포넌트는 props.id를 읽을 수 있지만 props.key는 읽을 수 없다.

 

함수 컴포넌트에서 이벤트 처리하기

리액트 공식 문서는 아래와 같다.

https://ko.reactjs.org/docs/handling-events.html

 

이벤트 처리하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사하다.

몇 가지 문법 차이는 다음과 같다.

  • React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용한다.
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.

예를 들어, HTML은 다음과 같다.

<button onclick="activateLasers()">
  Activate Lasers
</button>

React에서는 약간 다르다.

<button onClick={activateLasers}>  Activate Lasers
</button>

또 다른 차이점으로, React에서는 false를 반환해도 기본 동작을 방지할 수 없다.

반드시 preventDefault를 명시적으로 호출해야 한다.

예를 들어, 일반 HTML에서 폼을 제출할 때 가지고 있는 기본 동작을 방지하기 위해 다음과 같은 코드를 작성할 수 있다.

<form onsubmit="console.log('You clicked submit.'); return false">
  <button type="submit">Submit</button>
</form>

React에서는 다음과 같이 작성할 수 있다.

function Form() {
  function handleSubmit(e) {
    e.preventDefault();    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

여기서 e는 합성 이벤트이다. React는 W3C 명세에 따라 합성 이벤트를 정의하기 때문에 브라우저 호환성에 대해 걱정할 필요가 없다. React 이벤트는 브라우저 고유 이벤트와 정확히 동일하게 동작하지는 않는다.

더 자세한 사항은 합성 이벤트을 참고하자.

 

합성 이벤트에 관련된 공식 문서 https://ko.reactjs.org/docs/events.html

 

합성 이벤트(SyntheticEvent) – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

합성 이벤트(SyntheticEvent)

React는 UI Library이다.

그렇기 때문에 DOM 내에 기본적인 이벤트들을 다룰 수 있어야 한다.

➡ 웹 표준인 Event 인터페이스를 래핑하고 있다. (감싸고 있다.)

기본 Event 인터페이스를 래핑한 이벤트합성 이벤트라고 한다.

더보기

이벤트 핸들러는 모든 브라우저에서 이벤트를 동일하게 처리하기 위한 이벤트 래퍼 SyntheticEvent 객체를 전달받는다. stopPropagation()  preventDefault()를 포함해서 인터페이스는 브라우저의 고유 이벤트와 같지만 모든 브라우저에서 동일하게 동작한다.

 

자세한 내용은 공식 문서를 참고하자.

 

React를 사용할 때 DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없다. 대신, 엘리먼트가 처음 렌더링될 때 리스너를 제공하면 된다.

 

아래의 예시 코드를 보자

import React, { useState } from "react";

const Banner = () => {
  const [visible, setVisible] = useState(true);
  const getCoupon = () => {
    alert("10% 할인 쿠폰을 받으셨습니다.");
  };
  const closeBanner = () => {
    setVisible(false); // 비동기
  };

  return visible ? (
    <div
      style={{
        backgroundColor: "orange",
        fontWeight: "bold",
        height: "50px",
        display: "flex",
        justifyContent: "space-around",
        alignItems: "center",
      }}
      onClick={getCoupon}
    >
      이 곳을 클릭해서 쿠폰을 받아가세요.
      <button onClick={closeBanner}>닫기</button>
    </div>
  ) : null;
};

export default Banner;

닫기 button을 클릭하면, Banner alert가 먼저 뜬 다음 배너가 사라진다.

이벤트 버블링 때문에 이러한 현상이 발생하게 되는데, 이벤트 버블링이라면 closeBanner ➡ getCoupon 순으로 실행되어야 하는 거 아닐까?

 

원인은 setVisible 함수가 비동기이기 때문이다.

setVisivle은 비동기적으로 실행이 되고, alert는 동기적으로 실행이 되기 때문에 alert가 먼저 실행이 되는 것이다.

 

그렇다면 이러한 이벤트가 퍼지는 것을 막기 위해서는 어떻게 해야 할까?

합성 이벤트를 사용하면 된다.

 const closeBanner = (e) => {
    e.stopPropagation();
    setVisible(false); // 비동기
  };

 

클래스 컴포넌트에서 이벤트 처리하기

ES6 클래스를 사용하여 컴포넌트를 정의할 때, 일반적인 패턴은 이벤트 핸들러를 클래스의 메서드로 만드는 것이다.

다음 Toggle 컴포넌트는 사용자가 “ON”과 “OFF” 상태를 토글 할 수 있는 버튼을 렌더링한다.

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

 

JSX 콜백 안에서 this의 의미에 대해 주의해야 한다.

JavaScript에서 클래스 메서드는 기본적으로 바인딩되어 있지 않다. 

this.handleClick을 바인딩하지 않고 onClick에 전달하였다면, 함수가 실제 호출될 때 this는 undefined가 된다.

 

이는 React만의 특수한 동작이 아니며, JavaScript에서 함수가 작동하는 방식의 일부이다.   일반적으로 onClick={this.handleClick}과 같이 뒤에 ()를 사용하지 않고 메서드를 참조할 경우, 해당 메서드를 바인딩 해야 한다.

 

bind를 호출하는 것이 불편하다면, 이를 해결할 수 있는 두 가지 방법이 있습니다.

실험적인 퍼블릭 클래스 필드 문법을 사용하고 있다면, 클래스 필드를 사용하여 콜백을 올바르게 바인딩할 수 있다.

class LoggingButton extends React.Component {
  // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.  // 주의: 이 문법은 *실험적인* 문법입니다.  handleClick = () => {    console.log('this is:', this);  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

Create React App에서는 이 문법이 기본적으로 설정되어 있습니다.

클래스 필드 문법을 사용하고 있지 않다면, 콜백에 화살표 함수를 사용하는 방법도 있습니다.

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.    return (      <button onClick={() => this.handleClick()}>        Click me
      </button>
    );
  }
}

이 문법의 문제점은 LoggingButton이 렌더링될 때마다 다른 콜백이 생성된다는 것이다.

대부분의 경우 문제가 되지 않으나, 콜백이 하위 컴포넌트에 props로서 전달된다면 그 컴포넌트들은 추가로 다시 렌더링을 수행할 수도 있다.

이러한 종류의 성능 문제를 피하고자, 생성자 안에서 바인딩하거나 클래스 필드 문법을 사용하는 것을 권장한다.

 

폼과 제어 컴포넌트

Form (폼)

폼 요소 중 대표적인 것이 input 태그이다.

 

HTML 폼 엘리먼트는 폼 엘리먼트 자체가 내부 상태를 가지기 때문에, React의 다른 DOM 엘리먼트와 다르게 동작한다.

예를 들어, 순수한 HTML에서 이 폼은 name을 입력받는다.

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

이 폼은 사용자가 폼을 제출하면 새로운 페이지로 이동하는 기본 HTML 폼 동작을 수행한다. React에서 동일한 동작을 원한다면 그대로 사용하면 된다.

그러나 대부분의 경우, JavaScript 함수로 폼의 제출을 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리합니다. 이를 위한 표준 방식은 “제어 컴포넌트 (controlled components)“라고 불리는 기술을 이용하는 것이다.

 

제어 컴포넌트 (Controlled Component)

HTML에서 <input>, <textarea>, <select>와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트한다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트된다.

 

우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어한다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 한다.

 

예를 들어, 이전 예시가 전송될 때 이름을 기록하길 원한다면 폼을 제어 컴포넌트 (controlled component)로 작성할 수 있다.

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>
    );
  }
}

 

textarea 태그

HTML에서 <textarea> 엘리먼트는 텍스트를 자식으로 정의합니다.

<textarea>
  Hello there, this is some text in a text area
</textarea>

React에서 <textarea> value 어트리뷰트를 대신 사용합니다. 이렇게하면 <textarea>를 사용하는 폼은 한 줄 입력을 사용하는 폼과 비슷하게 작성할 수 있습니다.

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

this.state.value를 생성자에서 초기화하므로 textarea는 일부 텍스트를 가진채 시작되는 점을 주의해주세요.

 

select 태그

HTML에서 <select>는 드롭 다운 목록을 만듭니다. 예를 들어, 이 HTML은 과일 드롭 다운 목록을 만듭니다.

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

selected 옵션이 있으므로 Coconut 옵션이 초기값이 되는 점을 주의해주세요. React에서는 selected 어트리뷰트를 사용하는 대신 최상단 select태그에 value 어트리뷰트를 사용합니다. 한 곳에서 업데이트만 하면되기 때문에 제어 컴포넌트에서 사용하기 더 편합니다. 아래는 예시입니다.

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  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={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>
    );
  }
}

 

전반적으로 <input type="text">, <textarea>  <select> 모두 매우 비슷하게 동작합니다.

모두 제어 컴포넌트를 구현하는데 value 어트리뷰트를 허용합니다.

주의

select 태그에 multiple 옵션을 허용한다면 value 어트리뷰트에 배열을 전달할 수 있습니다.

<select multiple={true} value={['B', 'C']}>

 

폼 - 다중 입력 다루기

다중 입력 제어하기

여러 개의 input 태그를 가지고 있는 form은 어떻게 만들 수 있을까?

import React, { useState } from "react";

function SimpleForm() {
  const [nickname, setNickname] = useState("");
  const [password, setPassword] = useState("");

  const handleChange = (e) => {
    if(e.target.name === "nickname") return setNickname(e.target.value);
    else return setPassword(e.target.value);
  };

  const hadleSubmit = (e) => {
    // submit의 기본 기능이 동작하지 않도록 함
    e.preventDefault();    
    alert(`nickname : ${nickname}, password : ${password}`);
  };

  return (
    <form onSubmit={hadleSubmit}>
      <label>닉네임 :</label>
      <input
        type="text"
        name="nickname"
        onChange={handleChange}
        value={nickname}
      />
      <br/>
      <label>패스워드 :</label>
      <input
        type="text"
        name="password"
        onChange={handleChange}
        value={password}
      />
      <input type="submit" value="제출" />
    </form>
  );
}

export default SimpleForm;

이런 식으로 만들 수 있을 것이다.

여기서 input 태그가 더 늘어나게 된다면 코드는 더 길어지게 될 것이고, 가독성이 떨어질 우려가 있다.

 

그렇다면 어떻게 수정할 수 있을까?

state를 객체로 생성하여 하나로 다룰 수 있다.

import React, { useState } from "react";

function SimpleForm() {
  const [userInputs, setUserInputs] = useState({
    nickname: "",
    password: "",
  });

  const handleChange = (e) => {
    setUserInputs({ ...userInputs, [e.target.name]: e.target.value });
  };

  const hadleSubmit = (e) => {
    // submit의 기본 기능이 동작하지 않도록 함
    e.preventDefault();
    const { nickname, password } = userInputs;
    alert(`nickname : ${nickname}, password : ${password}`);
  };

  return (
    <form onSubmit={hadleSubmit}>
      <label>닉네임 :</label>
      <input
        type="text"
        name="nickname"
        onChange={handleChange}
        value={userInputs.nickname}
      />
      <br />
      <label>패스워드 :</label>
      <input
        type="text"
        name="password"
        onChange={handleChange}
        value={userInputs.password}
      />
      <input type="submit" value="제출" />
    </form>
  );
}

export default SimpleForm;

 

비제어 컴포넌트

 

ref

 

 


나중에 확인해야 할 것

다시 듣기

중간 미션

2. 컴포넌트 반복하기 실습 

3. key 마지막 강의에서 중간 미션 중 key 추가하기

 

'React' 카테고리의 다른 글

[React] 전역 상태 관리  (0) 2022.07.27
[React] 비동기 프로그래밍과 API 호출  (0) 2022.07.26
[React] Lifecycle와 useEffect()  (0) 2022.07.25
[React] State  (0) 2022.07.09
리액트 기본 문법(JSX)과 컴포넌트 소개  (0) 2022.07.01