# 3 프론트엔드 개발

## 3.1 프론트엔드 개발환경 설정

### 3.1.1 Node.js 와 NPM 설치

* 프론트엔드
  * 사용자 바로 앞에서 사용자와 상호작용
  * 애플리케이션 로직을 수행하고 백엔드 서버로 요청을 보냄
  * 웹 서비스에서 클라이언트 혹은 프론트엔드란 웹 브라우저를 뜻함
    * 사용자는 자신의 컴퓨터에서 브라우저 실행
    * 브라우저는 인터넷을 이용해 서버에 있는 자원(HTML, JavaScript, CSS)을 사용자의 컴퓨터로 다운로드 및 실행



* Node.js
  * Node.js가 등장하기 전까지 자바스크립트는 브라우저 내에서만 실행 가능
    * 즉, JS는 브라우저상에서 HTML 렌더링의 일부로 실행하거나 개발자창의 자바스크립트 콘솔을 이용해 실행
  * Node.js는 자바스크립트를 컴퓨터에서 실행할 수 있게 해주는 JS 런타임 환경
    * 이는 곧 JS를 클라이언트 언어뿐 아니라 서버 언어로도 사용할 수 있다는 뜻
  * Node.js는 구글 크롬의 V8 자바스크립트 엔진을 실행



* NPM (Node Package Manager)
  * Node.js의 패키지 관리 시스템
  * NPM을 이용해 npmjs에서 Node.js 라이브러리를 설치
  * Node.js를 설치하면 함께 설치

node 프로젝트를 초기화하려면, npm init을 사용 (여기서는 npx라는 툴로 리액트 애플리케이션을 초기화할 예정)

### 3.1.2 비주얼 스튜디오 코드 설치

### 3.1.3 프론트엔드 애플리케이션 생성

리액트 애플리케이션 생성

```java
>npx create-react-app todo-react-app
```

프로젝트 폴더로 이동 및 애플리케이션 실행

```java
>cd todo-react-app

> npm start
```

* 비주얼 스튜디오 코드에서 개발 환경 설정
  * 비주얼 스튜디오 코드에 워크스페이스 생성
    * File → Add Foler to Workspace
* create-react-app 기본 생성 파일
  * package.json
    * 프로젝트의 메타데이터로 사용할 node.js 패키지 목록 등을 포함
  * public 디렉터리 아래 파일
    * index.html
      * http://localhost:3000이 가장 처음으로 리턴하는 HTML 파일
      * 리액트에서 html 파일은 index.html 하나 밖에 없음
      * 다른 페이지들은 React.js를 통해 생성되고 index.html에 있는 root 엘리먼트 아래에 동적으로 랜더링
  * src 디렉터리 아래의 파일
    * index.js는 index.html과 함께 가장 처음으로 실행되는 JS 코드
    * 이 자바스크립트 코드가 **리액트 컴포넌트**를 root 아래에 추가
    * App.js는 기본으로 생성된 리액트 컴포넌트

### 3.1.4 material-ui 패키지 설치



material-ui/core 설치

```java
> npm install @material-ui/core
```

material-ui/icons 설치

```java
npm install @material-ui/icons
```

### 3.1.5 브라우저의 동작 원리



