TIL React 공식문서 파헤치기

flobeeee·2021년 2월 10일
0

Today I Learned

목록 보기
6/35
post-thumbnail

이번 설연휴동안 리액트 공식문서에 있는 예시들을 다 직접 해볼 계획이다.
과제구현은 어느정도 진행됐지만, 아직까진 이해도가 부족하다.
이 글을 완성하면 리액트 기본은 안다고 할 수 있지 않을까..!
이 글은 공식문서를 기반으로 설명을 덧붙인 내용이다.

리액트 이해하기 좋은 유튜브 영상

🌀 시작하기

[새로운 React 앱 만들기]

npx create-react-app my-app
cd my-app
npm start

우선 설치명령어로 리액트 설치.
npm start를 하면, 창이뜨면서 리액트 아이콘이 빙글빙글 돌아가는데, 그게 정상임.
아이콘 밑에 Edit src/App.js and save to reload. 라고 명시되어있음.
src 폴더 App.js 파일을 수정하면서 결과물을 바로 확인하면 됨.

주요개념

🌀 1. Hello World

그런데 나는 index.js 를 건들였다.

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

왜냐하면 index.js 상단에 import 가 설정되어 있어서!

import React from 'react';
import ReactDOM from 'react-dom';

정리하면,

import React from 'react'; // 이렇게 리액트를 사용할 것이다! 명시해두고
import ReactDOM from 'react-dom';

ReactDOM.render( // 이부분을 통해서 화면에 보여지는 것 같다
  <h1>Hello, world!</h1>, // html 처럼 사용하는 JSX를 사용
  document.getElementById('root') // 이 부분은 기본적으로 있어야 한다. 없으면 에러.
); 

이 코드만으로도 우리는 Hello, world! 가 잘 찍히는 것을 확인.

🌀 2. JSX 소개

const element = <h1>Hello, world!</h1>;

이 문법은 JSX라고 하며, JS를 확장한 문법이다.

  • JSX에 표현식 포함하기
import React from 'react';
import ReactDOM from 'react-dom';

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>; // JSX 는 {}를 이용해 JS변수를 넣는다.

ReactDOM.render(
  element, // 이런식으로 JSX를 끌어올 수 있구나!
  document.getElementById('root')
);

이렇게 코드를 작성하면 Hello, Josh Perez 를 화면에서 확인할 수 있다.

JSX 의 {} 안에는 유효한 모든 JS표현식 을 넣을 수 있다고 명시되어있다.
링크에 들어가보면, 구조분해할당과 삼항연산자 등을 확인할 수있다.


이번 예시는 JS 함수의 호출 결과인 formatName(user)<h1> 엘리먼트에 포함했다.

import React from 'react';
import ReactDOM from 'react-dom';

function formatName(user) { // 4. 결과 리턴
  return user.firstName + ' ' + user.lastName;
}

const user = { // 3. user 값 확인(함수의 파라미터로 사용)
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}! // 2. formatName 함수 실행
  </h1>
);

ReactDOM.render(
  element, // 1. 여기서 엘리멘트 부르기
  document.getElementById('root')
);

가독성을 좋게 하기 위해 JSX를 여러 줄로 나눴습니다. 필수는 아니지만, 이 작업을 수행할 때 자동 세미콜론 삽입을 피하고자 괄호로 묶는 것을 권장합니다. (공식문서)

이렇게 명시되어 있는 것을 보니, JSX 쓸때 () 로 묶는 게 좋을듯하다.


  • JSX 도 표현식입니다.

컴파일이 끝나면, JSX 표현식이 정규 JavaScript 함수 호출이 되고 JavaScript 객체로 인식됩니다.

즉, JSX를 if 구문 및 for loop 안에 사용하고, 변수에 할당하고, 인자로서 받아들이고, 함수로부터 반환할 수 있습니다. (공식문서)

if 문도 잘 된다는 뜻인 것 같다. (단 JSX 내부에서는 삼항연산자 사용)

import React from 'react';
import ReactDOM from 'react-dom';

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = { // 이부분을 const user = null; 로 바꾸면
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    {getGreeting(user)}
  </h1>
);

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>; // 이부분이 활성화된다.
}

ReactDOM.render(
  element,
  document.getElementById('root')
);

  • JSX 속성 정의

속성에 따옴표를 이용해 문자열 리터럴을 정의할 수 있습니다.
const element = <div tabIndex="0"></div>;

