Posted:      Updated:

React(리액트)

facebook.com 의 UI를 더 잘 만들기 위해 페이스북에서 만든 Javascript UI 라이브러리이다.

컴포넌트

웹사이트는 정보가 조금만 증가해도 그 정보를 표현하는 html이 기하급수적으로 복잡해진다.
눈으로 보기에 복잡해보이지 않아도 실제로는 코드가 굉장히 복잡하다.

리액트는 복잡한 코드를 정리정돈할 수 있도록 사용자 정의 태그를 만들 수 있게 해준다.
리액트에서 사용자 정의 태그를 컴포넌트 라고 부른다.

컴포넌트의 기능

가독성을 획기적으로 높일 수 있다.
재사용성이 높아진다.
유지보수가 편리해진다.

개발 환경

공식 문서에서 개발 환경을 어떻게 세팅하는지 찾아볼 수 있다.

Online Playgrounds

온라인 상에서 리액트 어플리케이션을 구현해볼 수 있다.
예를 들어 CodeSandbox 가 있다.

Add React to a Website

웹 사이트를 가지고 있다면 처음부터 끝까지 리액트 앱으로 만드는 것이 아니라 부분적으로 리액트 기능을 추가하고 싶을 때 사용하는 옵션이다.

Create a New React App

`Tool Chain 을 활용한다.
Tool Chain은 리액트로 앱을 개발할 때 필요한 여러 개발 환경, 도구 등을 잘 모아서 한 방에 제공해주는 편리한 도구들이다.

create-react-app

Tool Chain 중에 하나이다.
설치를 위해 npm 이 필요하다.

npm install -g create-react-app

npm

npm이란 Node.js 를 이용해서 만들어진 여러가지 앱들을 손쉽게 설치할 수 있도록 도와주는 도구이다.
사용을 위해 Node.js 를 설치한다.

코드 작성하고 실행하기

editor 활용하기: visual studio code

Microsoft에서 만든 무료 프로그램이다.
command line 명령어로 컴퓨터를 제어할 수 있는 프로그램이 내부적으로 설치되어 있다.

create-react-app 으로 개발환경을 만들면 terminal에서 npm run start 명령을 통해 실행시킬 수 있다.
ctrl + C 를 누르면 실행을 종료할 수 있다.

Directory 구조

크게 srcpublic 이라는 디렉토리가 있다.
public 에는 index.html 이 있다.

<div id="root"> </div>

리액트를 통해서 만들어낸 컴포넌트들은 id가 root인 태그 안에 들어가게 된다.

이 안에 들어가는 컴포넌트들은 src 디렉토리 안에 있는 파일들이다.
이후 개발을 할 때 대부분의 파일은 src 디렉토리 안에 넣게 되며, entry 파일은 index.js 파일이다.

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

<APP/> 이라는 것이 리액트를 통해 만든 사용자 정의 태그, 즉 컴포넌트이다.

import App from './App';

<APP/> 컴포넌트의 실제 구현은 import 를 통해 불러온 src 안의 App.js 파일이다.

css를 수정하고 싶은 경우 index.js 에 import 되어 있는 index.css 파일을 수정하면 된다.

배포

빌드 시 npm run build 명령을 사용하면 build 라고 하는 파일이 추가된다.
이 안에 있는 index.html 은 불필요한 용량을 차지하는 정보를 제거했기 때문에 공백이 하나도 없는 상태이다.
이와 같이 실제로 서비스할 때는 build 안에 있는 파일들을 사용한다.

Web Server가 문서를 찾는 최상위 디렉토리 Document rootbuild 디렉토리 안에 있는 파일들을 위치시키면 실서버 환경이 완성딘다.

serve

npm을 통해 설치할 수 있는 간단한 웹서버이다.

npx serve -s build

위 명령을 통해 한 번만 실행시킬 웹서버를 다운로드 받을 수 있다.
-s build 옵션은 build 디렉토리를 document root 로 하는 옵션이다.

웹서버로 접속할 수 있는 주소를 확인한 뒤 localhost:5000 으로 접속한다.

컴포넌트

class App extends Component {
  render() {
    return (
      <div className="App">
        Hello, React!!
      </div>
    );
  }
}

App 이라는 클래스를 만들고, 컴포넌트라고 하는 리액트가 갖고 있는 클래스를 상속하여 새로운 클래스를 만든다.
이는 render 라고 하는 메서드를 가지고 있다.

class Subject extends Component {
  render() {
    return ( 
      <header>
        <h1>WEB</h1>
        world wide web!
      </header>
    );
  }
}

클래스는 대문자로 시작하며, 컴포넌트는 반드시 하나의 최상위 태그만 사용해야 한다.
Subject 에서는 header 태그가 최상위 태그이다.
따라서 <header> 부분을 Subject 라는 이름의 태그로 정의했다.
<subject> 태그를 추가하면 div classApp 인 태그 안쪽에 <header> 라는 태그가 생긴다.

props

class Subject extends Component {
  render() {
    return ( 
      <header>
        <h1>{this.props.title}</h1>
        {this.props.sub}
      </header>
    );
  }
}

class Content extends Component{
  render(){
    return(
      <article>
        <h2>{this.props.title}</h2>
        {this.props.desc}
      </article>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <Subject title="WEB" sub="world wide web!"></Subject>
        <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
      </div>
    );
  }
}

속성을 사용해서 텍스트를 지정하도록 할 수 있다.
HTML에서 속성이라고 하는 것은 리액트에서 props 라고 한다.
이를 활용해서 코드의 재사용성을 높일 수 있다.

Debugging

리액트 홈페이지에 “Community” -> “Tools” 부분에 Debugging 라는 도구로 현재 리액트로 만들어진 앱의 상태를 확인할 수 있다.

“크롬에 설치” 를 클릭하여 설치한 후 React Debugger 라는 툴을 설치한다.
개발자 도구에서 React 탭을 통해 리액트 컴포넌트를 볼 수 있다.

컴포넌트 파일로 분리하기

import React, { Component } from 'react';

Component 클래스를 로딩해야 한다.

export default TOC;

외부에서 사용할 수 있도록 허용할 것인가를 export 를 통해 작성한다.

import TOC from "./componets/TOC";

외부에서 사용할 때는 import 로 로딩한다.

state

props 의 값에 따라 내부의 구현에 필요한 데이터들이다.
propsstate 는 철저하게 분리되어 있어야 한다.

<Subject title="WEB" sub="world wide web!"></Subject>

하드코딩된 props 의 값을 state 로 바꾸어 코드를 개선한다.

constructor (props){
  super(props);
  this.state = {
    subject : { title : 'WEB' , sub : 'World Wide Web!' }
  }
}

this.state={} 코드를 통해 state 값을 초기화시킨다.
this.state 에 Subject props의 값으로 객체를 부여한다.

<Subject title={this.state.subject.title} sub={this.sate.subject.sub}></Subject>

props 의 데이터를 state 에서 가져오도록 수정한다.

정보 은닉
내부적으로 state 값이 subject 가 있는지 알 수 없다.
외부에서 알 필요가 없는 정보를 은닉할 수 있다.

여러가지 값을 다룰 때

constructor (props){
  super(props);
  this.state = {
    subject : { title : 'WEB' , sub : 'World Wide Web!' }
    contents : [
      {id : 1 , title : 'HTML' , desc : 'HTML is for information'},
      {id : 2 , title : 'CSS' , desc : 'CSS is for design'},
      {id : 3 , title : 'JavaScript' , desc : 'JavaScript is for interactive'},
    ]
  }
}

대괄호로 배열을 표시한다.

<TOC data={this.stat.contents}></TOC>

배열 이름을 주입시킨다.

class TOC extends Component{
  render(){
    var lists=[ ];
    var data=this.props.data;
    var i=0;
    while( i < data.length ) {
      lists.push( <li key={data[i].id}><a href={"/content"+data[i].id}>{data[i].title}</a></li>);
      i=i+1;
    }
    return(
      <nav>
        <ul>
          {lists}
        </ul>
      </nav>
    );
  }
}

<li> 태그를 하나씩 생성하면서 <lists> 변수에 담고 반환시킨다.
이때, key 를 지정하지 않으면 에러가 발생하므로 key 라고 하는 props 를 지정해준다.
엘리먼트를 여러개 자동으로 생성하는 경우에는 식별자key 를 통해 적어줘야 한다.

이벤트

render 함수는 어떤 HTML을 그릴 것인가를 결정한다.
리액트에서는 props 의 값이나 state 값이 바뀌면 해당되는 컴포넌트의 render 함수가 호출된다.
render 함수가 다시 호출됨이 따라 render 함수 하위의 컴포넌트들의 render 함수도 호출된다.
따라서 화면이 다시 그려진다.

constructor (props){
  super(props);
  this.state = {
    subject : { title : 'WEB' , sub : 'World Wide Web!' }
    contents : [
      {id : 1 , title : 'HTML' , desc : 'HTML is for information'},
      {id : 2 , title : 'CSS' , desc : 'CSS is for design'},
      {id : 3 , title : 'JavaScript' , desc : 'JavaScript is for interactive'},
    ],
    mode : 'welcome',
    welcome : {title : 'welcome', desc : 'Hello, React!!'},
  }
}

mode 의 기본값은 welcome 으로 하고, modewelcome 일 때 content 영역에 표시할 텍스트를 지정한 코드이다.

render() {
  var _title,_desc=null;
  if (this.state.mode === 'welcome') {
    _title = this.state.welcome.title;
    _desc = this.state.welcome.desc;
  } else if( this.state.mode === 'read') {
    _title = this.state.contents[0].title;
    _desc = this.state.contents[0].desc;
  }
  return (
    <div className="App">
      <Subject title={this.state.subject.title} sub={this.sate.subject.sub}></Subject>
      <TOC data={this.state.contents}></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
  );
}

render 함수에 조건문을 사용하여 mode 의 값에 따라 컴포넌트의 rendering 결과가 달라지게 하는 코드이다.

<header>
     <h1><a href="/" onClicn{function () {
         alert( 'Hi' );
      }>{this.state.subject.title}</a></h1>
     {this.state.subject.sub}
</header>

<a> 라는 링크를 클릭할 때 어떤 자바스크립트 코드가 실행되도록 이벤트를 구현한 코드이다.
이때 자바스크립트의 onclick 대신 리액트의 onClick 을 사용한다.
이 코드 실행 시 페이지가 reload 되므로, reload 되지 않도록 기본 동작을 막아야 한다.

<header>
     <h1><a href="/" onClicn{function (e) {
         console.log(e);
         e.preventDefault();
         alert( 'Hi' );
      }>{this.state.subject.title}</a></h1>
     {this.state.subject.sub}
</header>
e.preventDefault();

위 코드를 통해 이벤트가 발생한 태그의 기본적인 동작을 못하도록 막을 수 있다.
위에서는 최종적으로 링크를 클릭할 때마다 e 의 정보를 console 에 출력하고, preventDefault() 함수를 통해 페이지가 이동되지 않도록 한다.

<header>
     <h1><a href="/" onClicn{function (e) {
         console.log(e);
         e.preventDefault();
         this.setState({
           mode:'welcome'
         });
      }.bind(this)}>{this.state.subject.title}</a></h1>
     {this.state.subject.sub}
</header>

이벤트 함수 내에서 this 가 컴포넌트 자기 자신을 가리키도록 bind(this) 를 추가해야 한다.

this.state.mode ='welcome' 를 통해 값을 수정하면 리액트는 알지 못한다.
따라서 리액트가 state 의 값이 바뀐 사실을 알 수 있도록 this.setState() 함수를 호출할 때 mode를 수정한다.

constructer() 함수에서는 this.state.mode ='welcome' 처럼 작성해도 상관 없다.

컴포넌트 이벤트

class Subject extends Component {
  render() {
    return ( 
      <header>
        <h1><a href="/" onClick={function(e){
           e.preventDefault();
           this.props.onChangePage();
         }.bind(this) } >{this.props.title}</a></h1>
        {this.props.sub}
      </header>
    );
  }
}

링크를 클릭하여 onClick 이벤트가 실행될 때 함수가 실행된다.
e.preventDefault(); 를 통해 페이지가 바뀌는 것을 막는다.
이후 <subject> 컴포넌트의 onChangePage() 함수를 호출한다.

constructor(props){
  super(props);
  this.state = {
    mode:'read',
    selected_content_id: 2,
    subject:{title:'WEB', sub:'World Wide Web!'},
    welcome:{title:'Welcome', desc:'Hello, React!!'},
    contents:[
      {id:1, title:'HTML', desc:'HTML is for information'},
      {id:2, title:'CSS', desc:'CSS is for design'},
      {id:3, title:'JavaScript', desc:'JavaScript is for interactive'}
    ]
  }
}

selected_content_id 를 2로 설정해서 기본적으로 2번 content 가 선택되도록 할 수 있다.

render() {
  var _title,_desc=null;
  if (this.state.mode === 'welcome') {
    _title = this.state.welcome.title;
    _desc = this.state.welcome.desc;
  } else if(this.state.mode === 'read'){
    _title = this.state.contents[0].title;
    _desc = this.state.contents[0].desc;
    var i = 0;
    while(i < this.state.contents.length){
      var data = this.state.contents[i];
      if(data.id === this.state.selected_content_id) {
        _title = data.title;
        _desc = data.desc;
        break;
      }
      i = i + 1;
    }
  }
  return (
    <div className="App">
      <Subject 
        title={this.state.subject.title} 
        sub={this.sate.subject.sub} 
        onChangePage={ function() {
          this.setState( {mode: 'welcome'} );
          }.bind(this) }> </Subject>
      <TOC 
        onChangePage={function(id){
          this.setState({
            mode:'read',
            selected_content_id: Number(id)
          });
        }.bind(this)} 
        data={this.state.contents}
      ></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
  );
}

read 모드일 때 반복문을 수행하여 현재 순번에 해당하는 content 를 표시되게 한다.
<TOC> 컴포넌트 list의 onChangePage 가 발생했을 때 this.setState 를 이용해서 mode 값과 함께 selected_content_id 의 값을 변경하면 된다.
function(id)onChangePage() 함수를 실행시킬 때 항목의 id 값을 넘겨준다.
<TOC> 컴포넌트에서 넘어오는 값을 selected_content_id: Number(id) 와 같이 Number() 를 활용해 숫자로 바꿔준다.

class TOC extends Component{
  render(){
    var lists=[ ];
    var data=this.props.data;
    var i=0;
    while( i < data.length ) {
      lists.push(
        <li key={data[i].id}>
          <a 
          href={"/content/"+data[i].id}
          data-id={data[i].id}
          onClick={function(e){
            e.preventDefault();
            this.props.onChangePage(e.target.dataset.id);
          }.bind(this)}
          >{data[i].title}</a>
        </li>);
      i=i+1;
    }
    return(
      <nav>
        <ul>
          {lists}
        </ul>
      </nav>
    );
  }
}

data-id = {data[i].id} 와 같은 속성을 주고 로드해서 해당하는 id 값을 확인할 수 있다.
이벤트가 발생한 태그를 가리킬 때는 target 을 사용하며, 이를 통해 <a> 태그를 알아내고 <a> 태그가 가리키는 data-id 값에 접근할 수 있다.
data- 로 시작되는 속성은 dataset 이라고 하는 형태로 접근할 수 있다.
data-id 속성을 사용하므로 접미사 id 를 사용하여 this.props.onChangePage(e.target.dataset.id); 와 같은 형식으로 사용한다.

이렇게 하면 WEB 클릭 시 modewelcome 이 되고 목록을 클릭하면 moderead 가 되어 selected_content_id 바뀌므로 본문 내용도 바뀌게 된다.

속성을 이용하지 않고 하는 방법

<li key={data[i].id}>
     <a 
         href={"/content/"+data[i].id}
         data-id={data[i].id}
         onClick={function(id, e){
            e.preventDefault();
            this.props.onChangePage(id);
         }.bind(this, data[i].id)}
     >{data[i].title}</a>
</li>

bind(this,data[i].id) 처럼 bind(this) 의 두번째 인자로 data[i].id 값을 주면 된다.
onClick 이벤트에서 실행되는 함수의 첫번째 매개변수 값이 bind 의 두번째 인자 값인 data[i].id 가 된다.

댓글남기기