Skip to content
egoing edited this page Aug 4, 2021 · 22 revisions

step -1

목표

React 없이 애플리케이션을 만들 수 있다.

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <script>
            data=[
                { id: 1, title: "html", description: "html is..." },
                { id: 2, title: "css", description: "css is..." },
                { id: 3, title: "js", description: "js is..." }
            ]
            selected_id = null;
            function refreshNav(){
                var navTag = '';
                for(var i=0; i<data.length; i++){
                    navTag += `
                        <li>
                            <a href="#" onclick="refreshArticle(${data[i].id});">
                                ${data[i].title}
                            </a>
                        </li>
                    `;
                }
                document.querySelector('#nav').innerHTML = navTag;    
            }            
            function refreshArticle(id){
                var title = 'Welcome';
                var desc = 'Hello, WEB';
                if(id !== null){
                    for(var i=0; i<data.length; i++){
                        if(data[i].id === id){
                            title = data[i].title;
                            desc = data[i].description;
                        }
                    }
                }
                var articleTag = `
                    <h2>${title}</h2>
                    ${desc}
                `;
                document.querySelector('article').innerHTML = articleTag;    
            }
        </script>
        <header>
            <h1><a href="#" onclick="refreshArticle()">WEB</a></h1>
        </header>
        <nav>
            <ul id="nav">
                
            </ul>
        </nav>
        <article>

        </article>
        <script>            
            refreshNav();
            refreshArticle('Welcome', 'Hello, WEB');
        </script>
    </body>
</html>

결과

실습 : https://jsbin.com/joquyavate/edit?html,output

step0

목표

리액트의 개발환경을 구축하고 싶다.

힌트

  1. nodejs 설치
  2. npm i create-react-app
  3. create-react-app myapp
  4. cd myapp
  5. npm start

결과

step1

목표

아래의 코드를 리액트화하고 싶다.

<header>
        <h1>WEB</h1>
      </header>
      <nav>
        <ul>
          <li>
            <a href="/1">html</a>
          </li>
          <li>
            <a href="/2">css</a>
          </li>
          <li>
            <a href="/3">javascript</a>
          </li>
        </ul>
      </nav>
      <article>
        <h2>Welcome</h2>
        Hello, WEB!
      </article>

결과

step2

목표

header, nav, article 태그가 너무 복잡합니다. 이들을 컴포넌트화하고 싶다.

힌트

각 태그를 Header, Nav, Article 라는 이름의 컴포넌트로 만들어서 단순화 해보세요.

function Header() {
  //todo
}
function Nav() {
  //todo
}
function Article() {
  //todo
}
export default function App() {
  return (
    <div>
      <Header />
      <Nav />
      <Article />
    </div>
  );
}

결과

step 3

목표

컴포넌트가 너무 경직 되어 있습니다. HTML 태그 처럼 속성을 통해서 컴포넌트를 커스터마이징 하고 싶습니다.

힌트

아래와 같은 인터페이스를 가지고 있는 컴포넌트를 만들어보세요.

function Nav(props) {
  //todo
}
function Article(props) {
  //todo
}
export default function App() {
  return (
    <div>
      <Header />
      <Nav
        data={[
          { id: 1, title: "html", description: "html is..." },
          { id: 2, title: "css", description: "css is..." },
          { id: 3, title: "js", description: "js is..." }
        ]}
      />
      <Article title="제목" description="본문" />
    </div>
  );
}

결과

step 4

목표

컴포넌트에 이벤트 인터페이스를 추가해서 핸들러의 상태 변화를 통보 받을 수 있도록 하고 싶다.

힌트

function Header(props) {
  //todo
}
function Nav(props) {
  //todo
}
export default function App() {
  var article = null;
  if (mode === "WELCOME") {
    // todo
  } else if (mode === "READ") {
    // todo
  }
  return (
    <div>
      <Header
        onChangeMode={function() {
          setMode("WELCOME");
        }}
      />
      <Nav
        data={topics}
        onChangeMode={function(topic_id) {
          setMode("READ");
          setSelectedId(topic_id);
        }}
      />
      {article}
    </div>
  );
}

결과

step 5

목표

글생성 기능을 추가하고 싶습니다.

알아야 할 것

  1. 데이터를 변경할 때는 불변성을 유지하도록 주의 합니다.
    1. 원시 데이터 타입은 그냥 변경하면 됩니다.
    2. 배열과 객체는 원본을 복제한 후에 변경하면 됩니다.
    3. 차이는 다음 영상을 참고해주세요. https://youtu.be/fDJG4CjbB3A?t=215 참고 : https://gist.github.com/aluksidadi/10aa07c6f007efc587f793212548ad51
  2. immutable 과 관련된 이슈는 다음 영상을 참고하세요. https://opentutorials.org/module/4075
  3. immutable.js 와 같은 라이브러리를 이용하는 것을 추천합니다.
  4. 기존 js 문법의 문제점과 immutable.js를 사용하는 이유 : https://docs.google.com/spreadsheets/d/1tn5YI9wDZieZOwJHCaT_cQuixw64tr_iv3Pv6AqdfI8/edit#gid=0

