🔽이전 글
[Spring Boot] React연동 프로젝트
프로젝트 실행하기 위해 Spring Boot 웹서버, React 프론트 웹 서버도 함께 실행시킨다. 1. 리액트 프로젝트 생성- /spring03/frontboard 폴더 생성cd spring03npx create-react-app frontboard 2. 리액트 라이브러
iieunji023.tistory.com
💥💥💥
Spring Boot 서버가 실행되지 않았을 때 프론트 서버부터 시작하면 Uncaught runtime error가 발생한다.
axios request가 예외발생시키기 때문에 try-catch로 wrapping 해줘야 한다.
BoardList.js 예외처리 로직 추가
const getBoardList = async () => {
var pageString = 'page=0';
try{ // 백엔드 서버가 실행되지 않으면 예외발생. AXIOS ERROR
const resp = (await axios.get("//localhost:8080/api/board/list/free?" + pageString));
if(resp.status === 200) {
setBoardList(resp.data); // boardList에 데이터가 들어감
console.log(resp.data);
} else if(resp.status === 404) {
alert("서버 페이지가 없습니다.");
} else if(resp.status === 500) {
alert("서버 오류입니다.");
}
}catch(error) {
console.log(">>>>> " + error);
alert("서버가 연결되지 않았습니다.");
}
}
- 콘솔창에 axios error가 뜨는 것을 확인할 수 있음
그럼 이제 페이징 기능을 구현해보겠다!
(Backend) /dto/PagingDto.java 생성
package com.eunji.backboard.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PagingDto {
private int pageSize; // 화면당 보여지는 게시글 최대개수
private int totalPageNum; // 총 페이지 수
private long totalListSize; // 총 게시글 수
private int page; // 현재 페이지
private int startPage; // 시작 페이지 번호
private int endPage; // 마지막 페이지 번호
private int startIndex; // 시작 인덱스 번호
private int block; // 현재 블럭(구간)
private int totalBlockNum; // 총 블럭 수
private int prevBlock; // 이전 블럭
private int nextBlock; // 다음 블럭
/*
* 전체 리스트 크기와 현재 페이지와 페이지마다 나타낼 글 개수, 블럭 수를 가지고
* 필요 변수들의 값을 계산하는 생성자
*/
public PagingDto(Long totalListSize, Integer page, Integer pageSize, Integer blockSize ) {
this.pageSize = pageSize;
this.page = page;
this.totalListSize = totalListSize;
// 변수 값 계산
// 전체 블럭 수 계산
this.totalPageNum = (int) Math.ceil(this.totalPageNum * 1.0 / this.pageSize);
// 현재 블럭 계산
this.block = (int) Math.ceil((this.page) * 1.0 / blockSize);
// 한 블럭 시작페이지
this.startPage = ((this.block - 1) * blockSize + 1);
// 현재 블럭 마지막 페이지
this.endPage = this.startPage + blockSize - 1;
// 블럭 마지막 페이지 검증(한 블랙이 10페이지가 안넘으면 마지막 페이지를 최대 페이지 수로 다시 변경 10 -> 3)
if(this.endPage > this.totalPageNum) this.endPage = this.totalBlockNum;
// 이전 블럭(클릭 시, 이전 블럭 마지막 페이지)
this.prevBlock = (this.block * blockSize) - blockSize;
// 이전 블럭 검증
if(this.prevBlock < 1) this.prevBlock = 1; // 1페이지보다 작을 순 없음
// 다음 블럭
this.nextBlock = (this.block * blockSize + 1);
// 다음 블럭 검증
if(this.nextBlock > this.totalPageNum) this.nextBlock = this.totalPageNum; // 전체 페이지 수보다 블럭 수가 클 순 없음
// 시작 인덱스 번호
this.startIndex = (this.page - 1) * this.pageSize;
}
}
(Backend) /dto/Header.java 생성
package com.eunji.backboard.dto;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Header<T> {
private LocalDateTime transactionTime; // json으로 전달한 시간(스프링부트 -> 리액트)
private String resultCode; // 트랜잭션이 성공인지 실패인지 알려줌
private String description;
private T data; // 실제 게시글 데이터 담는 곳
private PagingDto paging;
@SuppressWarnings("unchecked")
public static <T> Header<T> OK() {
return (Header<T>) Header.builder()
.transactionTime(LocalDateTime.now())
.resultCode("OK")
.description("NO ERROR")
.build();
}
@SuppressWarnings("unchecked")
public static <T> Header<T> OK(T data) {
return (Header<T>) Header.builder()
.transactionTime(LocalDateTime.now())
.resultCode("OK")
.description("NO ERROR")
.data(data)
.build();
}
@SuppressWarnings("unchecked")
public static <T> Header<T> OK(T data, PagingDto paging) {
return (Header<T>) Header.builder()
.transactionTime(LocalDateTime.now())
.resultCode("OK")
.description("NO ERROR")
.data(data)
.paging(paging)
.build();
}
@SuppressWarnings("unchecked")
public static <T> Header<T> OK(String description) {
return (Header<T>) Header.builder()
.transactionTime(LocalDateTime.now())
.resultCode("ERROR")
.description(description)
.build();
}
}
RestBoardController.java list() 수정
List<BoardDto> -> Header<List<BoardDto>>로 형 변환
- RestBoardController.java 변경된 코드
public Header<List<BoardDto>> list(@PathVariable(value="category") String category,
@RequestParam(value="page", defaultValue = "0") int page,
@RequestParam(value = "kw", defaultValue = "") String keyword) {
PagingDto paging = new PagingDto(pages.getTotalElements(), pages.getNumber() + 1, 10, 10);
// Header<>에 담아줌
Header<List<BoardDto>> last = Header.OK(result, paging);
return last;
}
(Front) BoardList.js 변수 부분 수정
- BoardList.js
const getBoardList = async () => {
var pageString = 'page=0';
try{ // 백엔드 서버가 실행되지 않으면 예외발생. AXIOS ERROR
const resp = (await axios.get("//localhost:8080/api/board/list/free?" + pageString));
const resultCode = resp.data.resultCode; // header가 잘못된 경우 진행되면 안됨
console.log(resultCode);
setBoardList(resp.data.data); // boardList 변수에 담는 작업
const paging = resp.data.paging;
console.log(resp.data.data);
console.log(paging);
}catch(error) {
console.log(">>>>> " + error);
alert("서버가 연결되지 않았습니다.");
}
}
- resultCode = OK
- resp.data.data = 게시글 Array()
- paging = 페이지 object
- BoardList.js
import axios from 'axios'; // REST API 호출 핵심!!
// Hook함수 사용
import React, { useState, useEffect } from 'react';
// Navigation
import { Link } from 'react-router-dom';
function BoardList() { // 객체를 만드는 함수
// 변수 선언
// return{} | render() html, react 태그에서 반복할 때 사용됨
const [boardList, setBoardList] = useState([]); // 배열값을 받아서 상태를 저장하기 때문에 []
const [pageList, setPageList] = useState([]); // 페이징을 위한 배열데이터
const [nextBlock, setNextBlock] = useState(0); // 다음 블럭 값
const [prevBlock, setPrevBlock] = useState(0); // 이전 블럭 값
const [lastPage, setLastPage] = useState(0); // 마지막 페이지 번호
// 함수선언
// 제일 중요!!
const getBoardList = async (page) => {
var pageString = (page == null) ? 'page=0' : 'page=' + page;
try{ // 백엔드 서버가 실행되지 않으면 예외발생. AXIOS ERROR
const resp = (await axios.get("//localhost:8080/api/board/list/free?" + pageString));
const resultCode = resp.data.resultCode; // header가 잘못된 경우 진행되면 안됨
// console.log(resultCode); // OK or ERROR
if(resultCode == 'OK') {
setBoardList(resp.data.data); // boardList 변수에 담는 작업
const paging = resp.data.paging;
// console.log(resp.data.data);
console.log(paging); // 개발이 완료되면 콘솔로그는 주석처리할 것
const { endPage, nextBlock, page, prevBlock, startPage, totalListSize, totalPageNum } = paging;
console.log(totalListSize);
console.log(totalPageNum);
const tmpPages = [];
for (let i = startPage; i <= endPage; i++) {
tmpPages.push(i); // [1, 2, 3, 4, ...]
}
setPageList(tmpPages);
setNextBlock(nextBlock);
setPrevBlock(prevBlock);
setLastPage(totalPageNum);
} else {
alert("문제가 발생하였습니다");
}
}catch(error) {
console.log(">>>>> " + error);
alert("서버가 연결되지 않았습니다.");
}
}
// onPageClick() - 페이지 번호 클릭시
function onPageClick(page){
console.log(page);
getBoardList(page - 1); // Spring Boot에서는 0부터 시작했기 때문에 -1을 해줘야 함
}
useEffect(() => {
getBoardList();
}, []); // 값이 없을때는 빈 화면
return (
<div className='container'>
<table className='table'>
<thead className='table-dark'>
<tr className='text-center'>
<th>번호</th>
<th style={{width: '50%'}}>제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
{/* 반복으로 들어갈 부분 */}
{boardList.map((board) => (
<tr className='text-center' key={board.bno}>
<td>{board.bno}</td>
<td className='text-start'>{board.title}</td>
<td>{board.writer}</td>
<td>{board.hit}</td>
<td>{board.createDate}</td>
</tr>
))}
</tbody>
</table>
{/* 페이징 처리 */}
<div className='d-flex justify-content-center'>
<nav aria-label='Page navigation'>
<ul className='pagination'>
<li>
<button className='page-link' aria-label='first' onClick={() => onPageClick(1)}>
<span>«</span>
</button>
</li>
<li className='page-item'>
<button className='page-link' aria-label='previous' onClick={() => onPageClick(prevBlock)}>
<span><</span>
</button>
</li>
{pageList.map((page, index) => (
<li className='page-item' key={index}>
<button className='page-link' onClick={() => onPageClick(page)}>
{page}
</button>
</li>
))}
<li className='page-item'>
<button className='page-link' aria-label='next' onClick={() => onPageClick(nextBlock)}>
<span>></span>
</button>
</li>
<li>
<button className='page-link' aria-label='first' onClick={() => onPageClick(lastPage)}>
<span>»</span>
</button>
</li>
</ul>
</nav>
</div>
</div>
);
}
export default BoardList;
작성일 출력 변경
(front) /common/CommonFunc.js 생성
- 작성일 수정하기 위한 함수 formatDate() 작성
- 작성일이 초 단위까지 나오는 문제(?) 발생
- 따라서 CommonFunc.js에서 작성일을 변경
- CommonFunc.js
export function formatDate(date) { // 2024-07-01T09:27:11.269634
var result = date.replace('T', ' '); // T를 공백으로 변경
var index = result.lastIndexOf(' '); // 초 앞에 있는 : 위치 값, ' '은 yyyy-mm-dd만 남김
result = result.substr(0, index); // 초 뒤로 삭제
return result;
}
- 년월일만 나오도록 수정
- BoardList.js에서 작성일 출력 코드 수정
<td>{common.formatDate(board.createDate)}</td>
- CommonFunc.js에 작성한 formarDate() 메서드를 통해 변경해줌
/BoardList.js 댓글 갯수 표시
<tbody>
{/* 반복으로 들어갈 부분 */}
{boardList.map((board) => (
<tr className='text-center' key={board.bno}>
<td>{board.bno}</td>
<td className='text-start'>{board.title}
{
board.replyList != null &&
<span class="badge text-bg-warning">{board.replyList.length}</span>
}
</td>
<td>{board.writer}</td>
<td>{board.hit}</td>
<td>{common.formatDate(board.createDate)}</td>
</tr>
))}
</tbody>
게시글 번호 변경
(Backend) /dto/BoardDto.java 게시글 번호 변수 추가
private Long num;
/restcontroller/RestBoardController.java 게시글 번호 계산 로직 추가
long curNum = pages.getTotalElements() - pages.getNumber() * 10; // 게시글 번호
for (Board origin : pages) {
List<ReplyDto> subList = new ArrayList<>();
BoardDto bdDto = new BoardDto();
// 게시글 번호 추가
bdDto.setNum(curNum--); // 한번 돌때마다 1씩 빼줌
....
}
- 이제 프론트에서도 출력값을 bno가 아닌 num으로 바꿔줘야 함
/BoardList.js bno를 num으로 변경
<tr className='text-center' key={board.bno}>
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] React연동 프로젝트(4) - 상세 화면 (0) | 2024.07.04 |
---|---|
[Spring Boot] React연동 프로젝트(3) - 회원가입 및 로그인 기능 구현 (1) | 2024.07.04 |
[Spring Boot] React연동 프로젝트(1) - 프로젝트 생성 및 리스트 출력 (1) | 2024.07.02 |
[Spring Boot] JPA 프로젝트 - 비밀번호 찾기 및 변경 기능 구현(2) (0) | 2024.06.28 |
[Spring Boot] JPA 프로젝트 - 비밀번호 찾기 및 변경 기능 구현(1) (0) | 2024.06.27 |