중괄호를 사용하여 어트리뷰트에 JavaScript 표현식을 삽입할 수도 있습니다.
const element = <img src={user.avatarUrl}></img>;

  • 경고

JSX는 HTML보다는 JavaScript에 가깝기 때문에, React DOM은 HTML 어트리뷰트 이름 대신 camelCase 프로퍼티 명명 규칙을 사용합니다.

예를 들어, JSX에서 classclassName가 되고 tabindextabIndex가 됩니다.


  • JSX로 자식 정의
const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

자식을 정의할 수 있지만, 형제끼리 정의할 수 없다.
꼭 부모태그로 감싸줘야한다. 그게 JSX규칙임.


  • JSX는 객체를 표현합니다.
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

//위, 아래 예시는 동일합니다.(오.. 신기하다)

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

🌀 3. 엘리멘트 렌더링

엘리먼트는 React 앱의 가장 작은 단위다.(공식문서)
(엘리먼트와 컴포넌트는 다름! 엘리먼트는 컴포넌트의 구성요소!)

HTML 파일 어딘가에 <div>가 있다고 가정해 봅시다.

<div id="root"></div> (public폴더 index.html 보면 있다)

이 안에 들어가는 모든 엘리먼트를 React DOM에서 관리하기 때문에 이것을 “루트(root)” DOM 노드라고 부릅니다.

React로 구현된 애플리케이션은 일반적으로 하나의 루트 DOM 노드가 있습니다. React를 기존 앱에 통합하려는 경우 원하는 만큼 많은 수의 독립된 루트 DOM 노드가 있을 수 있습니다.

React 엘리먼트를 루트 DOM 노드에 렌더링하려면 둘 다 ReactDOM.render()로 전달하면 됩니다.(공식문서)

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

  • 엘리먼트 두개를 렌더링 해보았다. (두개의 파일을 이용했다.)

App.js

function App() {
  const element = <h1>Hello, world</h1>
  const element2 = <h1>Hello, JSX</h1>
  return (
    <div> // 이렇게 부모태그로 감싸줘야 한다.(규칙임)
    {element}
    {element2}
    </div>
  )
}

export default App;

index.js (맨처음 기본세팅이 이렇게 되어있다.)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

이렇게 두 파일을 작성하고 화면을 보면
Hello, world
Hello, JSX
두 문장을 확인할 수 있다.


  • 렌더링 된 엘리먼트 업데이트 하기

React 엘리먼트는 불변객체입니다. 엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없습니다. 엘리먼트는 영화에서 하나의 프레임과 같이 특정 시점의 UI를 보여줍니다.

UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 ReactDOM.render()로 전달하는 것입니다.(공식문서)

index.js

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
} // 렌더링이 포함되어있다 (=1초마다 렌더링된다)

setInterval(tick, 1000); // 1초마다 tick이 실행되는데

화면을 보면 실시간으로 시간이 바뀌는 것을 볼 수 있다.
(내용을 바꿔주려면 전체를 렌더하는 방법뿐일까 ?)


  • 변경된 부분만 업데이트하기

React DOM은 해당 엘리먼트와 그 자식 엘리먼트를 이전의 엘리먼트와 비교하고 DOM을 원하는 상태로 만드는데 필요한 경우에만 DOM을 업데이트합니다.

매초 전체 UI를 다시 그리도록 엘리먼트를 만들었지만 React DOM은 내용이 변경된 텍스트 노드만 업데이트했습니다.(공식문서)

(와.. 전체를 렌더링하는 것처럼 코드를 짰지만, 실제로는 변경되는 부분만 바뀐다니.. 대단하다!)

🌀 4. Components and Props

컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 살펴볼 수 있습니다.

개념적으로 컴포넌트는 JavaScript 함수와 유사합니다. “props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.

컴포넌트를 정의하는 가장 간단한 방법은 JavaScript 함수를 작성하는 것입니다.(공식문서)

  • 함수 컴포넌트와 클래스 컴포넌트
// 함수 컴포넌트
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// React의 관점에서 볼 때 위 두 가지 유형의 컴포넌트는 동일

