오늘은 어제 진행했던 Todolist-noDB에서 백엔드부분에 약간의 코드를 추가하고, 프론트엔드에 대한 부분을 코드를 짜고 프론트엔드와 백엔드를 연결해주는 과정을 들었다.
<App.jsx> import TodoCard from "./components/TodoCard"; import axios from "axios"; import { useEffect, useState } from "react"; import CreateToDo from "./components/CreateTodo"; function App() { const [toDoList, setToDoList] = useState(); const getToDoList = async () => { try { const response = await axios.get( `${process.env.REACT_APP_BACKEND_URL}/todo` ); if (response.status !== 200) { alert("요청을 불러오지 못했습니다."); return; } setToDoList(response.data); } catch (error) { console.error(error); } }; useEffect(() => { getToDoList(); }, []); return ( <div className="min-h-screen flex flex-col justify-start items-center pt-16"> <h1 className="text-4xl font-bold">AWESOME TO DO LIST 😎</h1> <div> <div className="mt-8 text-sm font-semibold"> If I only had an hour to chop down a tree, I would spend the first 45 minutes sharpening my axe, Abrabam Lincoln </div> <div className="text-xs"> 나무 베는데 한 시간이 주어진다면, 도끼를 가는데 45분을 쓰겠다, 에비브러햄 링컨 </div> <CreateToDo getToDoList={getToDoList} /> <ul className="mt-16 flex flex-col w-1/2"> {toDoList && toDoList.map((v, i) => { return ( <TodoCard key={i} title={v.title} isDone={v.isDone} index={i} getToDoList={getToDoList} /> ); })} </ul> </div> </div> ); } export default App;
먼저 import를 통한 연결.
TodoCard, axios, useEffect, useState, CreateToDo컴포넌트와 라이브러리를 가져오도록 했다.
TodoCard는 todolist를 표시하는 구성요소.
CreateToDo는 todolist에 새로운 항목을 추가하여 사용되는 구성 요소.
axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리인데 여기서 프론트와 백엔드를 연결해주기위해 axios를 사용하였다.
useEffect와 useState는 React의 훅(hook)으로, 각 구성 부품 주기에서 특정 작업을 수행하고 상태(상태)를 관리할 수 있도록 도와준다.
다음으로 app의 구성요소.
useState를 사용하여 toDoList상태를 관리하도록 했다.
getToDoList 함수를 정의하기위해 axios를 사용하여 RESTful API를 호출하였고, 백엔드 서버로부터 투두리스트 데이터를 가져오고, 이를 컴포넌트의 state인 toDoList에 설정했다.
getToDoList 함수는 비동기 함수이므로, async 키워드로 선언된다. 이 함수는 try-catch 블록으로 둘러싸여 있는데 try 블록에서는 axios를 사용하여 get 메서드로 /todo 엔드포인트에 요청을 보내고, 그 결과를 response 변수에 저장한다.
axios 요청에 대한 응답을 response라는 변수에 담았다.
useEffect에서 비동기함수를 사용하기 위해서 "async" 를 밖으로 뺐다.
get 요청에 주소를 담고 주소는 .env를 사용해 만들어주었다.
response 객체의 status를 확인하여 요청이 성공적으로 처리되었는지 확인하는데 if문을 사용해 HTTP 상태 코드가 200인 경우, response 객체의 data를 setToDoList 함수를 사용하여 컴포넌트의 toDoList state에 설정하도록 했다.
요청이 실패한 경우 response 객체의 status 프로퍼티가 200이 아니면, 요청이 실패했음을 알리는 메시지를 띄우고, 함수를 종료한다. 마지막으로, catch 블록에서는 오류를 콘솔에 출력한다.
useEffect Hook을 사용하여, 마운트가 완료된 후 getToDoList 함수를 실행하여 최초 API 호출을 수행하도록 한다.
return 안에
제목으로 들어갈 문구와 명언 그리고 CSS적인 요소를 추가했다.
CreateToDo 컴포넌트를 사용하여 새로운 ToDo 항목을 만들고, ToDo 항목 목록을 표시하는 데 사용되는 ul 요소를 렌더링했다.
ToDo 항목 목록은 toDoList 배열에서 가져오고, TodoCard 컴포넌트를 사용하여 각 ToDo 항목을 표시한다.
각 ToDo 항목은 key, title, isDone, index, getToDoList와 같은 속성을 가지고, TodoCard 컴포넌트는 이러한 속성을 사용하여 ToDo 항목을 표시한다.
<TodoCard.jsx> import axios from "axios"; const TodoCard = ({ title, isDone, index, getToDoList }) => { const onClickToggle = async () => { try { const response = await axios.put( `${process.env.REACT_APP_BACKEND_URL}/todo/done/${index}` ); if (response.status !== 200) { alert("요청을 불러오지 못했습니다."); return; } getToDoList(); } catch (error) { console.error(error); } }; const onClickDelete = async () => { try { const response = await axios.delete( `${process.env.REACT_APP_BACKEND_URL}/todo/${index}` ); if (response.status !== 200) { alert("요청을 불러오지 못했습니다."); return; } getToDoList(); } catch (error) { console.error(error); } }; return ( <> {isDone ? ( <li className="flex my-4" onClick={onClickToggle}> <div className="relative"> <div className="border-4 border-pink-400 w-8 h-8 rounded-xl"></div> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 border-4 border-white bg-pink-400 w-8 h-8 scale-75 rounded-xl"></div> </div> <div className="text-2xl ml-4">{title}</div> <button onClick={onClickDelete}>삭제</button> </li> ) : ( <li className="flex my-4" onClick={onClickToggle}> <div className="border-4 border-pink-400 w-8 h-8 rounded-xl"></div> <div className="text-2xl ml-4">{title}</div> <button onClick={onClickDelete}>삭제</button> </li> )} </> ); }; export default TodoCard;
변경시 호출함수(put)
상단에 컴포넌트는 title, isDone, index, getToDoList라는 props를 받아오고, 이를 사용하여 Todo 리스트의 각 항목을 렌더링한다.
onClickToggle 함수는 async 함수다. axios.put 메서드를 호출하여 서버에 완료 여부를 업데이트하고, getToDoList() 함수를 호출하여 업데이트된 투두리스트를 다시 가져온다.
axios.put 메서드는 첫 번째 매개변수로 API 엔드포인트를 받고, 두 번째 매개변수로 인덱스 값을 전달한다. 이 인덱스 값은 TodoCard 컴포넌트의 index prop으로부터 받았다.
axios.put 메서드가 성공적으로 호출되면, response.status가 200과 같은지 확인한다. 만약 200과 같지 않다면, alert를 호출하여 오류를 알린다.
getToDoList() 함수를 호출하여 업데이트된 투두리스트를 가져오고, 만약 오류가 발생하면 console.error를 사용하여 에러를 콘솔에 출력한다.
삭제버튼 클릭시 호출함수(delete)
axios.delete 메소드를 사용하여 /todo/${index} 경로에 DELETE 요청을 보내게 된다. ${process.env.REACT_APP_BACKEND_URL}은 백엔드 서버의 URL을 가져오는 환경 변수다.
만약 요청이 성공하면 response.status가 200이 되므로, getToDoList() 함수를 호출하여 투두리스트를 다시 불러오도록 하고, 요청이 실패한 경우, 에러 메시지가 콘솔에 출력되도록 한다.
return 안에
isDone에 삼항연산자를 사용하여 isDone이 참일 경우에는 색상이 조정된 버튼과 함께 완료된 todo를 나타내는 요소를 보여주고, 그렇지 않은 경우에는 미완료된 할 일을 나타내는 요소를 보여준다.
버튼과 투두 요소에는 각각 onClickToggle()과 onClickDelete() 함수가 클릭 이벤트로 할당되어 있다. 이 두 함수는 각각 완료/미완료 상태를 토글하거나 할 일을 삭제하는 API 요청을 보내고, 이후에 서버에서 반환된 데이터로 ToDoList 컴포넌트를 다시 렌더링하는 함수인 getToDoList()를 실행한다.
<CreateTodo.jsx> import axios from "axios"; import { useState } from "react"; const CreateToDo = ({ getToDoList }) => { const [title, setTitle] = useState(""); const onSubmitCreateToDo = async (e) => { try { e.preventDefault(); if (!title) { alert("타이틀을 입력해주세요!"); return; } const response = await axios.post( `${process.env.REACT_APP_BACKEND_URL}/todo`, { title, desc: `${title} 블체스 끝까지 해내자`, } ); if (response.status !== 200) { alert("요청을 불러오지 못했습니다."); return; } getToDoList(); setTitle(""); } catch (error) { console.error(error); } }; return ( <form className="flex mt-2" onSubmit={onSubmitCreateToDo}> <input className="grow border-2 border-green-200 rounded-lg focus:outline-yellow-400 px-2 py-1 text-lg" type="text" value={title} onChange={(e) => setTitle(e.target.value)} /> <input className="ml-4 px-2 py-1 bg-green-200 hover:bg-yellow-400 rounded-lg text-gray-50" type="submit" value="새 투두 생성" /> </form> ); }; export default CreateToDo;
import를 통한 연결.
Todo 생성.
useState를 사용하여 title 상태 값을 초기화하고, onSubmitCreateToDo 함수를 선언.
onSubmitCreateToDo 함수는 이벤트를 받아서, 타이틀을 검증하고, axios를 사용하여 새로운 ToDo를 생성. 그리고 생성된 ToDo를 화면에 반영하는 역할을 한다.
만약, 사용자가 입력하지 않은 경우, "타이틀을 입력해주세요!" 라는 경고 메시지가 출력.
axios.post를 사용하여, 새로운 ToDo 생성을 서버에 요청. 생성된 ToDo는 제목과 함께 desc 값에 ${title} 블체스 끝까지 해내자라는 문자열을 설정하여 생성.
응답이 정상적으로 수신되면 getToDoList 함수를 호출하여 ToDo 목록을 갱신하고, setTitle 함수를 사용하여 title 값을 초기화한다. 그리고 예외가 발생한 경우, 콘솔에 에러를 출력한다.
Todo 항목을 생성하는 기능을 구현한 컴포넌트.
useState를 사용하여, 입력한 투두 항목의 title 값을 관리한다. 그리고 초기값은 빈 문자열로 설정.
사용자가 Todo 항목의 title 값을 입력하면, onChange 핸들러를 통해 title state가 업데이트.
사용자가 Todo 생성 버튼을 클릭하면, onSubmit 이벤트 핸들러호출.
이벤트 핸들러에서는 e.preventDefault() 메서드를 호출하여 기본 이벤트를 취소하고, title 값이 빈 문자열인지 검사.
axios.post를 사용하여, 새로운 Todo 항목을 생성. 이때, process.env.REACT_APP_BACKEND_URL 환경 변수를 사용하여 백엔드 서버의 주소를 지정.
새로운 Todo 항목이 성공적으로 생성되면, getToDoList 함수를 호출하여 새로운 Todo 항목을 가져와 화면을 업데이트하고, title state를 빈 문자열로 업데이트하여 입력창을 초기화.
만약, Todo 항목 생성이 실패하면 response.status 값을 검사하여 알림 메시지를 띄우고 처리를 중단.
return 문에서는 입력창과 생성 버튼을 렌더링한다. onSubmit 이벤트 핸들러를 등록하여, 사용자가 입력창에 값을 입력하고 버튼을 클릭하면 onSubmitCreateToDo 함수가 실행되도록 한다.
- Origin
`https://api.openweathermap.org/data/2.5/weather?lat=${_lat}&lon=${_lon}&appid=${process.env.REACT_APP_WEATHER_API}&units=metric`
http 주소는 각각의 구성요소가 하나로 합쳐진 형태.
웹에서는 https://api.openweathermap.org:443 프로토콜과 호스트가 포함된 경로를 같은 출처라고 인식.
- SOP(Same-Origin Policy)
문자 그대로 같은 출처를 가진 곳에서만 리소스를 요청할 수 있는 정책. SOP는 2011년, RFC에서 처음 등장한 보안 정책.
Q. 왜 우리는 같은 출처에서만 리소스를 요청해야만 할까요?
웹 브라우저에는 개발자 도구가 있고 원한다면 프론트 엔드 코드를 완벽하게 카피해낼 수 있다.
이렇게 복사한 가짜 사이트에서 요청을 보낸다면 우리는 구분하지 못하는 상황이 벌어질 수 있다.
SOP 정책을 사용하면 이러한 문제를 해결할 수 있다.
그렇다면 우리는 항상 같은 출처에서만 리소스를 공유할 수 있는 것일까요?
- CORS(Cross-Origin Resource Sharing)
교차 출처(다른 출처)간의 리소스를 공유할 수 있는 정책.
해결방법 (NodeJS 기준)
시간이 없다. 바로 기초강의 영상 보러가야한다 바이.