Showing

[Springboot | React] 리액트 기초(State, props, stores)와 Spring boot와 통신 본문

JAVA, SPRING/Spring boot, React

[Springboot | React] 리액트 기초(State, props, stores)와 Spring boot와 통신

RabbitCode 2023. 6. 7. 20:19

1. 리액트의 문제해결

(1) 렌더링 기준 : 컴포넌트의 상태(State)

리액트는 컴포넌트 단위로 관리한다. 일반 html은 한 페이지 단위로 새로고침이 발생한다. 즉, 한페이지 단위로 렌더링하는 것이다. 유튜브 같은 경우 영상을 날라 용량이 크고 지속적으로  페이지 자체의 로딩이 느리다. 그런데 어떠한 상태 하나만 바뀌었다고(좋아요만 클릭함) 페이지 자체가 다시 렌더링되는 것은 로드 시간 낭비일 뿐더러 영상이 다시 처음부터 시작하게 된다.

 

반면, 좋아요만 클릭해서 좋아요 상태만 바뀔 수 있다면, 영상이 끊기지 않고 계속 이어질 것이다. 리액트에서는 이러한 '상태'를 관리하게 될 것이다. state와 이를 상속받은 props 같은 아이들이 이에 해당한다.

(2) props 예시

가령 제일 최상단 배너 컴포넌트에서 상태를 하나 만든다. 그리고 이 상태를 밑의 댓글창에서 써야한다고 가정하도록 하자. 그러면 '상속'을 해주어야 한다. props로 상속을 받아와서 결국 댓글창 부분도 state가 될 것이다. 결론적으로 상단 배너가 바뀌면 댓글창도 바뀔 것이다.

 

상태는 변경되면 그 상태에 따라서만 바꾸는 상속의 개념이 들어가는데 이때 발생하는 문제가 있다. 종속 관계중 하나만 바뀌어도 다른 것들까지 다시 렌더링된다는 점이다. 이에 컴포넌트에 종속되어 있지 않고 외부 리액트 서버 자체에 종속되어 있는 스토어라는 개념을 만들 수 있다. 스토어에 상태를 저장하고 이 상태를 쓰고 있는 애들이 변경되면 쓰고 있는 애들만 다시 렌더링해주는 것이다.

 

(3) useEffect

특정한 스테이트, 상태가 바뀌면 useEffect 안에 있는 코드 블럭이 실행된다.

useEffect(()=>{
}, []); //[] 안을 비워놓으면 맨 처음 한번만 실행이 된다.