// 클래스 컴포넌트
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
  • 함수컴포넌트 : 데이터를 가진 하나의 “props” (props는 속성을 나타내는 데이터입니다) 객체 인자를 받은 후 React 엘리먼트를 반환하므로 유효한 React 컴포넌트입니다. 이러한 컴포넌트는 JavaScript 함수이기 때문에 말 그대로 “함수 컴포넌트”라고 호칭합니다.

  • 클래스컴포넌트 : 또한 ES6 class를 사용하여 컴포넌트를 정의할 수 있습니다.


  • 컴포넌트 렌더링

이전까지는 React 엘리먼트를 DOM 태그로 나타냈습니다.
const element = <div />;

React 엘리먼트는 사용자 정의 컴포넌트로도 나타낼 수 있습니다.
const element = <Welcome name="Sara" />;
React가 사용자 정의 컴포넌트로 작성한 엘리먼트를 발견하면 JSX 어트리뷰트와 자식을 해당 컴포넌트에 단일 객체로 전달합니다. 이 객체를 “props”라고 합니다.

import React from 'react';
import ReactDOM from 'react-dom';

function Welcome(props) { // 3. 여기 해당 컴포넌트에 전달
  return <h1>Hello, {props.name}</h1>; // 객체처럼 쓸 수 있음
}

const element = <Welcome name="Sara" />; // 2. 사용자정의컴포넌트라 속성와 자식을

ReactDOM.render(
  element, // 1. element 불러오기
  document.getElementById('root')
);

이렇게 하면, 화면에 Hello, Sara 가 보인다.
(보통 사용자정의 컴포넌트속성과 출력내용을 나누는 것 같다)

  • 주의
    컴포넌트의 이름은 항상 대문자로 시작합니다.
    React는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리합니다.
    예를 들어 <div />는 HTML div 태그를 나타내지만, <Welcome />은 컴포넌트를 나타내며 범위 안에 Welcome이 있어야 합니다.

(리액트는 소문자 대문자로 HTML과 컴포넌트를 구분한다!)


  • 컴포넌트 합성

컴포넌트는 자신의 출력에 다른 컴포넌트를 참조할 수 있습니다. 이는 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있음을 의미합니다. React 앱에서는 버튼, 폼, 다이얼로그, 화면 등의 모든 것들이 흔히 컴포넌트로 표현됩니다.

예를 들어 Welcome을 여러 번 렌더링하는 App 컴포넌트를 만들 수 있습니다. (공식문서)

import React from 'react';
import ReactDOM from 'react-dom';

function Welcome(props) { // 3. 여기 해당 컴포넌트에 전달
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" /> // 2. 사용자 정의 컴포넌트라 속성을 
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />, // 1. APP 컴포넌트를 부르고
  document.getElementById('root')
);

화면에
Hello, Sara
Hello, Cahal
Hello, Edite
이렇게 잘 보인다.

(속성따로 출력따로 컴포넌트를 작성할 수 있구나..)


  • 컴포넌트 추출
import React from 'react';
import ReactDOM from 'react-dom';

function formatDate(date) {
  return date.toLocaleDateString();
}

function Comment(props) { // 3. 2번에서 가져온 내용들을 적용
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img
          className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">
        {formatDate(props.date)} // 4. 날짜를 정돈하는 함수 부름
      </div>
    </div>
  );
}

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment // 1. Comment 컴포넌트를 부르는데
    date={comment.date} // 2. 속성에 comment 객체 내용을 끌어옴
    text={comment.text}
    author={comment.author}
  />,
  document.getElementById('root')
);

이 컴포넌트는 author(객체), text(문자열) 및 date(날짜)를 props로 받은 후 소셜 미디어 웹 사이트의 코멘트를 나타냅니다.

오 딱봐도 복잡해보인다.

이걸 아래 예문처럼 컴포넌트로 추출해 재사용성을 늘릴 수 있다.

import React from 'react';
import ReactDOM from 'react-dom';

function formatDate(date) {
  return date.toLocaleDateString();
}

function Avatar(props) {
  return (
    <img
      className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">{props.user.name}</div>
    </div>
  );
}

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author}
  />,
  document.getElementById('root')
);

예문보면서 과정을 이해하고, 직접 추출을 시도해보니 잘된다!
내용을 그대로 가져와서 새로운 컴포넌트에 넣고, prop를 조정해주면 된다.


  • props 는 읽기 적용입니다.

함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 됩니다.

function sum(a, b) {
  return a + b;
}
//이런 함수들은 순수 함수라고 호칭합니다. 
//입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하기 때문입니다.

