SSR(Server Side Rendering)/Thymeleaf

Thymeleaf(타임리프) 기능(주석, 블록, 자바스크립트 인라인, 템플릿 조각, 템플릿 레이아웃)

녁이 2023. 12. 12. 19:06
728x90
반응형

2023.12.12 - [SSR(Server Side Rendering)/Thymeleaf] - Thymeleaf[타임리프]란? 타임리프의 기본 기능

 

Thymeleaf[타임리프]란? 타임리프의 기본 기능

타임리프(Thymeleaf)란? - 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용되는 템플릿 엔진이다. ( 백엔드 서버에서 동적으로 렌더링 한다는 말은 타임 리프가 서버 사이드 HTM

junhyuk-develop.tistory.com

2023.12.12 - [SSR(Server Side Rendering)/Thymeleaf] - Thymeleaf(타임리프) 기본 기능 및 객체, 문법(리터럴 대체, 반복, 조건)

 

Thymeleaf(타임리프) 기본 기능 및 객체, 문법(리터럴 대체, 반복, 조건)

2023.12.12 - [SSR(Server Side Rendering)/Thymeleaf] - Thymeleaf[타임리프]란? 타임리프의 기본 기능 Thymeleaf[타임리프]란? 타임리프의 기본 기능 타임리프(Thymeleaf)란? - 타임리프는 백엔드 서버에서 HTML을 동적으

junhyuk-develop.tistory.com

위의 게시글들을 순서대로 읽고 해당 게시글을 읽는 것을 추천한다. 더 타임리프에 대해 깊이 있기 이해할 수 있음.

 


주석

  1.  표준 HTML 주석 : 타임리프가 렌더링 하지 않고, 그대로 남겨둔다. →  <!-- aaa -->
  2.  타임리프 파서 주석 : 타임리프의 진짜 주석이다. 렌더링에서 주석 부분을 제거한다.   <!-- /* aaa */ --> 
  3.  타임리프 프로토타입 주석 : HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 렌더링 한 경우에만 보이는 기능   <!-- /*/ aaa /*/ -->
<body>

<h1>예시</h1>
<span th:text="${data}">html data</span>

<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->

<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */-->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->

<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->

</body>

 

 

결과

<h1>예시</h1>
<span>Spring!</span>
<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->
<h1>2. 타임리프 파서 주석</h1>
<h1>3. 타임리프 프로토타입 주석</h1>
<span>Spring!</span>

 

 


블록

<th:block>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그이다.

 

앞선 글에서 우리는 th:each를 사용한 타임리프의 반복문을 공부한 적이 있다.

만일 우리가 반복문을 통해서 user들의 정보를 출력하고 싶은데, 그 정보를 하나씩이 아니라, user마다 userName과 age를 한 블록으로 한꺼번에 반복해서 보여주고 싶다면 어떻게 해야할까?

 

코드로 살펴보자.

<th:block th:each="user : ${users}">
 <div>
 사용자 이름1 <span th:text="${user.username}"></span>
 사용자 나이1 <span th:text="${user.age}"></span>
 </div>
 <div>
 요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
 </div>
</th:block>

 

th:block을 통해 th:each문을 사용했다.

<div>로 사용자 이름,나이를 묶고, 요약을 그 아래 div로 나누었다. 이 div들을 하나의 블록으로 보게 된다.

 

 

실행 결과

<div>
사용자 이름1 <span>userA</span>
사용자 나이1 <span>10</span>
</div>
<div>
요약 <span>userA / 10</span>
</div>
<div>
사용자 이름1 <span>userB</span>
사용자 나이1 <span>20</span>
</div>
<div>
요약 <span>userB / 20</span>
</div>
<div>
사용자 이름1 <span>userC</span>
사용자 나이1 <span>30</span>
</div>
<div>
요약 <span>userC / 30</span>
</div>

 

우리가 원하는 대로, username과 age를 출력하고 그 user에 대한 요약을 하나의 블록으로 반복 출력했다.

 

타임리프의 특성상 HTML 태그안에 속성으로 기능을 정의해서 사용하는데, 위 예처럼 이렇게 사용하기 애매한 경우에 사용하면 된다. <th:block>은 렌더링시 제거된다.

 


자바스크립트 인라인(JavaScript Inline)

타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.

 

자바스크립트 인라인 기능은 다음과 같이 적용하면 된다.

<script th:inline="javascript">

 

 

자바스크립트 인라인 기능의 유무 차이를 코드로 살펴보자.

<body>

<!-- 자바스크립트 인라인 사용 전 -->
<script>
 var username = [[${user.username}]];
 var age = [[${user.age}]];
 
 //자바스크립트 내추럴 템플릿
 var username2 = /*[[${user.username}]]*/ "test username";
 
 //객체
 var user = [[${user}]];
</script>

<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript">
 var username = [[${user.username}]];
 var age = [[${user.age}]];
 
 //자바스크립트 내추럴 템플릿
 var username2 = /*[[${user.username}]]*/ "test username";
 
 //객체
 var user = [[${user}]];
</script>

</body>

HTTP 테그가 아닌 컨텐츠 내에서 타임리프를 적용하기 위해서, [[${...}]] 형태를 사용

 

 

자바스크립트 인라인 사용 전 - 결과

<script>
var username = userA;
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = /*userA*/ "test username";
//객체
var user = BasicController.User(username=userA, age=10);
</script>

 

 

자바스크립트 인라인 사용 후 - 결과

<script>
var username = "userA";
var age = 10;
//자바스크립트 내추럴 템플릿
var username2 = "userA";
//객체
var user = {"username":"userA","age":10};
</script>

 

 

자바스크립트 인라인을 통해 해결되는 문제들

  • 텍스트 렌더링 
    • userA가 변수명으로 사용되어서 자바스크립트 오류가 발생
    • userA 에서 "userA"로 변경해줌으로써 오류 해결
    • 추가로 자바스크립트에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해줌
  • 자바스크립트 내추럴 템플릿
    • 타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공, 자바스크립트 인라인 기능을 통해 이 기능을 활용할 수 있다.
    • var username2 = /*userA*/ "test username"; 에서 var username2 = "userA"; 로 바뀜. → 주석 활용 가능
  • 객체
    • 타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환
    • var user = BasicController.User(username=userA, age=10); 는 객체의 toString()이 호출된 값이다.
    • 자바스크립트 인라인 기능을 통해, var user = {"username":"userA","age":10}; 로 출력됨

 

추가로, 자바 스크립트 인라인은 each문을 지원한다.

[# th:each ~ ] 을 통해 사용한다.

<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
 [# th:each="user, stat : ${users}"]
 var user[[${stat.count}]] = [[${user}]];
 [/]
</script>

 

타임리프의 th:each문과 별다를게 없다.

[/] 를 통해 줄바꿈

 

결과

<script>
var user1 = {"username":"userA","age":10};
var user2 = {"username":"userB","age":20};
var user3 = {"username":"userC","age":30};
</script>

 


템플릿 조각

웹 페이지를 개발할 때는 공통 영역이 많이 있다.

예를 들어서 상단 영역이나 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다.

이런 부분을 코드를 복사해서 사용한다면 변경시 여러 페이지를 다 수정해야 하므로 상당히 비효율적이다.

타임리프는 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.

 

 

footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy">
 푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
 <p>파라미터 자리 입니다.</p>
 <p th:text="${param1}"></p>
 <p th:text="${param2}"></p>
</footer>
</body>
</html>

 

th:fragment 가 있는 태그는 다른곳에 포함되는 코드 조각으로 이해하자.

 

fragmentMain.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
<h1>부분 포함</h1>
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div>
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
</body>
</html>

살펴보면, th:insert, th:replace 등을 활용했다.

표현식은 ~{ 경로 :: 메서드이름} 이다.

 

fragmentMain.html 에서 th:fragment 테그가 있는  footer.html에서 해당 메서드 이름을 가진 부분을 가져온다고 이해하면 된다.

물론, 위의 코드를 보면 알 수 있듯, 파라미터를 사용하여 데이터 출력 부분을 바꿔줄 수 있다.

 

<h1> 파라미터 사용</h1> 부분만 결과를 보자면, 아래와 같다.

<h1>파라미터 사용</h1>
<footer>
<p>파라미터 자리 입니다.</p>
<p>데이터1</p>
<p>데이터2</p>
</footer>

 


템플릿 레이아웃

템플릿 조각은 일부 코드 조각을 가지고와서 사용했다면, 이번에는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법이다.

 

예를 들어서 <head>에 공통으로 사용하는 css , javascript 같은 정보들이 있는데, 이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 된다.

 

 

base.html

<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title,links)">
 <title th:replace="${title}">레이아웃 타이틀</title>
 
 <!-- 공통 -->
 <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
 <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
 <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
 
 <!-- 추가 -->
 <th:block th:replace="${links}" />

</head>

 

css,js 정보를 모아 저장해둔 base.html이다. 여기서 공통 부분은 말 그대로 모든 페이지가 공통적으로 사용하는 부분이다.

추가 부분과 tilte 부분을 페이지마다 다른 것들을 변경, 추가하면 된다.

th:fragmentcommon_header(title, links)를 설정해두었다. 다른 html 파일에서 해당 메서드 이름을 통해 호출하면 되는 것이다.

 

layoutMain.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
 <title>메인 타이틀</title>
 <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
 <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
<body>
메인 컨텐츠
</body>
</html>

 

common_header(~{::title},~{::link}) 부분이 핵심

→ base.html의 common_header를 호출하면서 layoutMain의 title과 links를 넘겨준다.

  • ::title 은 현재 페이지의 title 태그들을 전달한다.
  • ::link 는 현재 페이지의 link 태그들을 전달한다.

 

컨트롤러에서는 /layoutMain 를 보여주도록 코드를 작성했다.

그러나 템플릿 레이아웃을 통해, 결과는 아래의 코드가 웹에 보여지게 된다.

<html>
<head>
<title>메인 타이틀</title>
<!-- 공통 -->
<link rel="stylesheet" type="text/css" media="all" href="/css/awesomeapp.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<script type="text/javascript" src="/sh/scripts/codebase.js"></script>
<!-- 추가 -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">
</head>
<body>
메인 컨텐츠
</body>
</html>

 

결과를 보면 

  • 메인 타이틀이 전달한 부분으로 교체되었다.
  • 공통 부분은 그대로 유지되고, 추가 부분에 전달한 들이 포함된 것을 확인할 수 있다.

레이아웃 개념을 두고, 그 레이아웃에 필요한 코드 조각을 전달해서 완성하는 것으로 이해하면 된다.

 

템플릿 레이아웃 확장

앞서 이야기한 개념을 정도에만 적용하는게 아니라 전체에 적용할 수도 있다.

→ 모든 페이지가 같은 형식의 틀인 경우를 말하는 것이다.

 

 

layoutFile.html 페이지의 틀

<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
 <title th:replace="${title}">레이아웃 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
 <p>레이아웃 컨텐츠</p>
</div>
<footer>
 레이아웃 푸터
</footer>
</body>
</html>

 

layoutFile.html 을 보면 기본 레이아웃을 가지고 있는데, <html>에 th:fragment 속성이 정의되어 있다.

이 레이아웃 파일을 기본으로 하고 여기에 필요한 내용을 전달해서 부분부분 변경하는 것으로 이해하면 된다.

 

 

layoutExtendMain.html → 컨트롤러에서 호출한 View 템플릿

<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title}, ~{::section})}" xmlns:th="http://www.thymeleaf.org">
<head>
 <title>메인 페이지 타이틀</title>
