블록체인 TIL-5Week-31Day

디오·2023년 4월 12일
0

오늘은 어제 진행했던 Todolist-noDB에서 백엔드부분에 약간의 코드를 추가하고, 프론트엔드에 대한 부분을 코드를 짜고 프론트엔드와 백엔드를 연결해주는 과정을 들었다.



✅Todolist-Front.End

<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 항목을 표시한다.

      • react 각각의 component가 무엇인지 구분하기 위해서 key(i)값을 넣어주었다.



<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를 통한 연결.

    • 상단 코드에서는 HTTP 요청을 보내기 위해 axios를 사용하고, 컴포넌트의 상태를 관리하기 위해 useState를 사용.



  • 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 함수가 실행되도록 한다.



☑️CORS

  • 프론트엔드 개발을 하면 한 번쯤은 무조건 만나는 화면인 CORS 정책 위반에 대해서 살펴보도록 한다.
  • Origin
`https://api.openweathermap.org/data/2.5/weather?lat=${_lat}&lon=${_lon}&appid=${process.env.REACT_APP_WEATHER_API}&units=metric`
  • http 주소는 각각의 구성요소가 하나로 합쳐진 형태.

    • https:// - Protocol
    • api.openweathermap.org - Host
    • data/2.5/weather - Path
    • lat={_lat}&lon={_lon}&appid=${process.env.REACT_APP_WEATHER_API}&units=metric - Query String
      (생략되어 있지만 http - 80, https - 443 Port가 포함.)
  • 웹에서는 https://api.openweathermap.org:443 프로토콜과 호스트가 포함된 경로를 같은 출처라고 인식.



  • SOP(Same-Origin Policy)
  • 문자 그대로 같은 출처를 가진 곳에서만 리소스를 요청할 수 있는 정책. SOP는 2011년, RFC에서 처음 등장한 보안 정책.

    • RFC(Request for Comments)문서로, 국제 인터넷 표준화 기구(IETF; Internet Engineering Task Force)에서 관리하는 기술 표준.
  • Q. 왜 우리는 같은 출처에서만 리소스를 요청해야만 할까요?

    • 웹 브라우저에는 개발자 도구가 있고 원한다면 프론트 엔드 코드를 완벽하게 카피해낼 수 있다.

    • 이렇게 복사한 가짜 사이트에서 요청을 보낸다면 우리는 구분하지 못하는 상황이 벌어질 수 있다.

    • SOP 정책을 사용하면 이러한 문제를 해결할 수 있다.

그렇다면 우리는 항상 같은 출처에서만 리소스를 공유할 수 있는 것일까요?



  • CORS(Cross-Origin Resource Sharing)
  • 교차 출처(다른 출처)간의 리소스를 공유할 수 있는 정책.

    • 기본적으로 CORS 정책은 서버 ↔ 서버 간에는 발생하지 않고 서버 ↔ 브라우저 간에 발생하게 됩니다.(Insomnia로 요청을 보냈을 때는 잘 받을 수 있는 이유다.)
    • CORS 정책 위반이 발생하면 브라우저 측에서는 해당 요청을 파기 시킨다.
      (이때 서버 측은 정상적으로 요청을 응답했다고 판단하므로 주의해야한다.)
  • 해결방법 (NodeJS 기준)

    • CORS 라이브러리를 설치 후 Express 미들웨어로 추가한다.
      (이 때의 CORS는 정책의 CORS가 아니라 NodeJS의 패키지 중 하나인 CORS다. - 동일이름)



🌜하루를 마치며..

시간이 없다. 바로 기초강의 영상 보러가야한다 바이.

profile
개발자가 되어가는 개린이"

0개의 댓글