//반면에 다음 함수는 자신의 입력값을 변경하기 때문에 순수 함수가 아닙니다.
function withdraw(account, amount) {
  account.total -= amount;
}

모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 합니다. (규칙)

물론 애플리케이션 UI는 동적이며 시간에 따라 변합니다. 다음 장에서는 “state”라는 새로운 개념을 소개합니다. React 컴포넌트는 state를 통해 위 규칙을 위반하지 않고 사용자 액션, 네트워크 응답 및 다른 요소에 대한 응답으로 시간에 따라 자신의 출력값을 변경할 수 있습니다.(공식문서)

(props 는 고정된 값, state 는 변동가능한 값으로 이해하면 되나)

🌀 5. State and Liftcycle

3번 주제에서 다뤄본 째깍거리는 시계 예시를 다시 살펴보겠습니다. 엘리먼트 렌더링에서는 UI를 업데이트하는 한 가지 방법만 배웠으며, 렌더링 된 출력값을 변경하기 위해 ReactDOM.render()를 호출했습니다.

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

이 섹션에서는 Clock 컴포넌트를 완전히 재사용하고 캡슐화하는 방법을 배울 것입니다. 이 컴포넌트는 스스로 타이머를 설정할 것이고 매초 스스로 업데이트할 것입니다.

시계가 생긴 것에 따라 캡슐화하는 것으로 시작할 수 있습니다.

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() { // 렌더링 코드를 아예 반복되는 함수안에 집어넣음
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

그러나 여기에는 중요한 요건이 누락되어 있습니다. Clock이 타이머를 설정하고 매초 UI를 업데이트하는 것이 Clock의 구현 세부사항이 되어야 합니다.

이상적으로 한 번만 코드를 작성하고 Clock이 스스로 업데이트하도록 만들려고 합니다.

이것을 구현하기 위해서 Clock 컴포넌트에 “state”를 추가해야 합니다.

State는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어됩니다.

(아마도 setInterval(tick, 1000); 이 따로 있어서 그런것같다)


  • 함수에서 클래스로 변환하기

다섯 단계로 Clock과 같은 함수 컴포넌트를 클래스로 변환할 수 있습니다.

  1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
  2. render()라고 불리는 빈 메서드를 추가합니다.
  3. 함수의 내용을 render() 메서드 안으로 옮깁니다.
  4. render() 내용 안에 있는 props를 this.props로 변경합니다.
  5. 남아있는 빈 함수 선언을 삭제합니다.
import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()}/>,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Clock은 이제 함수가 아닌 클래스로 정의됩니다.

render 메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 <Clock />을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용됩니다. 이것은 로컬 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해줍니다.

(잘모르겠는데, 효율적으로 렌더링한다는 것 같다. 더 봐야겠다.)


  • 클래스에 로컬 Stste 추가하기
  1. render() 메서드 안에 있는 this.props.date를 this.state.date로 변경합니다.
  2. 초기 this.state를 지정하는 class constructor를 추가합니다.
  3. <Clock /> 요소에서 date prop을 삭제합니다.
import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) { // 2번 수정사항
    super(props);
    this.state = {date: new Date()}
  }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2> // 1번 수정사항
      </div>
    )
  }
}


ReactDOM.render(
  <Clock />, // 3번 수정사항
  document.getElementById('root')
);

이렇게 하면 시간이 움직이지않음. 시간움직이는 건 다음단계!


  • 생명주기 메서드를 클래스에 추가하기

많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요합니다.

Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 합니다. 이것은 React에서 “마운팅”이라고 합니다.

또한 Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제하려고 합니다.

컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있습니다.

