티스토리 뷰

728x90

화면 그리기

리액트에서 cookie 사용
  • yarn i react-cookie
리액트에서 부트스트랩 사용
  • yarn install react-bootstrap bootstrap
화면 그리기
  • 3 일차 첫 번째 시간
  • App.js
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import Board from "./Board";
import { instanceOf } from "prop-types";
import { withCookies, Cookies } from "react-cookie";

class App extends Component {
  static propTypes = {
    cookies: instanceOf(Cookies).isRequired,
  };

  static defaultProps = {
    menu1: true,
    menu2: false,
    boardType: "board",
  };

  constructor(props) {
    super(props);
    const { cookies } = this.props;

    console.log(
      "App constructor:" + cookies.get("menu1") + ", " + cookies.get("menu2")
    );

    this.state = {
      menu1:
        cookies.get("menu1") !== "" ? cookies.get("menu1") : this.props.menu1,
      menu2:
        cookies.get("menu2") !== "" ? cookies.get("menu2") : this.props.menu2,
      boardType:
        cookies.get("boardType") !== ""
          ? cookies.get("boardType")
          : this.props.boardType,
    };
    this.handleCreate = this.handleCreate.bind(this);
  }

  handleCreate(data) {
    console.log("App handleCreate:" + data.boardType);
    const { cookies } = this.props;

    cookies.set("menu1", data.menu1, { path: "/" });
    cookies.set("menu2", data.menu2, { path: "/" });
    cookies.set("boardType", data.boardType, { path: "/" });

    /* this.setState({
      menu1: data.menu1,
      menu2: data.menu2,
      boardType: data.boardType
    }); */
  }

  render() {
    return (
      <div>
        <Board
          key={this.state.boardType}
          boardType={this.state.boardType}
          menu1={this.state.menu1}
          menu2={this.state.menu2}
          onCreate={this.handleCreate}
        />
      </div>
    );
  }
}

export default withCookies(App);
  • index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "bootstrap/dist/css/bootstrap.min.css";
import { CookiesProvider } from "react-cookie";