사용:
useEffect(()=>{
//여기 안에 콜백 함수 적어주고,
}, [//이 배열 안에다 어떠한 상태가 바뀌면 이 useEffect를 돌아라]); // 고 지정

https://youtu.be/spjuvezL7fE

2. 리액트 폴더 구조 소개

apis dir : back end와 연결할 axios 함수들 (백엔드와 통신하기 위해 axios opne library 사용 : http get, post, patch, delete 같은 것들을 axios 함수를 사용해서 통신을 한다.)

assets : 이미지, 비디오, 폰트

componnents : 제일 최소단위 컴포넌트들. 작은 단위. top3 게시물이라든가, 버튼이라든가, 카드, 인풋 등 커스텀한 작은 녀석들

constants : 상수들
interfaces : 타입스크립트에만 존재, Type으로 사용할 인스턴스들을 저장해둠

stores : 스토어 함수들 저장

utils : 각종 메서드들 저장

view : 큰 단위. 큰 페이지 혹은 레이아웃 단위의 컴포넌트들. 가령 로그인 페이지, 메인페이지, 게시물 리스트페이지, 마이페이지 이런 것들...  저장

 

view와 components는 둘다 컴포넌트를 저장

 

3. Axios로 Spring boot와 통신

npm i axios

4. Spring boot : Controller 작성

아래 코드는 로컬 호스트에 지정한 포트번호로 들어갔을 때(@RequestMapping("/")) 그 중에 리퀘스트 url 패턴이 그냥 지정한 포트번호이고 뒤에 아무 것도 존재하지 않을 적에(@GetMapping("")) 겟 메소드로 왔을 때를 처리해준다.

package com.example.board.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class MainController {
    @GetMapping("")
    public String hello(){
        return "Connection Successful";
    }
}

@RestController : 해당 클래스를 Controller 레이어로 인식하도록 한다. Rest한 형태로 컨트롤러를 만들건데, 

 

api만 태워서 데이터를 넘겨줄 때, 일반적으로 @Controller에서는 @ResponseBody를 추가해주어서, Body에 담아서 넘겨준다고 지정을 해주었다.

@RestController는 위의 과정을 합친 형태이다.

@RestController = @Controller + @ResponseBody : 하나로 합쳐져서 데이터를 직접 보내는 형태로 Controller를 만들겠다!

 

@RequestMapping(URL 패턴) : Request의 URL의 패턴을 보고 해당하는 패턴이 왔을 때, 해당하는 패턴이 있는 해당 클래스 실행

@GetMapping("") :  GetMapping은 request 메소드, get 메소드, patch, delete 등과 같은 애들과 매핑되면서 end 포인트가 뭔지를 알아냄. 여기서는 엔드포인트가 딱히 없다.

 

5. 프론트, 백엔드 연결

 

리액트(프론트)단은 데이터를 직접보내는 형태로 컨트롤러를 만든 것을 호출하는 것을 목표로 함

리액트에서 GetMapping("") 된 녀석을 부르기 위해 리액트에서 request로 보낼 호스트 포트와 url을 보내주면 된다. axios로 get을 쓸 때가 왔다.

우선 App.tsx를 정리해준다.

import React, { useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios';

function App() {
  const [connection, setConnection] = useState<string>(''); //('') 초기화값
  // 위와 같이 스테이트를 선언할 때는 처음부터 사용할 상태, 스테이트에 대한 상수를 선언해주고
  // 그 상수를 변경시켜주는 set스테이트명으로 바꿔주어야 한다.
  const connectionTest = ()=>{
    //axios.get('http://localhost:8079/'); //8079 포트이면서 엔드포인트는 지정하지 않은 상태로 보내자
    axios.get('http://localhost:8079/').then((res)=>{
      //받은 res 바디 데이터를 받은 res 데이터로 connection state를 변경시켜줄 것이다.
      setConnection(res.data);
    }).catch((error)=>{
      setConnection(error.message);
    })
  }
  useEffect(()=>{
    connectionTest();
  });
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{connection}</p>
      </header>
    </div>
  );
}

export default App;

화면에 뿌려지는 (상태변화 캐치용)변수를 지정할 때 state를 사용해야 한다.

 

then을 연결하여 결과가 성공적이면 response를 받아준다. 

6. 왜 안될까?

Access to XMLHttpRequest at 'http://localhost:8079/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

 

CORS 정책에 의해서 origin 3000번이 로컬호스트 8079번으로 들어가는 것이 막힌 것이다. 같은 호스트 내에서 다른 포트로 들어오는 것을 막은 것이다.

CORS (Cross-Origin Resource Sharing)는 웹 브라우저에서 사용되는 보안 메커니즘으로, 교차 출처 HTTP 요청을 제한한다.  "http://localhost:3000" 출처에서 "http://localhost:8079/"로의 요청이 CORS 정책에 의해 차단되었음을 나타냅니다.

이 문제를 해결하기 위해서는 "http://localhost:8079/"에서 실행 중인 서버에서 응답에 적절한 CORS 헤더를 포함하도록 구성해야 합니다. 구체적으로, 서버는 "Access-Control-Allow-Origin" 헤더를 포함하고 값을 "http://localhost:3000"으로 설정하여 해당 출처로부터의 요청을 허용해야 합니다.

CORS 헤더를 구성하는 방법은 사용 중인 서버 사이드 기술에 따라 다를 수 있습니다. 그러나 대부분의 경우, 서버의 응답에서 필요한 헤더를 설정하여 교차 출처 요청을 허용할 수 있습니다. 서버 프레임워크 또는 프로그래밍 언어에 대한 문서를 참조하거나 CORS 헤더를 활성화하는 방법에 대해 검색하여 자세한 정보를 얻을 수 있습니다.

서버를 구성하여 "Access-Control-Allow-Origin" 헤더를 포함하도록 설정하면 오류가 발생하지 않고 "http://localhost:3000"에서 "http://localhost:8079/"로의 요청이 허용될 것입니다.

 

이러한 막힘스프링에서 풀어주어야 한다.

 

7. 다시 스프링부트로

@CrossOrigin(URL패턴)

@CrossOrigin(URL패턴) : 어떠한 url 패턴에 대해서 허용을 하겠다고 할 수 있음

package com.example.board.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(originPatterns = "http://localhost:3000") //로컬 호스트 3000번에 대해서 허용을 하겠다.
@RestController
@RequestMapping("/")
public class MainController {
    @GetMapping("")
    public String hello(){
        return "Connection Successful";
    }
}

다시 스프링부트를 재부팅해본다.

됐 다 ! ! !

완벽하게 프론트와 백엔드가 연결된 것을 확인할 수 있다.