Waffle-Skile.github.io

React (1)

Prerequisite

프로젝트 생성

create-react-app은 웹 서버를 세밀하게 설정하는 부담 없이 바로 React를 다룰 수 있게 만들어주는 개발 도구입니다.

명령 프롬프트창을 연 다음 본인이 프로젝트를 생성할 폴더로 이동해주세요. 다음 코드를 입력해 create-react-app 을 설치하고 프로젝트를 만들어주세요.

$ npm install -g create-react-app
$ create-react-app waffle-react

둘째 줄의 waffle-react는 프로젝트 이름으로, 본인 취향껏 바꾸셔도 좋습니다.

$ cd waffle-react
$ npm start

React와 create-react-app의 설치가 제대로 이루어졌을 경우, http://localhost:3000/에 접속하시면 다음과 같은 화면을 볼 수 있습니다.

React App

목표

과제 3과 같이 Single-page Application을 만드는 것을 목표로 합니다.

React.Component

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”. Retrieved from What is React? - Tutorial: Intro to React

Component 는 React에서 View를 구성하는 기본 단위입니다. HTML에 비유하자면 일종의 Element입니다. HTML의 DOM 구조처럼 계층 구조를 가지며, Component는 다른 Components를 감쌀 수 있습니다. React는 기존의 JavaScript를 대신하여 편리하게 HTML DOM을 조작하기 위한 프레임워크이고, 그래서 가상 DOM 추상화 개념을 활용하여 기존의 HTML DOM과 유사한 앱 구조를 만들었습니다.

Example 1

다음은 create-react-app 제작시 등장하는 기본 뼈대 코드입니다.

/* src/index.js */
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
/* src/App.js */
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

위와 같이 모든 Component는 React.Component를 상속하며, render() 함수를 이용해서 이 Component를 전통적인 HTML DOM으로 변환하는 것을 볼 수 있습니다. Component 내의 render() 함수는 HTML 태그를(?) 리턴합니다. (곧 설명: JSX)

export, import

React 프로젝트를 설계할 때, 보통 한 Component는 하나의 *.js 파일에 담습니다. 다른 *.js 파일의 자료(함수, 개체 등)를 가져오고 싶을 때 importexport문을 사용합니다.

export default Something;

Default export에는 모듈을 대표하는 단 하나의 자료를 지정합니다.

import Something from './Something.js';

위 구문은 해당 *.js파일의 default export를 Something 이라는 이름으로 import합니다.

여러개의 자료를 export하는 등, 보다 자세한 내용을 원하시면 다음 MDN docs를 참고하세요.

JSX

위 예제에서 보았던 HTML 태그는 React 고유의 JSX 라는 문법입니다. 이는 HTML 태그와 유사한 구조를 갖고 있으나, HTML DOM으로 변환하기 위한 중간 단계를 상징하며 일부 차이점이 있습니다.

<div>
  { condition ? <JsxTag /> : null }
</div>
<ul>
  { [...Array(5).keys()].map((x) => <li>{x}</li>) }
</ul>

Props

상위 Component에서 하위 Component에 자료를 전달해주는 방법입니다. HTML DOM의 Attributes와 유사한 역할을 수행합니다.

React Component에서는 this.props로 Object로써 접근할 수 있습니다. 또한 React Component에 constructor(props)의 인수로도 넘어옵니다.

Example 2

import React, { Component } from 'react';

class Adder extends Component {
  render() {
    return (
      <p>
        {this.props.operand1} + {this.props.operand2}
        {' = '}{this.props.operand1 + this.props.operand2}
      </p>
    );
  }
}

class App2 extends Component {
  render() {
    let spreadee = {
      operand1: 3.1,
      operand2: 2.7
    };

    return (
      <div>
        <Adder operand1={3.7} operand2={2.3} />
        <Adder {...spreadee} />
      </div>
    );
  }
}

export default App2;

State

