dev-course day22

2rlokr·2025년 4월 2일

dev-course

목록 보기
22/43
post-thumbnail

오늘 배운 것

타임리프

Layout과 Fragments

레이아웃과 프래그먼트는 웹 애플리케이션의 템플릿 구조를 효율적으로 관리하고 일관성을 유지하기 위한 기능이다. 양 기능은 서로 보완적인 역할을 하며, 복잡한 웹 페이지를 설계하고 유지보수하는 데 자주 사용되는 방법이다.

레이아웃(Layout)

레이아웃은 웹 애플리케이션의 공통된 페이지 구조를 정의하고 재사용할 수 있도록 도와주는 기능이다. 이를 통해 여러 페이지에서 일관된 레이아웃을 유지하면서도 페이지별로 고유한 콘텐츠를 쉽게 삽입할 수 있다.

  • 일반적으로 웹 페이지의 공통된 구조(헤더, 푸터, 사이드바와 같은 내용)나 디자인 요소를 포함하며, 이를 통해 페이지 간 일관성을 유지하고 템플릿의 중복을 줄일 수 있다.
  • 레이아웃은 상속과 포함을 활용하여, 공통된 레이아웃을 정의하고, 이를 다양한 템플릿에서 재사용할 수 있게 한다.

프래그먼트(Fragments)

웹 템플릿에서 재사용 가능한 부분적인 HTML 구성 요소를 정의하고 관리하는 강력한 기능이다. 프래그먼트는 웹 페이지의 구조와 디자인 요소를 모듈화하고, 템플릿의 재사용성과 유지보수성을 향상시키는 데 중대한 역할을 한다.

템플릿 내의 th:fragment 속성을 사용하여 정의된다.th:fragment는 HTML 요소에 대해 독립적인 구성 요소를 정의하며, 이를 통해 해당 요소의 특정 부분을 다른 템플릿에서 재사용할 수 있게 한다. 프래그먼트는 일반적으로 공통된 레이아웃 요소나 재사용 가능한 콘텐츠 블록을 정의하는 데 사용됩니다.

실습

th:each

ul 태그에 붙였을 때

<ul th:each="item : ${shoppingList}">
	<li>[[${item}]]</li>
</ul>
  • ul 태그에 th:each를 적용하면 ul 태그부터 반복된다.
<ul>
  <li>양파</li>
</ul>
<ul>
  <li>감자</li>
</ul>
<ul>
  <li>당근</li>
</ul>

li 태그에 붙였을 때

<ul>
  <li th:each="item : ${shoppingList}">[[${item}]]</li>
</ul>
  • li 태그에 th:each를 적용하면 li태그부터 반복된다.
<ul>
  <li>양파</li>
  <li>감자</li>
  <li>당근</li>
</ul>

th:object & th:field

<form method="post" th:object="${post}">
	<input type="hidden" value="user1" name="author">
	<!--<input type="hidden" value="user1" th:field="*{author}">-->
  • 주석으로 처리한 부분은 author 에 value로 입력해둔 user1이 들어가지 않는다.
  • th:fieldvaluename을 자동 설정해주기 때문에 value를 덮어써버린다.

PostMapping

입력한 값을 넘겨 받고 싶을 때는 @PostMapping 을 설정해두어야 한다.

@PostMapping("/4")
public String processSyntaxPage4(Post post) {
	log.info("post.toString() = {}", post.toString());
	return "/syntax/page4";
}
  • 이전에 @GetMapping만 있었을 때는, 제출 버튼을 누르면 에러가 났다. 하지만, 이제는 요청 본문에 post 정보가 들어가서 전달된다.

th:href & th.each

<a th:href="@{~/}">index</a>
  • ~를 넣어주면 컨텍스트 루트(Context Root)를 기준으로 동적으로 경로를 넣어줄 수 있다.
<li th:each="idx : ${#numbers.sequence(1,4)}">
	<a th:href="@{~/page/{idx}(idx=${idx})}">/page/[[${idx}]]</a>