이러한 메서드들은 “생명주기 메서드”라고 불립니다.(공식문서)

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) { // 2. 우선 constructor 실행
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() { // 생명주기 메서드 (존재하는 메서드임) 
    this.timerID = setInterval( // 타이머ID 어떻게 저장하는지 주의!
      () => this.tick(), // 4. 렌더링되면 이 메소드 실행
      1000
    );
  }

  componentWillUnmount() { // 생명주기 메서드 (존재하는 메서드임)
    clearInterval(this.timerID); // 타이머 분해
  }

  tick() { // Clock 컴포넌트가 매초 작동하도록 메서드 구현
    this.setState({ // this.setState 로 this.state 업데이트
      date: new Date() // 5. 1초마다 tick 메서드 실행
    });
  }

  render() { // 3. 랜더링실행
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}


ReactDOM.render(
  <Clock />, // 1. 여기서 부르면
  document.getElementById('root')
);

이제 시계는 매초 째깍거립니다.

현재 어떤 상황이고 메서드가 어떻게 호출되는지 순서대로 빠르게 요약해 보겠습니다.

  1. <Clock />ReactDOM.render()로 전달되었을 때 React는 Clock 컴포넌트의 constructor를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.

  2. React는 Clock 컴포넌트의 render() 메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.

  3. Clock 출력값이 DOM에 삽입되면, React는 componentDidMount() 생명주기 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 매초 컴포넌트의 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.

  4. 매초 브라우저가 tick() 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. setState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.

  5. Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.


  • State를 올바르게 사용하기

setState()에 대해서 알아야 할 세 가지

  1. 직접 State를 수정하지 마세요

예를 들어, 이 코드는 컴포넌트를 다시 렌더링하지 않습니다.

// Wrong
this.state.comment = 'Hello';
대신에 setState()를 사용합니다.

// Correct
this.setState({comment: 'Hello'});
this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다.

  1. State 업데이트는 비동기적일 수도 있습니다.

예를 들어, 다음 코드는 카운터 업데이트에 실패할 수 있습니다.

// Wrong

this.setState({
  counter: this.state.counter + this.props.increment,
});

// Correct

this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

위에서는 화살표 함수를 사용했지만, 일반적인 함수에서도 정상적으로 작동합니다.

  1. State 업데이트는 병합됩니다

setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합합니다.

예를 들어, state는 다양한 독립적인 변수를 포함할 수 있습니다.

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

별도의 setState() 호출로 이러한 변수를 독립적으로 업데이트할 수 있습니다.

componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

휴.. 이 예문들은 잘 모르겠다 ㅠ

시계를 세개로 추가해보는 예문을 보자

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() { // 생명주기 메서드
    this.timerID = setInterval( // 타이머ID 어떻게 저장하는지 주의!
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() { // 생명주기 메서드
    clearInterval(this.timerID); // 타이머 분해
  }

  tick() { // Clock 컴포넌트가 매초 작동하도록 메서드 구현
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

function App () {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

화면에
Hello, world!
It is 2:51:41 PM.
Hello, world!
It is 2:51:41 PM.
Hello, world!
It is 2:51:41 PM.
이 내용을 확인할 수 있다.(시간은 계속 움직인다)

🌀 6. 이벤트 처리하기

React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사합니다. 몇 가지 문법 차이는 다음과 같습니다.

  • React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용합니다.
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
//예를 들어, HTML은 다음과 같습니다.
<button onclick="activateLasers()">
  Activate Lasers
</button>

//React에서는 약간 다릅니다.
<button onClick={activateLasers}>
  Activate Lasers
</button>

또 다른 차이점으로, React에서는 false를 반환해도 기본 동작을 방지할 수 없습니다. 반드시 preventDefault를 명시적으로 호출해야 합니다. 예를 들어, 일반 HTML에서는 새 페이지를 여는 링크의 기본 동작을 방지하기 위해 다음과 같은 코드를 작성합니다.

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>
React에서는 다음과 같이 작성할 수 있습니다.

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

여기서 e는 합성 이벤트입니다. React는 W3C 명세에 따라 합성 이벤트를 정의하기 때문에 브라우저 호환성에 대해 걱정할 필요가 없습니다. React 이벤트는 브라우저 고유 이벤트와 정확히 동일하게 동작하지는 않습니다. 더 자세한 사항은 합성 이벤트을 참고하시기 바랍니다.

(이 부분은 잘 모르겠다.)

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

ES6 클래스를 사용하여 컴포넌트를 정의할 때, 일반적인 패턴은 이벤트 핸들러를 클래스의 메서드로 만드는 것입니다. 예를 들어, 다음 Toggle 컴포넌트는 사용자가 “ON”과 “OFF” 상태를 토글 할 수 있는 버튼을 렌더링합니다.

import React from 'react';
import ReactDOM from 'react-dom';

class Toggle extends React.Component {
  constructor(props) { // 2. constructor 실행
    super(props);
    this.state = {isToggleOn: true};

    // 콜백에서 'this'가 작동하려면 아래와 같이 바인딩 해주어야 합니다(필수)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() { // 4. 버튼클릭하면 해당메소드 실행
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }
  
  render() { // 3. 렌더링 실행
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    )
  }
}

ReactDOM.render(
  <Toggle />, // 1. Toggle 컴포넌트 부르고
  document.getElementById('root')
);

JSX 콜백 안에서 this의 의미에 대해 주의해야 합니다. JavaScript에서 클래스 메서드는 기본적으로 바인딩되어 있지 않습니다. this.handleClick을 바인딩하지 않고 onClick에 전달하였다면, 함수가 실제 호출될 때 this는 undefined가 됩니다.

(바인드와 함께 쓰는건 일단 그대로 적용하고 추후에 사용법을 다시 숙지해야겠다.)


  • 이벤트 핸들러에 인자 전달하기
    루프 내부에서는 이벤트 핸들러에 추가적인 매개변수를 전달하는 것이 일반적입니다. 예를 들어, id가 행의 ID일 경우 다음 코드가 모두 작동합니다.
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

위 두 줄은 동등하며 각각 화살표 함수와 Function.prototype.bind를 사용합니다.

두 경우 모두 React 이벤트를 나타내는 e 인자가 ID 뒤에 두 번째 인자로 전달됩니다. 화살표 함수를 사용하면 명시적으로 인자를 전달해야 하지만 bind를 사용할 경우 추가 인자가 자동으로 전달됩니다.

(지금은 일단 이런내용도 있구나 하고, 다음에 쓸일 있을때 와서 참고해야겠다.)

🌀 7. 조건부 렌더링

React에서 조건부 렌더링은 JavaScript에서의 조건 처리와 같이 동작합니다. if 나 조건부 연산자 와 같은 JavaScript 연산자를 현재 상태를 나타내는 엘리먼트를 만드는 데에 사용하세요. 그러면 React는 현재 상태에 맞게 UI를 업데이트할 것입니다.

로그인 상황에 맞게 문장 출력하는 예시

import React from 'react';
import ReactDOM from 'react-dom';

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) { // 3. 화면에 보여짐
  return <h1>Please sign up.</h1>;
}


function Greeting(props) { // 2. 속성 false 받아오기
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />; // 2-1. 여기가 실행된다
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}: // true로 바꿔도 잘 작동한다.
  <Greeting isLoggedIn={false} />, // 1. Gretting 컴포넌트부르기
  document.getElementById('root')
);

(이런식으로 조건문에 따라 컴포넌트로 연결시키는 것 같다!)

  • 엘리먼트 변수

엘리먼트를 저장하기 위해 변수를 사용할 수 있습니다. 출력의 다른 부분은 변하지 않은 채로 컴포넌트의 일부를 조건부로 렌더링 할 수 있습니다.

import React from 'react';
import ReactDOM from 'react-dom';

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

function UserGreeting(props) { // 바로위 예시에 있던 코드
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) { // 바로위 예시에 있던 코드
  return <h1>Please sign up.</h1>;
}

function Greeting(props) { // 바로위 예시에 있던 코드
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

class LoginControl extends React.Component {
  constructor(props) { // 2. constructor 실행
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() { // 3. 렌더링 실행
    const isLoggedIn = this.state.isLoggedIn;
    let button; // 엘리멘트 변수
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />, // 1. LoginControl 컴포넌트 부르기
  document.getElementById('root')
);

로그인버튼의 글자와
로그인유무에따른 출력문장이 바뀐다..

와우 복잡하고 길다.. ㅠ

다음 예시에서 코드를 더 짧게 작성하는 방법을 알려준다.

  • 논리 && 연산자로 If를 인라인으로 표현하기
import ReactDOM from 'react-dom';

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 && // 와 이부분 신기하다.
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

화면에
Hello!
You have 3 unread messages.
이렇게 출력된다.
unreadMessages.length < 0 && 로 바꾸면 Hello! 만 출력된다.

JavaScript에서 true && expression은 항상 expression으로 평가되고 false && expression은 항상 false로 평가됩니다.

따라서 && 뒤의 엘리먼트는 조건이 true일때 출력이 됩니다. 조건이 false라면 React는 무시합니다.

false로 평가될 수 있는 표현식을 반환하면 && 뒤에 있는 표현식은 건너뛰지만 false로 평가될 수 있는 표현식이 반환된다는 것에 주의해주세요. 아래 예시에서, <div>0</div>이 render 메서드에서 반환됩니다.(공식문서)

render() {
  const count = 0;
  return (
    <div>
      { count && <h1>Messages: {count}</h1>}
    </div>
  );
}
  • 조건부 연산자로 IF-Else 구문 인라인으로 표현하기

엘리먼트를 조건부로 렌더링하는 다른 방법은 조건부 연산자인 condition ? true: false를 사용하는 것입니다.

아래의 예시에서는 짧은 구문을 조건부로 렌더링합니다.

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

가독성은 좀 떨어지지만, 더 큰 표현식에도 이 구문을 사용할 수 있습니다.

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}
  • 컴포넌트가 렌더링하는 것을 막기

가끔 다른 컴포넌트에 의해 렌더링될 때 컴포넌트 자체를 숨기고 싶을 때가 있을 수 있습니다. 이때는 렌더링 결과를 출력하는 대신 null을 반환하면 해결할 수 있습니다.

아래의 예시에서는 가 warn prop의 값에 의해서 렌더링됩니다. prop이 false라면 컴포넌트는 렌더링하지 않게 됩니다.

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

컴포넌트의 render 메서드로부터 null을 반환하는 것은 생명주기 메서드 호출에 영향을 주지 않습니다. 그 예로 componentDidUpdate는 계속해서 호출되게 됩니다.

🌀 8. 리스트와 Key

아래는 map()함수를 이용하여 numbers 배열의 값을 두배로 만든 후 map()에서 반환하는 새 배열을 doubled 변수에 할당하고 로그를 확인하는 코드입니다.

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

이 코드는 콘솔에 [2, 4, 6, 8, 10]을 출력합니다.

React에서 배열을 엘리먼트 리스트로 만드는 방식은 이와 거의 동일 합니다.

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

엘리먼트 모음을 만들고 중괄호 {}를 이용하여 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')
);

즉, 다음 예제로 만들면

import React from 'react';
import ReactDOM from 'react-dom';

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

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);
  • 1
  • 2
  • 3
  • 4
  • 5

를 화면에서 확인할 수 있다.


  • 기본 리스트 컴포넌트

이 예제는 바로 위 예제와 결과가 같습니다.

import React from 'react';
import ReactDOM from 'react-dom';

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

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

이 코드를 실행하면 리스트의 각 항목에 key를 넣어야 한다는 경고가 표시됩니다. “key”는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트입니다. 다음 섹션에서 key의 중요성에 대해서 더 설명하겠습니다. 이제 numbers.map() 안에서 리스트의 각 항목에 key를 할당하여 키 누락 문제를 해결하겠습니다.

import React from 'react';
import ReactDOM from 'react-dom';

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}> // 바뀐부분
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

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

이 예제를 실행하면 더이상 오류가 뜨지 않습니다.


  • Key

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

Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것입니다. 대부분의 경우 데이터의 ID를 key로 사용합니다. (바로 위 예제 변경된 부분처럼)

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

렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있습니다.
(안정적인 ID를 가지게 해야겠다.)

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로 사용합니다.

더 자세히 알고 싶다면 왜 key가 필요한가에 대한 더 자세한 설명을 읽어보세요.


  • Key로 컴포넌트 추출하기

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

예시: 올바른 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 오류메세지까지 제어해서!)