</head>
<body>
<section>
 <p>메인 페이지 컨텐츠</p>
 <div>메인 페이지 포함 내용</div>
</section>
</body>
</html>

 

 

layoutExtendMain.html 는 현재 페이지인데, <html> 자체를 th:replace 를 사용해서 변경하는 것을 확인할 수 있다. 결국 layoutFile.html 에 필요한 내용을 전달하면서 <html> 자체를 layoutFile.html 로 변경한다.

 

 

결과를 확인해보자.

<html>
<head>
<title>메인 페이지 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<section>
<p>메인 페이지 컨텐츠</p>
<div>메인 페이지 포함 내용</div>
</section>
<footer>
레이아웃 푸터
</footer>
</body>
</html>

 

이렇게 템플릿 레이아웃을 사용하면, 같은 틀을 사용하는 웹 서비스의 경우 유지 보수에 더 용이할 수 있다.

만약 이를 사용하지 않을 때, 공통 부분을 수정해야하는 상황이 온다면 모든 페이지를 일일이 수정해야 한다.....

 


정리

지금까지 타임리프가 뭐고, 타임리프가 가지고 있는 특징과 문법, 기능들에 대해서 설명했다.

다음 게시글에서는 스프링과 타임리프를 통합해서 어떻게 또 사용되는지 예시들을 들으며, 더 자세히 설명하겠다.

 

728x90
반응형