</li>
  • Thymeleaf의 유틸리티 객체인 #numbers를 사용하여 숫자 리스트(시퀀스)를 생성하는 기능을 사용했다.
  • {idx}에 동적으로 값을 넣어주고 그 값은 idx=${idx} 와 같이 th:each로 받아온 idx를 사용한다고 표시해두었다.

th:with

템플릿 내에서 변수를 선언하고 사용할 때 쓰인다. 변수를 한 번 설정하면, 해당 블록 안에서 계속 사용할 수 있다.

<div th:with="name='구구단'">

th:block

타임리프에서 만들어주는 블록이다. html 태그 안에서 사용하기 애매한 경우에 이걸 넣어주면 알아서 바꿔준다.

<th:block th:each="idx : ${#numbers.sequence(1,9)}">
	<p>[[${target}]] x [[${idx}]] = [[${target} * ${idx}]]</p>
</th:block>

HTML 태그를 생성하지 않고, 논리적으로 블록을 그룹핑할 때 사용한다. 주로 조건문, 반복문, 변수 정의 등을 깔끔하게 묶을 때 유용하다.

th:inline

<input type="text" id="username" disabled>
<script th:inline="javascript">
	const username =/*[[${username}]]*/ "hello";
    const usernameEl = document.getElementById('username');
    usernameEl.value = username;
</script>
  • /*[[${username}]]*/ 값이 있으면 주석을 해제하고 username 값을 담고, 없으면 주석이 아예 삭제되어 빈 상태가 된다.
  • 여기서 ${username} 값이 없다면, const username=;이 된다.
  • Thymeleaf는 주석/*...*/을 HTML 렌더링 과정에서만 사용하고, 최종적으로 브라우저에 전달될 때는 제거한다.
    • 값이 있으면 값만 남긴다.
    • 값이 없으면 아무것도 남기지 않는다.
  • getElementByID로 input 태그를 객체로 가지고 있게 된다.
  • usernameElvalue라는 속성을 추가한 것이다.

layout:decorate

<html lang="en"
    layout:decorate="~{layouts/basic_layout}">
  • layout:decorate는 Thymeleaf Layout Dialect라는 추가 라이브러리를 사용할 때 쓰는 문법이다.
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
  • 위와 같은 의존성을 추가해줘야 한다.

th:fragment

common_headers.html

<!--common_headers.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <th:block th:fragment="commonHeader">
        <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
        <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
    </th:block>
</head>
</html>
  • 필요한 부분만 구현하면 된다.
  • header에 들어갈 부분이라, <body>부분을 지워도 상관이 없는 것이다.
  • th:fragment로 지정해둔다.

footer.html

<!--footer.html-->
<footer class="footer footer-horizontal footer-center bg-base-200 text-base-content rounded p-10" th:fragment="footer"> 
  ... 생략
</footer>
  • footer에 필요한 부분이다.
  • th:fragment로 지정해둔다.

top_bar.html

<!--top_bar.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div class="navbar bg-base-200" th:fragment="top_bar">
    <button th:if="${isLogin}" class="btn btn-ghost text-lg">Hello!</button>
    <button th:unless="${isLogin}" class="btn btn-ghost text-lg">No Auth!</button>
    </div>
</body>
</html>
  • 마찬가지로, top-bar에 필요한 부분을 구현하였고, th:fragment로 지정해두었다.

th.insert & th.replace

basic_layout.html

<!DOCTYPE html>
<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<!--Thymeleaf Layout Dialect를 사용하기 위해 필요한 네임스페이스 선언이다.-->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <th:block th:insert="~{fragments/common_headers::commonHeader}"></th:block>
</head>
<body class="min-h-screen flex flex-col">
    <div th:replace="~{fragments/top_bar::top_bar}"></div>
    <div class="container mx-auto mt-4 flex-grow" layout:fragment="contents">

    </div>
    <footer th:replace="~{fragments/footer::footer}"></footer>