🌀 9. 폼

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)“라고 불리는 기술을 이용하는 것입니다.

  • 제어 컴포넌트

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

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

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

다음예시를 통해 입력값이 this.state 로 잘 이동하는 것을 알 수 있다.

import React from 'react';
import ReactDOM from 'react-dom';

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


ReactDOM.render(
  <NameForm  />,
  document.getElementById('root')
);

value 어트리뷰트는 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value가 되고 React state는 신뢰 가능한 단일 출처 (single source of truth)가 됩니다. React state를 업데이트하기 위해 모든 키 입력에서 handleChange가 동작하기 때문에 사용자가 입력할 때 보여지는 값이 업데이트됩니다.

제어 컴포넌트로 사용하면, input의 값은 항상 React state에 의해 결정됩니다. 코드를 조금 더 작성해야 한다는 의미이지만, 다른 UI 엘리먼트에 input의 값을 전달하거나 다른 이벤트 핸들러에서 값을 재설정할 수 있습니다.


  • textarea태그

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

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

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

import React from 'react';
import ReactDOM from 'react-dom';

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


ReactDOM.render(
  <EssayForm  />,
  document.getElementById('root')
);

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

(나는 하나의 컴포넌트에 this.state에 모든데이터를 넣었는데, 각각의 컴포넌트를 제어하는 방법도 생각해봐야겠다.)


  • 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 어트리뷰트를 사용합니다. 한 곳에서 업데이트만 하면되기 때문에 제어 컴포넌트에서 사용하기 더 편합니다. 아래는 예시입니다.