힌트

function Create(props) {
  return (
    <article>
      <h1>Create</h1>
      <form
        action="topics"
        method="post"
        onSubmit={function(e) {
          //todo
        }}
      >
        <p>
          <input type="text" name="title" placeholder="title" />
        </p>
        <p>
          <textarea name="description" placeholder="description" />
        </p>
        <p>
          <input type="submit" />
        </p>
      </form>
    </article>
  );
}
function Control(props) {
  return ( 
    <ul>
      <li>
        <input
          type="button"
          value="create"
          onClick={function() {
            //todo
          }}
        />
      </li>
    </ul>
  );
}
export default function App() {
  var article = null;
  if (mode === "WELCOME") {
  } else if (mode === "READ") {
  } else if (mode === "CREATE") {
    article = (
      <Create
        onCreate={function(data) {
          data.id = nextId;
          setTopics([...topics, data]);
          setSelectedId(nextId);
          setMode("READ");
          setNextId(nextId + 1);
        }}
      />
    );
  }
  return (
    <div>
      //...
      <Control
        onChangeMode={function(mode) {
          setMode(mode);
        }}
      />
    </div>
  );
}

결과

step 6

목표

수정 기능을 추가하고 싶습니다.

알아야 할 것

  • 폼을 수정할 때는 onChange 이벤트를 통해서 state 값을 수정하고, 수정된 state값이 value에 반영될 수 있도록 처리해야 합니다.

힌트

function Update(props) {
  var [title, setTitle] = useState(props.data.title);
  var [description, setDescription] = useState(props.data.description);
  return (
    <article>
      <h1>Update</h1>
      <form
        action="topics"
        method="put"
        onSubmit={function(e) {
          //todo
        }}
      >
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={title}
            onChange={function(e) {
              //todo 
            }}
          />
        </p>
        <p>
          <textarea
            name="description"
            placeholder="description"
            value={description}
            onChange={function(e) {
              //todo
            }}
          />
        </p>
        <p>
          <input type="submit" />
        </p>
      </form>
    </article>
  );
}
function Control(props) {
  //todo
}
export default function App() {
  //todo
  } else if (mode === "UPDATE") {
    var selectedTopic = null;
    for (var i = 0; i < topics.length; i++) {
      if (topics[i].id === selectedId) {
        selectedTopic = topics[i];
        break;
      }
    }
    article = (
      <Update
        data={selectedTopic}
        onUpdate={function(data) {
          var newTopics = [...topics];
          for (var i = 0; i < newTopics.length; i++) {
            if (newTopics[i].id === data.id) {
              newTopics[i].title = data.title;
              newTopics[i].description = data.description;
              break;
            }
          }
          setTopics(newTopics);
          setMode("READ");
        }}
      />
    );
  }
  //...
}

결과

step 7

목표

삭제를 구현하고 싶습니다.

힌트

<Control
    onChangeMode={function(mode) {
      if (mode === "DELETE") {
        todo 
      }
    }}
/>

결과

step 8

목표

코드가 너무 많아졌습니다. 코드를 파일로 분리해서 정리 정돈하고 싶습니다.

알아야 할 것

export, import
https://stackblitz.com/edit/react-exim?file=src/App.js
MyModule.js

// 외부에 노출을 원하지 않는 함수
function privateFunc() {
  return "private";
}
// privateFunc를 이용해서 외부에서 사용되도록 고안된 함수
function publicFunc() {
  return privateFunc() + " + " + "public";
}
// 외부에서 사용할 수 있도록 export 한다.
export { publicFunc };
// 외부에서 renamedFunc로 사용하도록 한다.
export { publicFunc as renamedFunc };
// var, const, let도 가능하다.
export var publicVar = "public var";
function defaultFunc() {
  return "default";
}
export default defaultFunc;

App.js

import { publicFunc } from "./MyModule";
console.log(publicFunc());
import { renamedFunc } from "./MyModule";
console.log(renamedFunc());
import { publicVar } from "./MyModule";
console.log(publicVar);
import my from "./MyModule";
console.log(my());

결과

step 9 - ajax

이번 임무는 step8을 선행학습으로 하고 있습니다

목표

ajax를 이용해서 리로드 없이 동적으로 데이터를 변경하고 싶습니다.