</body>
</html>
  • th:insert 는 기존 태그를 유지하되 태그 내부에 내용만 삽입한다.
    • ~{fragments/common_headers::commonHeader} : fragments 폴더에 common_headers 파일의 fragment commonHeader을 추가한다.
  • th:replace 는 기존 태그가 제거되고 태그 자체를 대체한다.

layout_page_2.html

<div layout:fragment="contents">
	<p>레이아웃이 적용되었습니다!</p>
</div>
  • layout:fragment="이름" 을 사용하면 템플릿에서 해당 부분을 동적으로 변경할 수 있다.
  • 부모 레이아웃 파일(basic_layout.html)에서 layout:fragment를 정의하고, 자식 파일(layout_page_2.html)에서 layout:decorate를 사용하여 layout:fragment를 교체하는 방식이다.
  • 레이아웃을 유지하면서, 개별 페이지의 내용만 바꾸는 데 유용하다.
  • 부모의 태그를 사용하고, 본문 내용은 자식 파일의 내용을 사용한다.

오늘 궁금했던 것

<script th:inline="javascript">
const username =/*[[${username}]]*/"hello";

Q. 이 구문을 나는 username이 없을 때 "hello"를 기본값으로 넣어주기 위해 해두는 건 줄 알았다.. 근데, model에 username을 추가해주지 않았을 때 hello가 뜨지 않았고, 아무것도 뜨지 않았다.
왜지..?

A. Thymeleaf는 /*[[]]*/ 이 부분을 값이 있을 땐, 값을 넣어주고, 값이 없을 땐 주석으로 처리하고 아무것도 남기지 않는다. 즉, null이 되는 것이다.

Q. 그렇군.. 그럼 기본값으로의 역할은 하지 않는 거 아닌가? 저 "hello"는 왜 넣어주는 거지...?

A. 아직 잘 모르겠다.. AI에게 물어보니 저 구문은 애초에 기본값을 넣어주기 위한 구문이 아니라고 하던데,,, 너무 헷갈린다. 이건 다시 강사님께 여쭤보는 걸로..

A. 복습하면서 답을 알게됐다 ! 우선 포인트는 /[[]]/ 이걸 넣었을 때 타임리프가 값을 넣어준다. 그리고, 그 뒤의 구문은 "hello" 저걸 아예 빼버리면, IntelliJ에서 빨간 줄이 난다.. 또한, 정적 HTML을 실행시켰을 때 에러가 나버린다. 그렇게 되면 username을 사용하는 그 아래의 코드들은 실행이 되지 않는다. 그런 오류를 방지하기 위해 뒤에 붙여주는 거라고 한다.

팀 활동 후기

오늘은 스크럼 시간에 평소보다 편하게 + 웃으면서 했던 것 같다. 서로 수업 시간 때 혹은 복습하면서 생긴 질문을 나누었다. 또, 팀원들 중 한 명이 피곤할 때만 강사님이 들어오시는데, 오늘 컨디션 좋았다고 하시면서 그런데 오늘 강사님 안 들어올 것 같아요.. 이러셨는데 ㅋㅋㅋㅋ 들어오셨다. 너무 웃겼음. 암튼 화기애애한 으쌰으쌰하는 스크럼 시간이었다 ! :)

느낀점

타임리프를 어제 복습을 잘 해놔서 그런가 실습하면서 잘 따라갈 수 있었다. 그리고, 하시려는 걸 미리 만들어서 비교해볼 수도 있었다 ! 너무 뿌듯하잖아 ~ 근데 마지막 레이아웃, 프레그먼트에서 layout:fragment="contents" 이 부분에서 어디서 어디를 주입해주는 것인가.. 하는 것에서 좀 헷갈렸는데 정리하고 보니, 이해는 된다. 그치만, 바로바로 막 해석되지가 않아서 좀 더 익숙해져야 할 것 같다. 점점 스프링으로 들어오니 너무 재미있다 !!! 흡수해야 할 것은 많아지지만, 더 재밌어지는 것 같다. :) 그리고 오늘은 시간이 정말 빨리 갔다 ! 매일이 이러했으면.. ㅎ

0개의 댓글