[ Service 코드 작성 ]
[ Controller 클래스 생성 ]
@GetMapping(path="/testHome")
public List<PostVO> homepage() {
...
return boardService.findAll();
}
[ React 프론트엔드 화면 생성 ]
npm install react-router-dom
fetch를 사용할 수도 있으나, 사용편의성, 추가기능, 보안 등을 고려하여 axios로 결정. 성능 자체는 fetch가 더 낫기 때문에 대규모 프로젝트에서는 fetch를 사용하는 것이 나을 것으로 생각 됨.
백엔드 컨트롤러에 '/testHome'과 맵핑된 findAll 정의
npm install axios
/* axios 인스턴스 생성용 ApiClient.js */
import axios from 'axios'
export const apiClient = axios.create(
{
baseURL: 'http://localhost:8080',
timeout: 5000,
}
);
/* 이런 방식으로도 사용 가능*/
axios({
method: "get",
url: url,
headers: {},
data: {},
});
/*인스턴스를 얻어 호출 정의 ApiService.js */
import { apiClient } from "./ApiClient"
export const retrieveAllPost = () => apiClient.get(`/testHome`)
const [state, setState] = useState(초기값);
/* 숫자형 */
const [state, setState] = useState(0);
/* 문자형 */
const [state, setState] = useState('');
/* Array */
const [state, setState] = useState([]);
/* object */
const [state, setState] = useState({});
/* boolean */
const [state, setState] = useState(true);
import { useEffect, useState } from "react"
import { retrieveAllPost } from "../api/BoardApiService"
export default HomeBoardComponent
function HomeBoardComponent() {
const [posts, setPosts] = useState([])
const [post, setPost] = useState()
function refreshPost() {
retrieveAllPost()
.then((response) => {
setPosts(response.data)
})
.catch((error) => console.log(error))
}
useEffect( () => refreshPost(), [])
return(
<div>
<table className="table">
<thead>
<tr>
<th>category</th>
<th>title</th>
<th>content</th>
<th>writer</th>
<th>createTime</th>
</tr>
</thead>
<tbody>
{
posts.map(
post => (
<tr key={post.postId}>
<td> {post.category} </td>
<td> {post.title} </td>
<td> {post.content} </td>
<td> {post.userId} </td>
<td> {post.createdTime} </td>
</tr>
)
)
}
</tbody>
</table>
</div>
)
}
useEffect는 컴포넌트가 렌더링된 이후에 어떤 작업을 수행해야하는 지 정의한다. 우리가 실행하고자 하는 함수를 effect라고 하는데, useEffect에 함수를 넘기면 React는 이 함수를 기억했다가 DOM 업데이트가 된 이후 불러 실행한다. 즉, 렌더링 된 이후 useEffect에 등록해둔 effect(부수 효과 함수)가 비동기로 실행된다.
useEffect의 선택적 인수인 두 번째 인수로 특정 조건을 넣으면 조건에 만족하는 값들이 변경되지 않았다면 effect 수행을 건너뛰도록 할 수 있다. 또한, 이 조건을 활용하여 최적화를 할 수도 있다.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.
.map()
map() 함수는 특정 배열을 순회하며 지정된 함수를 값마다 적용하여 각 요소를 변환하고, 그 변환된 값을 모아서 새로운 배열로 반환하는 역할을 수행한다. 보통 함수 지정을 위해 람다함수를 사용한다.
Key 값은 React에게 어떤 항목을 변경, 추가 또는 삭제할지 식별시키기 위해 설정한다. 추후 Update, Delete 등의 작업에 사용된다.
React docs - map()
CORS
현재 프론트엔드 서버의 포트는 3000번이고, 백엔드 서버의 포트는 8080번 이다. 이로 인해 도메인(origin)이 달라 브라우저 단에서 CORS 오류 발생. WebConfig.java 파일을 만들어 CORS 설정을 따로 해주어 해결했다.
/* 글로벌 CORS 설정 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "http://localhost:8080")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(5000);
}
}
[ 에러 해결 ]
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Circular view path [testHome]: would dispatch back to the current handler URL [/testHome] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)] with root cause
jakarta.servlet.ServletException: Circular view path [testHome]: would dispatch back to the current handler URL [/testHome] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
ERROR 25164 코드의 에러가 발생했다. 뭐가 문제일까 보다가 ViewResolver 설정을 확인 해보라는 메시지에 컨트롤러 쪽에 잘못 작성한 코드가 있나 싶었는데 못 찾았다. 구글링 중 비슷한 문제를 해결한 것을 보았는데, 컨트롤러에서 View를 리턴하는게 아니라 Rest 요청에 대한 값을 리턴해주고 있어서 @Controller 어노테이션이 아닌, 뷰가 필요 없는 @RestController 어노테이션을 사용해야 제대로 작동하는 문제였다.
참고