Props는 (상위 컴포넌트가 준 것이기에) 변경할 수 없지만, State는 해당 Component에서 자유롭게 변경할 수 있는 Object입니다. this.state로 접근할 수 있지만, 몇 가지 제약사항이 있습니다.

Example 3

다음 소스코드는 간단한 경기 점수 카운터입니다.

import React, { Component } from 'react';

class App3 extends Component {
  constructor() {
    super();
    this.state = {
      blue: 0,
      red: 0
    };
  }

  render() {
    return (
      <div>
        <p>
          <span style={ {color: 'blue'} }>{this.state.blue}</span>
          {' : '}
          <span style={ {color: 'red'} }>{this.state.red}</span>
        </p>
        <p>
          <input type="button"
            onClick={() => this.setState({blue: this.state.blue + 1})}
            value="Blue Win" />
          <input type="button"
            onClick={() => this.setState({red: this.state.red + 1})}
            value="Red Win" />
        </p>
      </div>
    );
  }
}

export default App3;

Callback

Example 4

앞서 State를 설명할 때 일반적인 Web Components의 Event Handling을 다루었습니다. (onClick) 이번에는 <input type="text" /> 태그를 이용해 사용자로부터 입력을 받는 방법에 대해 알아보겠습니다.

import React, { Component } from 'react';

class App4 extends Component {
  constructor() {
    super();
    this.state = {
      name: ''
    };
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.name} />
        <input type="button" value="Greet"
          onClick={() => alert(`Hi, ${this.state.name}!`)} />
      </div>
    );
  }
}

export default App4;

이렇게 하면, 코드가 동작하지 않습니다. (내용을 쓸 수가 없습니다.) <input> 태그의 입장에서 value는 Props에 해당하므로 함부로 값을 바꿀 수 없기 때문입니다. 만약 value 속성을 주지 않는다면 내용은 쓸 수 있겠으나 그걸 React스럽게 가져올 방법이 없습니다.

이때는 onChange를 핸들링함으로써 문제를 해결할 수 있습니다.

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

render() {
  return (
    <div>
      <input type="text" value={this.state.name}
        onChange={this.handleChange.bind(this)} />
      <input type="button" value="Greet"
        onClick={() => alert(`Hi, ${this.state.name}!`)} />
    </div>
  );
}

이러한 Callback 함수의 특성을 활용하면, 하위 컴포넌트에서 상위 컴포넌트로 정보를 전달할 수 있습니다.

상위 컴포넌트는 하위 컴포넌트의 Props에 함수를 전달하고, 하위 컴포넌트는 그 함수를 호출할 때 매개변수에 본인의 정보를 전달하면 됩니다. 이때, binding에 주의해주셔야 합니다.

Example 5

앞선 Example 3를 변형시킨 버전입니다.

import React, { Component } from 'react';

class Team extends Component {
  constructor(props) {
    super(props);
    this.state = {score: 0};
  }

  render() {
    const color = {color: this.props.color};
    return (
      <div>
        <span style={color}>{this.state.score}</span><br />
        <button
          onClick={() => {
            this.setState({score: this.state.score + 1});
            this.props.handleNewGame();
          }}>Win!</button>
      </div>
    );
  }
}

class App5 extends Component {
  constructor() {
    super();
    this.state = {
      set: 0
    };
  }

  handleNewGame() {
    this.setState({ set: this.state.set+1 });
  }

  render() {
    const flex = {display: 'flex'}
    return (
      <div>
        <p>Set {this.state.set + 1}</p>
        <div style={flex}>
          <Team color="blue" handleNewGame={this.handleNewGame.bind(this)} />
          <div> : </div>
          <Team color="red" handleNewGame={this.handleNewGame.bind(this)} />
        </div>
      </div>
    );
  }
}

export default App5;

이 예제의 경우 하위 컴포넌트에서 상위 컴포넌트로 정보를 전달해주기 위한 실습 목적으로 설계했지만 이 단순한 예제의 경우 Team 컴포넌트는 state를 갖지 않는게 바람직합니다.

CSS

References