ReactDOM.render(
  <React.StrictMode>
    <CookiesProvider>
      <App />
    </CookiesProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
  • board.js
import React, { useState, Component, Fragment } from "react";

class Board extends Component {
  constructor(props) {
    super(props);
    this.state = {
      menu1: this.props.menu1,
      menu2: this.props.menu2,
      boardType: this.props.boardType,
      boardData: {
        id: "",
        title: "",
        contents: "",
        fname: "",
      },
    };
  }

  componentDidMount() {
    console.log("-------------------------- : " + this.props.boardType);
  }

  componentWillUnmount() {
    console.log("----------------Board Unmount");
  }

  render() {
    //console.log('===Board render:'+this.props.boardType);
    //console.log("menu1:"+this.state.menu1);
    //console.log("menu2:"+this.state.menu2);
    return (
      <div className="card">
        <h1>react board</h1>
        <div className="card-header">
          <ul className="nav nav-tabs">
            <li className="nav-item">
              <a
                className={"nav-link " + (this.state.menu1 ? "active" : "")}
                onClick={this.menuClick}
              >
                싱글이미지 게시판
              </a>
            </li>
            <li className="nav-item">
              <a
                className={"nav-link " + (this.state.menu2 ? "active" : "")}
                onClick={this.menuClick2}
              >
                멀티이미지 게시판
              </a>
            </li>
          </ul>
        </div>
        <div className="card-body">
          <div className="row">
            <div className="col-lg-4">
              <List
                key={this.state.boardType}
                boardType={this.state.boardType}
                onCreate={this.handleCreate}
              />
            </div>
            <div className="col-lg-5">
              <Detail
                key={this.state.boardType + this.state.boardData.id}
                id={this.state.boardData.id}
                title={this.state.boardData.title}
                contents={this.state.boardData.contents}
                date={this.state.boardData.date}
                fname={this.state.boardData.fname}
                boardType={this.state.boardType}
                onCreate={this.handleCreate2}
              />
            </div>
            <div className="col-lg-3">
              <Image
                key={
                  this.state.boardType +
                  this.state.boardData.id +
                  this.state.boardData.fname
                }
                boardType={this.state.boardType}
                fname={this.state.boardData.fname}
              />
            </div>
          </div>
        </div>
        <div className="card-footer">
          SpringBoot + MySQL + <strong>React</strong> + bootstrap4 게시판 만들기
        </div>
      </div>
    );
  }
}

class List extends Component {
  render() {
    const divStyle = {
      minHeight: "500px",
      maxHeight: "500px",
      overflowY: "auto",
    };

    return (
      <div className="card" style={divStyle}>
        <table className="table">
          <thead>
            <tr>
              <th>게시물 리스트</th>
            </tr>
          </thead>
          <tbody>
            {/* <tr>
                        <td>게시물 1</td>
                    </tr>
                    <tr>
                        <td>게시물 2</td>
                    </tr> */}
            {/* {this.state.boardList} */}
          </tbody>
        </table>
      </div>
    );
  }
}

class Detail extends Component {
  render() {
    const divStyle = {
      minHeight: "500px",
      maxHeight: "1000px",
    };
    const divCenter = {
      textAlign: "center",
    };

    return (
      <div className="card bg-light text-dark" style={divStyle}>
        <form name="form1" action="">
          <div className="form-group">
            <label className="control-label">제목:</label>
            <div>
              <input
                type="text"
                className="form-control"
                placeholder="제목을 입력하세요"
              />
            </div>
          </div>
          <div className="form-group">
            <label className="control-label">내용:</label>
            <div>
              <textarea className="form-control" rows="10"></textarea>
            </div>
          </div>
          <div className="form-group">
            <label className="control-label">이미지첨부: jpg,gif,png</label>
            <div>
              <input type="file" className="form-control" name="file" />
            </div>
          </div>
          <input type="hidden" name="id" />
        </form>
        <div style={divCenter}>
          <div className="btn-group">
            <button type="button" className="btn btn-primary">
              저장
            </button>
            <button type="button" className="btn btn-secondary">
              취소
            </button>
            <button type="button" className="btn btn-danger">
              삭제
            </button>
            <button type="button" className="btn btn-info">
              그림삭제
            </button>
          </div>
        </div>
      </div>
    );
  }
}

class Image extends Component {
  render() {
    const divStyle1 = {
      minHeight: "500px",
      maxHeight: "500px",
      overflowY: "auto",
      display: "block",
    };
    const divStyle1_1 = {
      minHeight: "500px",
      maxHeight: "1000px",
      display: "none",
    };
    const divStyle2 = {
      minHeight: "500px",
      maxHeight: "500px",
      overflowY: "auto",
      display: "block",
    };
    const divStyle2_1 = {
      minHeight: "500px",
      maxHeight: "1000px",
      display: "none",
    };
    const imgStyle = {
      width: "100%",
    };

    return (
      <Fragment>
        <div className="card bg-light text-dark">
          <img alt="image"></img>
        </div>
        <div className="card bg-light text-dark">
          <span></span>
        </div>
      </Fragment>
    );
  }
}

export default Board;

리액트로 하다 올해 못할 것 같아서 thymeleaf 환경으로 변경..

  • https://start.spring.io/
  • 프로젝트 생성
    • 의존성 추가
    • Spring Boot DevTools
    • Lombok
    • Spring Web
    • Thymeleaf
    • Spring Data JPA
    • MySQL Driver
  • mysql 연결을 위해 application.properties 입력
#디비 연결
spring.datasource.url=jdbc:mysql://localhost:3307/testproject?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# mysql 사용
spring.jpa.database=mysql
# 콘솔에 sql 출력 여부
spring.jpa.show-sql=true  

편익성을 높이는 몇 가지 설정

  • 자동 리로딩 설정 ( 자동 재시작 )
    • 웹 개발 시 코드를 수정하고 다시 deploy를 하는 과정 자동으로 설정
    • EditConfigurations -> Build and run -> Modify options 선택
    • On update action, On freme deactivation의 옵션 값을 Update classeds and resources로 지정
    • 설정이 추가된 후 수정하고 다른 작업을 하면 자동으로 빌드와 배포가 이루어짐

로그 레벨의 설정

  • 스프링 부트는 기본적으로 Log4j2가 추가
# 로그 레벨ㅇ 설정
logging.level.com.board.springframwork=info
logging.level.com.board=debug

인텔리제이의 DataSource 설정

  • 인텔리제이 얼티메이트는 JPA 관련 플러그인이 이미 설치 되어 있기 때문에 Data-Source를 설정해두면 나중에 엔티티 클래스의 생성이나 기타 클래스의 생성과 설정 시에 도움

테스트 환경과 의존성 주입 테스트

  • 스프링에는 test 라이브러리를 추가해야 하고 JUnit 등도 직접 추가 해야 하지만 부트는 프로젝트 생성할 때 이미 테스트 관련 설정이 완료되고 테스트 코드도 하나 생성되어 있음
  • 테스트 코드의 실행을 점검하기 위해 Data-SourceTests를 작성해서 HikaraCP의 테스트와 Lombok을 확인
// 테스트 폴더에서 lombok 하고 Log4j2 사용하게 하기 위한
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
  • 해당 코드를 build.gradle 에 추가해줘야 한다.

Spring Data JPA를 위한 설정

  • Spring Data JPA를 이용할 때 필요한 설정
  • application.properties에 작성
spring.jpa.hibernate.ddl-auto=create
# none - DDL을 하지 않음
# create-drop - 실행할 때 DDL을 실행하고 종료 시에 만들어진 테이블 등을 모두 삭제
# create - 실행할 때 마다 새롭게 테이블을 생성
# update - 기존과 다르게 변경된 부분이 있을 때는 새로 생성
# validate - 변경된 부분만 알려주고 종료


# JPA가 실행하는 SQL 을 같이 출력 
spring.jpa.show-sql=true

#실제로 실행되는 SQL 을 포맷팅해서 알아보기 쉽게 출력
spring.jpa.properties.hibernate.format_sql=true

컨트롤러와 Thymeleaf 만들기

  • controller 패키지 생성 -> SampleController 클래스 생성
@Controller
@Log4j2
public class SampleController {

    @GetMapping("/hello")
    public void hello(Model model){
        log.info("hello---------------------");

        model.addAttribute("msg","Hello World");
    }

}
  • 화면은 Thymeleaf를 이용하는데 위치를 주의해서 작성해야 한다,
  • 프로젝트 생성 시 만들어져 있는 templates 폴더에 hello.html을 작성
  • hello.html에서 가장 중요한 부분은 Thymeleaf의 네임스페이스를 추가하는 것
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

  <h1 th:text="${msg}"></h1>

</body>
</html>

JSON 데이터 만들기

  • 스프링 부트를 이용해 웹프로젝트를 구성할 때는 API 서버라는 것을 구성하는 경우도 많다
  • API 서버는 순수한 데이터만 전송하는 방식
@org.springframework.web.bind.annotation.RestController
@Log4j2
public class RestController {

    @GetMapping("/helloArr")
    public String[] helloArr(){
        log.info("helloArr-----------");

        return new String[]{"AAA", "BBB", "CDCDD"};
    }
}
  • 가장 많이 사용하는 방식 위 두가지

Thymeleaf

  • 반복문
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="str: ${list}" th:text="${str}"></li>
    </ul>

    <ul>
        <th:block th:each="str: ${list}">
            <li>[[${str}]]</li>
        </th:block>
    </ul>

</body>
</html>

반복문의 status 변수

  • Thymeleaf는 th:each를 처리할 때 현재 반복문의 내부 상태에 변수를 추가해서 사용할 수 있다
  • 일명 status 변수라 하는데 index/count/size/first/last/odd/even 등을 이용해 자주 사용하는 값 출력 가능
    <ul>
        <li th:each="str, status: ${list}">
            [[${status.index}]] -- [[${str}]]
        </li>
    </ul>
  • status 변수명은 사용자가 지정가능하고 inex는 0번부터 시작 하는 번호를 의미
  • count 는 1부터 시작

th:if / th:unless / th:switch

  • Thymeleaf는 제어문의 형태로 th:if / th:unless / th:switch를 이용할 수 있다
  • th:if와 th:unless는 사실상 별도의 속성으로 사용 할 수 있으므로 if ~ else와는 조금 다르게 사용 된다
  • 반복문의 홀짝을 구분해서 처리하고 싶다면?

// 홀짝 출력 
  <ul>
        <li th:each="str, status: ${list}">
            <span th:if="${status.odd}"> ODD -- [[${str}]]</span>
            <span th:unless="${status.odd}"> EVEN -- [[${str}]]</span>
        </li>
    </ul>

<!--    삼항 처리로  홀수만 출력하게  -->
    <ul>
        <li th:each="str, status: ${list}">
            <span th:text="${status.odd} ? 'ODD ---' + ${str}"></span>
        </li>
    </ul>

인라인 처리

728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함