알아야 할 것

useEffect - 사이드 이팩트란 무엇인가? react 에서 ajax 통신하는 방법 (https://devhints.io/js-fetch) .env.development.template => .env.development 로 변경해서 서버와 통신 할 REACT_APP_API_URL 값을 셋팅해줘야 한다.

순수자바스크립트로 구현한 예제

https://stackblitz.com/edit/web-vanilla?file=index.html

백엔드 구현

db

CREATE DATABASE mydb;
USE mydb;
CREATE TABLE `topic` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(45) DEFAULT NULL,
  `description` text,
  `created` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
);
INSERT INTO `topic` VALUES (1,'MySQL','MySQL is ...','2021-01-07 11:37:35'),(2,'Oracle','Oracle is ...','2021-01-07 11:37:35'),(3,'SQL Server','SQL Server is ...','2021-01-07 11:37:35');

python3

import random
from flask import Flask, request, redirect, send_from_directory, render_template, make_response, make_response, Response
import json
from flask_cors import CORS


import pymysql
app = Flask(__name__)
CORS(app)

resp = Response()
resp.headers['Access-Control-Allow-Origin'] = '*'


@app.route('/topics', defaults={'topic_id':None}, methods=['GET', 'POST'])
@app.route('/topics/<topic_id>', methods=['PUT', 'GET', 'DELETE'])
def topics(topic_id):
    try:
        db = pymysql.connect(
            user='root', 
            passwd='devpass', 
            host='127.0.0.1',
            port=3306,
            db='mydb', 
            charset='utf8',
            autocommit=True
        )
        cursor = db.cursor(pymysql.cursors.DictCursor)
        
        if request.method == 'GET':
            if topic_id == None:
                sql = "SELECT * FROM topic"
                cursor.execute(sql)
                topics = cursor.fetchall()
                output = []
                for topic in topics:
                    output.append({'id':topic['id'], 'title':topic['title'], 'created':topic['created'].strftime('%Y-%m-%d %H:%M:%S')})
                resp.set_data(json.dumps(output))
                return resp
            else:
                sql = "SELECT * FROM topic WHERE id = "+str(topic_id)
                cursor.execute(sql)
                print('sql', sql)
                topic = cursor.fetchone()
                output = json.dumps({'id':topic['id'], 'title':topic['title'], 'description':topic['description'], 'created':topic['created'].strftime('%Y-%m-%d %H:%M:%S')})
                resp.set_data(output)
                return resp
        elif request.method == 'POST':
            form = request.get_json()
            print('form', form)
            sql = """
                INSERT INTO topic (title, description, created) 
                VALUES(
                    '%(title)s',
                    '%(description)s',
                    NOW()
                )
                """ % {'title':form['title'], 'description':form['description']}
            cursor.execute(sql)
            output = json.dumps({'id':cursor.lastrowid, 'title':form['title'], 'description':form['description'] })
            resp.set_data(output)
            return resp
        elif request.method == 'PUT':
            form = request.get_json()
            sql = """
                UPDATE topic SET 
                    title = '%(title)s', 
                    description = '%(description)s'
                WHERE id = '%(topic_id)s'
                """ % {'topic_id': topic_id, 'title':form['title'], 'description':form['description']}
            cursor.execute(sql)
            output = json.dumps({'id':topic_id, 'title':form['title'], 'description':form['description'] })
            resp.set_data(output)
            return resp
        elif request.method == 'DELETE':
            sql = """
                DELETE FROM topic WHERE id = %(id)s            
                """ % {'id':topic_id}
            cursor.execute(sql)
            output = json.dumps({'id':topic_id})
            resp.set_data(output)
            return resp
    finally:
        cursor.close()
        db.close()



@app.route('/static/<path:path>')
def static_files(path):
    return send_from_directory('static', path)

@app.route('/', defaults={'path':None})
@app.route('/<path>')
def index(path):
    resp.set_data(render_template('index.html'))    
    return resp

if __name__ == '__main__':
	app.run(debug=True)

결과

step 9 - route

이번 임무는 step8을 선행학습으로 하고 있습니다

목표

UI에 따라서 페이지 리로딩 없이 URL이 변경되고, URL에 따라서 적당한 정보가 표시되도록 개선하고 싶습니다.

알아야 할 것

  • history.pushState를 이용해서 URL을 변경하는 방법

결과

step 10 - route

목표

step 9 - route 에서 구현한 내용을 react-route-dom을 이용해서 개선하고 싶습니다.

알아야할 것

  • react-route-dom을 이용하는 방법
  • react-route-dom을 이용해서 if 문을 없애고, 보다 단순하게 코드를 제어할 수 있게 됨을 공감

참고

결과

Clone this wiki locally