비동기 API 호출 연습

나혜수·2023년 2월 27일
0

자바스크립트 실전

목록 보기
9/19

작업 순서

  • api.js 만들기
    api 호출을 담당하는 코드를 api.js에 작성하여, 다른 js파일에서 api 호출 코드를 반복하지 않도록 한다.

  • TodoList 컴포넌트 만들기

  • TodoComments 컴포넌트 만들기

  • App 컴포넌트 만들기



index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Second Todo List</title>
</head>
<body>
    <main class ="app"></main>
    <script src ="./src/main.js" type="module"></script>
</body>
</html>

api.js

XMLHTTPRequest를 이용해 api를 불러온다. 참고로 XMLHTTPRequest는 요즘 잘 쓰이지 않고 `fetch가 많이 사용된다.

export function request (url, successCallback, failCallback) {
    const xhr = new XMLHttpRequest()
    xhr.addEventListener("load", (e) => {
        if(xhr.readyState ===4) {
            if(xhr.status === 200) {
                successCallback(JSON.parse(xhr.responseText))
            } else {
                failCallback(xhr.statusText)
            }
        }
    })
    xhr.addEventListener('error', (e) => failCallback(xhr.statusText))

    xhr.open('GET', url)
    xhr.send()
}

TodoList.js

onclick은 TodoList 안에 있는 항목을 클릭했을 때 실행할 콜백함수이다.
this.state.map( )에서 .join('')을 빠뜨리면 안된다. map 함수는 새로운 배열을 만들고, join은 배열을 한 줄로 만든다.

이벤트 바인딩을 render 함수 밖에서 하면 문제가 생긴다. innerHTML의 경우 이전에 쓰인걸 모두 날리고 다시 렌더링하는 것이기 때문에 이전에 걸었던 이벤트가 날라가 버린다. 따라서 렌더링하는 곳에서 매번 이벤트를 다시 걸어줘야 한다.

export default function TodoList({$target, initialState, onClick}) {
    const $element = document.createElement('div')
    $target.appendChild($element)

    this.state = initialState

    this.setState = nextState => {
        this.state = nextState
        this.render()
    }

    this.render = () => {
        if(Array.isArray(this.state)) {
        $element.innerHTML = `
            <h1>Simple TodoList</h1>
            <ul>
                ${this.state.map(({id, text}) => 
                   `<li data-id="${id}>${text}</li>`).join('')}
            </ul>
        `
        // 이벤트 바인딩을 render 함수 밖에서 하면 문제가 생긴다. 
        $element.querySelectorAll('li').forEach($li =>  {
                $li.addEventListener('click', (e) => {
                    const {id} = e.target.dataset
                    onClick(parseInt(id,10))
                })
            })
        }
    }
    this.render()
}

TodoComments.js

export default function TodoComments({$target, initialState}) {
    const $element = document.createElement('div')
    $target.appendChild($element)

    this.state = initialState

    this.setState = nextState => {
        this.state = nextState
        this.render()
    }

    this.render = () => {
        const {selectedTodo, comments} = this.state

        if(!selectedTodo || !comments) {
            $element.innerHTML = ''
            return
        }
        $element.innerHTML = `
            <h2>${selectedTodo.text}의 댓글들</h2>
            ${comments.length === 0? '댓글이 없습니다' : ''}
            <ul>
                ${comments.map(({content})=> `<li>${content}</li>`).join('')}
            </ul>
        `
    }
    this.render()
}

App.js

import TodoList from "./TodoList.js";
import TodoComments from "./TodoComments.js";
import { request } from "./api.js";

export default function App({$app}) {
    this.state = {
        todos: [],
        selectedTodo: null,
        comments:[]
    }

    this.setState = nextState => {
        this.state = nextState
        todoList.setState(this.state.todos)
        todoComments.setState({
            selectedTodo: this.state.selectedTodo,
            comments: this.state.comments
        })
    }

    const todoList = new TodoList({
        $target: $app,
        initialState: this.todos,
        onClick: id => {
            const selectedTodo = this.state.todos.find(todo => todo.id ===id)
            this.setState({
                ...this.state,
                selectedTodo
            })
            request(`https://kdt.roto.codes/comments?todo.id=${id}`, 
                    (comments) => {this.setState({...this.state,comments})
            })
        }
    })

    const todoComments = new TodoComments({
        $target: $app,
        initialState: {
            selectedTodo: this.state.selectedTodo,
            comments: this.state.comments
        }
    })
    
    // todos를 불러오기 
    this.init = () => {
        request(`https://kdt.roto.codes/todos`, (todos) => {
            this.setState({
                ...this.state,
                todos
            })
        })
    }
    this.init()
}

main.js

import App from'./App.js'

const $app = document.querySelector('.app')

new App({$app})

API를 붙이기 전에 main.js에 더미 데이터를 넣고 렌더링이 잘되는지 먼저 확인해보는 것이 좋다. 그래야 오류가 발생했을 때, API를 붙이는 과정에서의 문제인지 다른 코드에서의 문제인지 확인이 가능하다.

new TodoList({
  $target : $app,
  initialSrate : [
    {
      text : "Learn React"
    },
    {
      text : "Learn Javascript"
    },
    {
      text : "Learn CSS"
    }
  ]
})
  new TodoComments({
  $target : $app,
  initialSrate : {
    selectedTodo : {
      text : "Learn Javascript"
    },
    comments : [
      {
        text : "안녕하세요"
      },
      {
        text : "반가워요"
      },
    ]
  }
})
profile
오늘도 신나개 🐶

0개의 댓글