import React from 'react';
import ReactDOM from 'react-dom';

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


ReactDOM.render(
  <FlavorForm  />,
  document.getElementById('root')
);

전반적으로 <input type="text">, <textarea><select> 모두 매우 비슷하게 동작합니다. 모두 제어 컴포넌트를 구현하는데 value 어트리뷰트를 허용합니다.

주의

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

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


  • file input 태그

HTML에서 <input type="file">는 사용자가 하나 이상의 파일을 자신의 장치 저장소에서 서버로 업로드하거나 File API를 통해 JavaScript로 조작할 수 있습니다.

<input type="file" />
값이 읽기 전용이기 때문에 React에서는 비제어 컴포넌트입니다. 문서 뒷부분에서 다른 비제어 컴포넌트와 함께 설명하고 있습니다.

  • 다중 입력 제어하기

여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name 어트리뷰트를 추가하고 event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택할 수 있게 해줍니다.

아래는 예시입니다.

import React from 'react';
import ReactDOM from 'react-dom';

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}


ReactDOM.render(
  <Reservation  />,
  document.getElementById('root')
);

주어진 input 태그의 name에 일치하는 state를 업데이트하기 위해 ES6의 computed property name 구문을 사용하고 있습니다.

this.setState({
  [name]: value
});

ES5 코드는 아래와 같습니다.