* 브라우저 동작 원리
  * 브라우저 주소창에 [https://google.com](https://google.com) 같은 웹 주소를 입력하면
  * 브라우저는 HTTP GET 요청을[https://google.com의](https://google.xn--com-yh0o) 서버로 전송
  * 보통 프론트엔드가 있는 웹 서비스의 경우 HTML 파일을 결과로 반환
* HTML을 받은 후 텍스트로된 HTML을 브라우저에 보여주기까지
  1. 파싱
     1. 브라우저는 HTML을 트리 자료 구조 형태인 DOM 트리로 변환
     2. IMAGE, CSS, SCRIPT 등 다운로드해야 하는 리소스를 다운로드 (CSS의 경우 다운로드 후 CSS를 CSSOM 트리로 변환)
     3. 다운받은 자바스크립트를 인터프리트, 컴파일, 파싱 및 실행
  2. 렌더링
     1. DOM 트리와 CSSOM 트리를 합쳐 렌더 트리를 만듬
        * 내용인 DOM과 디자인인 CSSOM을 합침
        * 트리의 각 노드가 브라우저의 어디에 배치될지, 어떤 크기로 배치될지를 정하는 것
     2. 브라우저 스크린에 렌더 트리의 각 노드를 그려줌

위와 같은 2 단계를 거치면 사용자는 브라우저상에서 시각화된 HTML 파일을 볼 수 있게 됨

{% hint style="info" %}
개발자툴 → 디버깅창 : HTML Elements, CS, 자바스크립트 콘솔, **네트워크 탭**, **Application 탭**
{% endhint %}



![https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model?hl=ko](https://2669959621-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxncaDDrGpdjhVy86HbwM%2Fuploads%2FoeG11x7sWraUyrzKLiaC%2Fimage.png?alt=media\&token=b11b2900-3dee-4814-96cf-52ccbaf58757)

### 3.1.6 React.js&#x20;



* SPA (Single Page Application)
  * 웹 페이지를 로딩하면 사용자가 임의로 새로 고침하지 않는 이상 페이지를 새로 로딩하지 않는 애플리케이션
  * 서버에게 새 HTML 페이지를 요청하지 않고 자바스크립트가 동적으로 HTML을 재구성
  * 브라우저의 자바스크립트는 fetch, ajax 등의 함수로 서버에 데이터를 요청하고 받은 데이터를 이용해 자바스크립트 내에서 HTML 재구성
* React.js 첫 화면
  * 하얀 화면 : index.html 로딩중
    * HTML이 \<body>\</body>를 렌더링하다보면 마지막에 bundle.js라는 스크립트 로딩
    * bundle.js는 npm start를 실행했을 떄 만들어지는 스크립트인데, index.js를 포함
    * index.js의 일부로 ReactDom.render() 함수가 실행
    * render() 함수가 동적으로 HTML 엘리먼트를 리액트 첫 화면으로 바꿔줌
    * 이떄 렌더링된 하위 엘리먼트는 render() 함수의 매개변수로 들어가는 \<App /> 부분
* React 컴포넌트
  * 컴포넌트는 자바스크립트 함수 또는 자바스크립트 클래스 형태로 생성할 수 있음

{% tabs %}
{% tab title="함수 컴포넌트" %}
```javascript
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
    return (
        <div className="App">
          // JSX code
         </div>
    )
}

export default App;
```
{% endtab %}

{% tab title="클래스 컴포넌트" %}
```javascript
import React from 'react';
import logo from './logo.svg';
import './App.css';

class App extends React.Component {
    render() {
        return (
            <div className="App">
                //JSX code
            </div>
        ) 
}

export default App;
```
{% endtab %}
{% endtabs %}





* JSX
  * React가 한 파일에서 HTML과 자바스크립트를 함께 사용하려고 확장한 자바스크립트 문법
  * JSX 문법은 Babel이라는 라이브러리가 빌드 시 자바스크립트로 번역해 줌

```java
//index.js App 렌더링
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import AppRouter from "./AppRouter";

ReactDOM.render(
  <React.StrictMode>
    <AppRouter />
  </React.StrictMode>,
  document.getElementById("root")
);
```

ReacrDOM은 매개변수로 받은 \<App /> 컴포넌트를 이용해 DOM트리를 만들고, 이때 컴포넌트의 render() 함수가 반환한 JSX를 렌더링 함

* import를 이용해 App 컴포넌트를 불러옴
* <컴포넌트이름 />을 이용해 컴포넌트를 사용
* ReactDOM.render
  * 첫 번째 매개변수로 리액트 컴포넌트를 받음
  * 두 번째 매개변수로는 root 엘리먼트를 받음
    * root 엘리먼트에 첫 번째 매개변수로 넘겨진 리액트 컴포넌트를 root 엘리먼트 아래에 추가하라는 뜻
    * root 엘리먼트는 index.html에 정의되어 있음

## 3.2 프론트엔드 서비스 개발

⚠️ HTML Mock을 우선적으로 작성하고 UI에 들어가게 될 가짜 데이터 작성

### 3.2.1 Todo List

* Props와 State 적용, material-ui 적용

{% tabs %}
{% tab title="Todo.js" %}
```javascript
import React from "react";
import { ListItem, ListItemText, InputBase, Checkbox } from "@material-ui/core";

class Todo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { item: props.item };
  }
  render() {
    const item = this.state.item;
    return (
      <ListItem>
        <Checkbox checked={item.done} />
        <ListItemText>
          <InputBase
            inputProps={{ "arial-label": "naked" }}
            type="text"
            id={item.id}
            name={item.id}
            value={item.title}
            multiline={true}
            fullWidth={true}
          />
        </ListItemText>
      </ListItem>
    );
  }
}

export default Todo;
```
{% endtab %}

{% tab title="App.js" %}
```javascript
import React from "react";
import "./App.css";
import Todo from "./Todo.js";
import { Paper, List } from "@material-ui/core";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        { id: 0, title: "Hello World1 !", done: true },
        { id: 1, title: "Hello World2 !", done: false },
      ],
    };
  }

  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{ margin: 16 }}>
        <List>
          {this.state.items.map((item, idx) => (
            <Todo item={item} key={item.id} />
          ))}
        </List>
      </Paper>
    );

    return <div className="App">{todoItems}</div>;
  }
}

export default App;
```
{% endtab %}
{% endtabs %}



### 3.2.2 Todo 추가&#x20;

* Add 핸들러 추가
  * **onInputChange** : 사용자가 input 필드에 키를 하나 입력할 때마다 실행되며 input 필드에 담긴 문자열을 자바스크립트 오브젝트에 저장
  * **onButtonClick** : 사용자가 + 버튼을 클릭할 때 실행되며 onInputChange에서 저장하고 있던 문자열을 리스트에 추가
  * **enterKeyEventHandler** : 사용자가 input 필드상에서 엔터 또는 리턴키를 눌렀을 때 실행되며 기능은 onButtonClick과 같음

{% tabs %}
{% tab title="AddTodo.js" %}
```javascript
import React from "react";
import { TextField, Paper, Button, Grid } from "@material-ui/core";

class AddTodo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { item: { title: "" } };
    this.add = props.add;
  }

  onInputChange = (e) => {
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({ item: thisItem });
    console.log(thisItem);
  };

  onButtonClick = () => {
    this.add(this.state.item);
    this.setState({ item: { title: "" } });
  };

  enterKeyEventHandler = (e) => {
    if (e.key === "Enter") {
      this.onButtonClick();
    }
  };

  render() {
    return (
      <Paper style={{ margin: 16, padding: 16 }}>
        <Grid container>
          <Grid xs={11} md={11} item style={{ paddingRight: 16 }}>
            <TextField
              placeholder="Add Todo here"
              fullWidth
              onChange={this.onInputChange}
              value={this.state.item.title}
              onKeyPress={this.enterKeyEventHandler}
            />
          </Grid>
          <Grid xs={1} md={1} item>
            <Button
              fullWidth
              color="secondary"
              variant="outlined"
              onClick={this.onButtonClick}
            >
              +
            </Button>
          </Grid>
        </Grid>
      </Paper>
    );
  }
}

export default AddTodo;

```
{% endtab %}

{% tab title="App.js" %}
```javascript
import React from "react";
import "./App.css";
import Todo from "./Todo.js";
import AddTodo from "./AddTodo.js";
import {
  Paper,
  List,
  Container
} from "@material-ui/core";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        { id: 0, title: "Hello World1 !", done: true },
        { id: 1, title: "Hello World2 !", done: false },
      ],
    };
  }

  add = (item) => {
    const thisItems = this.state.items;
    item.js = "ID-" + thisItems.length;
    item.done= false;
    thisItems.push(item);
    this.setState({ items: thisItems });
    console.log("items: ", this.state.items);
  };

  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{ margin: 16 }}>
        <List>
          {this.state.items.map((item, idx) => (
            <Todo
              item={item}
              key={item.id}
            />
          ))}
        </List>
      </Paper>
    );

    return (
      <div className="App">
        <Container maxWidth="md">
          <AddTodo add={this.add} />
          <div className="TodoList">{todoItems}</div>
        </Container>
      </div>
    );
  }
}

export default App;

```
{% endtab %}
{% endtabs %}

### 3.2.3. Todo 삭제

{% tabs %}
{% tab title="Todo.js" %}
```javascript
import React from "react";
import {
  ListItem,
  ListItemText,
  InputBase,
  Checkbox,
  ListItemSecondaryAction,
  IconButton,
} from "@material-ui/core";
import DeleteOutlined from "@material-ui/icons/DeleteOutlined";

class Todo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { item: props.item };
    this.delete = props.delete;
  }

  deleteEventHandler = () => {
    this.delete(this.state.item);
  };

  checkboxEventHandler = (e) => {
    const thisItem = this.state.item;
    thisItem.done = !thisItem.done;
    this.setState({ item: thisItem });
    this.update(this.state.item);
  };

  offReadOnlyMode = () => {
    console.log("Event !!", this.state.readOnly);
    this.setState({ readOnly: false }, () => {
      console.log("ReadOnly? ", this.state.readOnly);
    });
  };

  enterKeyEventHandler = (e) => {
    if (e.key === "Enter") {
      this.setState({ readOnly: true });
      this.update(this.state.item);
    }
  };

  editEventHandler = (e) => {
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({ item: thisItem });
  };
  render() {
    const item = this.state.item;
    return (
      <ListItem>
        <Checkbox checked={item.done} onChange={this.checkboxEventHandler} />
        <ListItemText>
          <InputBase
            inputProps={{
              "arial-label": "naked",
              readOnly: this.state.readOnly,
            }}
            onClick={this.offReadOnlyMode}
            onKeyPress={this.enterKeyEventHandler}
            onChange={this.editEventHandler}
            type="text"
            id={item.id}
            name={item.id}
            value={item.title}
            multiline={true}
            fullWidth={true}
          />
        </ListItemText>
        <ListItemSecondaryAction>
          <IconButton
            arial-label="Delete Todo"
            onClick={this.deleteEventHandler}
          >
            <DeleteOutlined />
          </IconButton>
        </ListItemSecondaryAction>
      </ListItem>
    );
  }
}

export default Todo;

```
{% endtab %}

{% tab title="App.js" %}
```javascript
import React from "react";
import "./App.css";
import Todo from "./Todo.js";
import AddTodo from "./AddTodo.js";
import {
  Paper,
  List,
  Container
} from "@material-ui/core";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [ ],
    };
  }

  add = (item) => {
    const thisItems = this.state.items;
    item.js = "ID-" + thisItems.length;
    item.done= false;
    thisItems.push(item);
    this.setState({ items: thisItems });
    console.log("items: ", this.state.items);
  };
  
  delete = (item) => {
    const thisItems = this.state.items;
    console.log("Before Update Items : ", this.state.items);
    const newItems = thisItems.filter(e => e.id !==item.id);
    this.setState({ items: newItems }, () => {
      console.log("Update Items : ", this.state.items );
    });
  };
  
  render() {
    var todoItems = this.state.items.length > 0 && (
      <Paper style={{ margin: 16 }}>
        <List>
          {this.state.items.map((item, idx) => (
            <Todo
              item={item}
              key={item.id}
              delete={this.delete}
            />
          ))}
        </List>
      </Paper>
    );

    return (
      <div className="App">
        <Container maxWidth="md">
          <AddTodo add={this.add} />
          <div className="TodoList">{todoItems}</div>
        </Container>
      </div>
    );
  }
}

export default App;

```
{% endtab %}
{% endtabs %}

### 3.2.4 Todo 수정

1. 체크박스에 체크하는 경우
2. 타이틀을 변경하고 싶은 경우

```javascript
// Todo.js
import React from "react";
import {
  ListItem,
  ListItemText,
  InputBase,
  Checkbox,
  ListItemSecondaryAction,
  IconButton,
} from "@material-ui/core";
import DeleteOutlined from "@material-ui/icons/DeleteOutlined";

class Todo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { item: props.item, readOnly: true };
    this.delete = props.delete;
  }

  deleteEventHandler = () => {
    this.delete(this.state.item);
  };

  checkboxEventHandler = (e) => {
    const thisItem = this.state.item;
    thisItem.done = !thisItem.done;
    this.setState({ item: thisItem });
    this.update(this.state.item);
  };

  offReadOnlyMode = () => {
    console.log("Event !!", this.state.readOnly);
    this.setState({ readOnly: false }, () => {
      console.log("ReadOnly? ", this.state.readOnly);
    });
  };

  enterKeyEventHandler = (e) => {
    if (e.key === "Enter") {
      this.setState({ readOnly: true });
      this.update(this.state.item);
    }
  };

  editEventHandler = (e) => {
    const thisItem = this.state.item;
    thisItem.title = e.target.value;
    this.setState({ item: thisItem });
  };
  
  render() {
    const item = this.state.item;
    return (
      <ListItem>
        <Checkbox checked={item.done} onChange={this.checkboxEventHandler} />
        <ListItemText>
          <InputBase
            inputProps={{
              "arial-label": "naked",
              readOnly: this.state.readOnly,
            }}
            onClick={this.offReadOnlyMode}
            onKeyPress={this.enterKeyEventHandler}
            onChange={this.editEventHandler}
            type="text"
            id={item.id}
            name={item.id}
            value={item.title}
            multiline={true}
            fullWidth={true}
          />
        </ListItemText>
        <ListItemSecondaryAction>
          <IconButton
            arial-label="Delete Todo"
            onClick={this.deleteEventHandler}
          >
            <DeleteOutlined />
          </IconButton>
        </ListItemSecondaryAction>
      </ListItem>
    );
  }
}

export default Todo;

```

## 3.3 서비스 통합&#x20;

### 3.4.1 componentDidMount

* 렌더링 과정
  * 리액트는 브라우저에 보이는 HTML DOM 트리의 다른 버전인 ReactDOM(Virtual DOM)을 갖고 있음
  * 어떤 이유에서든 컴포넌트의 상태(state)가 변하면 ReactDOM은 이를 감지하고 변경된 부분의 HTML을 바꿔줌
  * HTML이 업데이트되면 우리는 변경된 결과를 눈으로 확인



* 마운팅 과정
  * 랜더링이 맨 처음 일어나는 순간,
  * 즉 ReactDOM 트리가 존재하지 않는 상태에서
  * 리액트가 처음으로 각 컴포넌트의 render() 함수를 콜해 자긴의 DOM 트리를 구성

```javascript
// App.js
componentDidMount() {
    const requestOptions = {
        method: "GET",
        headers: { "Content-Type": "application/json" },
    };
    
    fetch("htpp://localhost:8080/todo", requestOptions)
        .then((response) => response.json())
        .then(
            (response) => {
                this.setState({
                    items: response.data,
                });
            },
            (error) => {
                this.setState({
                    error,
                });
            }
        );
}
```

CORS 에러 메세지  뜸

![](https://2669959621-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxncaDDrGpdjhVy86HbwM%2Fuploads%2Fg5aRf3gS4LaGDoXZ0TyE%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-14%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.20.20.png?alt=media\&token=664ea32b-139a-4fa1-ad9f-86c2241737fa)

### 3.4.2 CORS

* CORS (Cross-Origin Resource Sharing)
  * 처음 리소스를 제공한 도메인이 현재 요청하려는 도메인과 다르더라도 요청을 허락해 주는 웹 보안 방침
  * CORS가 가능하려면 백엔드에서 CORS 에서 방침 설정을 해줘야 함

```java
package com.todoweb.todoSpringApp.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    private final long MAX_AGE_SECS = 3600;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("<http://localhost:3000>")
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(MAX_AGE_SECS);
    }
}
```

### 3.4.3 fetch



*   [https://developer.mozilla.org/en-US/docs/Web/API/Fetch\_API/Using\_fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_fetch)


* 비동기 오퍼레이션
  * 만약 내가 HTTP 요청을 백엔드에 보냈는데 백엔드가 이를 처리하는 데 1분이 걸리면 내 브라우저는 1분간 아무것도 못하는 상태가 됨
* 콜백 함수
  * 오퍼레이션이 현재 실행 중인 자바스크립트 스레드가 아니라 드른 곳에서 실행된다면, HTTP 응답을 받았다는 사실을 콜백 함수를 통해 알 수 있음

```java
// 콜백 함수를 이용한 XMLHttpRequest
var oReq = new XMLHttpRuest();
oReq.open("GET", "<http://localhost:8080/todo>");
oReq.onload = function () {
	console.log(oReq.response);
};
oReq.send();
```

* 콜백 지옥
  * XMLHttpRequest를 사용하는 경우 콜백 함수 내에서 또 다른 HTTP 요청을 하고 그 두 번째 요청을 위한 콜백을 또 정의하는 과정에서 코드가 굉장히 복잡해짐.
* Promise
  * 콜백 지옥을 피할 수 있음
  * 비동기 오퍼레이션에서 사용
  * 이 함수를 실행한 후 Promise 오브젝트에 명시된 사항들을 실행시켜 주겠다는 약속
  * 세 가지 상태, Pending, Resolve, Reject가 있음
    * pending : 오퍼레이션이 끝나길 기다리는 상태
    * resolve : 오퍼레이션이 성공적으로 끝남. 이때 resolve는 then의 매개변수로 넘어오는 함수를 실행
    * reject : 오퍼레이션 중 에러가 나는 경우. 그 결과 catch 매개변수로 넘어오는 함수가 실행
  * them이나 catch로 넘기는 함수들은 지금 당장 실행되는 것은 아님
    * 실제 함수들이 실행되는 시점은 resolve와 reject가 실행되는 시점

```java
// Promise를 사용한 XMLHttpRequest
function exampleFunction() {
	return new Promise((resolve, reject) => {
		var oReq = new XMLHttpRequest();
		oRequ.open("GET", "<http://localhost:8080/todo>");
		oReq.onload = function () {
			resolve(oReq.response);
		};
		oReq.onerror = fuction () {
			reject(oReq.response);
		};
		oReq.send();
	});
}

exampleFunction()
	.then((r) => console.log("Resolved " + r))
	.catch((e) => console.log("Rejectd " + e));
```

* **Fetch API**
  * API 서버로 http 요청을 송신 및 수신
  * 자바스크립트가 제공하는 메서드
  * url을 매개변수로 받거나 url과 options을 매개변수로 받음
  * fetch()함수는 Promise 오브젝트를 리턴
  * then과 catch에 콜백 함수를 전달해 응답을 처리

```java
fetch("localhost:8080/todo")
	.then(response => {
		//respone 수신 시 하고 싶은 작업
	})
	.catch(e => {
		// 에러가 났을 때 하고싶은 작업
  })
```

* fetch는 첫번째 매개변수로 uri를 받음
* 디폴트로 GET 메서드를 사용
* then에는 응답을 받은 후 실행할 함수 response ⇒ {}를 매개변수로 넘김
* catch에는 예외 발생 시 실행할 함수 e ⇒ {}를 넘김
* 메서드를 명시하고 싶은 경우나 헤더와 바디를 함께 보내야 할 경우에는 아래와 같이 주 번째 매개변수에 요청에 대한 정보가 담긴 오브젝트를 넘겨줌

```java
options = {
	method: "POST",
	headers: [
		["Content-Type", "application/json"]
	],
	body: JSON.stringify(data)
};

fetch("localhost:8080/todo", options)
	.then(response => {
		// response 수신 시 하고싶은 작업
	})
	.catch(e => {
	
	})
```

* 하드코딩된 “localhost:8080/todo” 리팩터링

src/api-config.js

```java
let backendHost;

const hostname = window && window.location && window.location.hostname;

if (hostname === "localhost") {
	backendHost = "<http://localhost:8080>";
}

export const API_BASE_URL = `${backendHost}`;
```

* 백엔드로 요청을 보낼 때 사용할 유틸리티 함수를 작성

{% tabs %}
{% tab title="ApiService" %}
```javascript
import { API_BASE_URL } from "../app-config";

export function call(api, method, request) {

  options = {
    headers: new Headers({
    "Content-Type": "application/json",
  }),
    url: API_BASE_URL + api,
    method: method,
  };
  if (request) {
    // GET method
    options.body = JSON.stringify(request);
  }

  return fetch(options.url, options)
    .then((response) =>
      response.json().then((json) => {
        if (!response.ok) {
          // response.ok가 true이면 정상적인 리스폰스를 받은것, 아니면 에러 리스폰스를 받은것.
          return Promise.reject(json);
        }
        return json;
      })
    )}
  );
}

```
{% endtab %}

{% tab title="or" %}
```
// 이런 유틸리티 함수가 없다면 반복해야 될 코드
```
{% endtab %}
{% endtabs %}



```java
// App컴포너트에서 ApiService 사용
import { call } from "./service/ApiService.js";

/* 기존 코드 */ 

componentDidMount() {
    call("/todo", "GET", null).then((response) =>
      this.setState({ items: response.data, loading: false })
    );
  }

  add = (item) => {
    call("/todo", "POST", item).then((response) =>
      this.setState({ items: response.data })
    );
  };

  delete = (item) => {
    call("/todo", "DELETE", item).then((response) =>
      this.setState({ items: response.data })
    );
  };
```

**Todo Update 수정**

* 프론트엔드 UI 부분을 위한 mock() 함수에서는 사용자가 키보드에서 입력하면 editEventHandler 함수가 title을 수정해 줌
* 따로 update() 함수를 App.js에 만들지 않아도 잘 동작
* 하지만 API를 이용해 update하려면 Serve API를 이용해 서버 데이터를 업데이트한 후
* 변경된 내용을 화면에 다시 출력하는 두 가지 작업이 필요

```java
// App 컴포넌트에 update() 함수 구현
update = (item) => {
    call("/todo", "PUT", item).then((response) =>
      this.setState({ items: response.data })
    );
  };
```

```java
// App 컴포넌트에서 Todo의 props에 연결
<Todo 
	item={item}
  key={item.id}
  delete={this.delete}
  update={this.update}
/>
```

```java
// Todo 컴포넌트에서 update 연결 및 사용
constructor(props) {
    super(props);
    this.state = { item: props.item, readOnly: true };
    this.delete = props.delete;
    this.update = props.update;
  }

enterKeyEventHandler = (e) => {
    if (e.key === "Enter") {
      this.setState({ readOnly: true });
      this.update(this.state.item);
    }
  };

checkboxEventHandler = (e) => {
    const thisItem = this.state.item;
    thisItem.done = !thisItem.done;
    this.setState({ item: thisItem });
    this.update(this.state.item);
  };
```