var partialState = {};
partialState[name] = value;
this.setState(partialState);

또한, setState()는 자동적으로 현재 state에 일부 state를 병합하기 때문에 바뀐 부분에 대해서만 호출하면 됩니다.

  • 제어되는 Input Null 값

제어 컴포넌트에 value prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없습니다. value를 설정했는데 여전히 수정할 수 있다면 실수로 value를 undefined나 null로 설정했을 수 있습니다.

아래 코드가 이것을 보여줍니다. (첫 번째 입력은 잠겨있지만 잠시 후 입력이 가능해집니다.)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
  • 제어 컴포넌트의 대안
    데이터를 변경할 수 있는 모든 방법에 대해 이벤트 핸들러를 작성하고 React 컴포넌트를 통해 모든 입력 상태를 연결해야 하기 때문에 때로는 제어 컴포넌트를 사용하는 게 지루할 수 있습니다. 특히 기존의 코드베이스를 React로 변경하고자 할 때나 React가 아닌 라이브러리와 React 애플리케이션을 통합하고자 할 때 짜증날 수 있습니다. 이러한 경우에 입력 폼을 구현하기 위한 대체 기술인 비제어 컴포넌트를 확인할 수 있습니다.

  • 완전한 해결책
    유효성 검사, 방문한 필드 추적 및 폼 제출 처리와 같은 완벽한 해결을 원한다면 Formik이 대중적인 선택 중 하나입니다. 그러나 Formik은 제어 컴포넌트 및 state 관리에 기초하기 때문에 배우는 걸 쉽게 생각하면 안 됩니다.


🌀 10. State 끌어올리기

(코드스테이츠 예시) 마우스클릭으로 값 끌어올리기
공식문서 에서는 온도예시를 확인할 수 있다.

import React from "react";

export default class parents extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: "날 바꿔줘!"
    };
    this.handleChangeValue = this.handleChangeValue.bind(this);
  }

  handleChangeValue() {
    this.setState({
      value: "보여줄게 완전히 달라진 값"
    });
  }

  render() { // handleChangeVlaue 함수를 속성으로 넘겨준다
    return (
      <div>
        <div>값은 {this.state.value} 입니다</div>
        <ChildComponent handleButtonClick={this.handleChangeValue} />
      </div>
    );
  }
}

function ChildComponent(props) { // 받아온 props를 바로 onClick에 넣는다.
  return <button onClick={props.handleButtonClick}>값 변경</button>;
}

🌀 12. React로 생각하기

이번주제는 링크로 들어가서 정독하는 게 좋을 듯 하다.
자세하게 쓰여져 있다.

React로 생각하기

11 주제는 다음에 시간이 날때 파헤쳐봐야겠다.

profile
기록하는 백엔드 개발자

0개의 댓글