[Thymeleaf] 타임리프, 기본적인 기능 정리
- [ Backend ]/기타
- 2022. 2. 20.
https://www.thymeleaf.org/
기본 기능: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
공식 메뉴얼 - 스프링 통합: https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
- 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용하는 템플릿 엔진이다.
다른 대표적인 SSR(서버사이드 렌더링) 언어인 jsp보다 더 스프링 친화적이기도 하고 jsp와 달리 순수 HTML형태를 유지하기 때문에 서버를 거치지 않고도 동적으로 렌더링된 결과를 확인하기에 좋다.
- 기본적으로 타임리프는 기존 HTML태그에 th:*속성을 지정하는 방식으로 동작한다. th:*와 같이 속성을 지정하면 해당 속성이 기존 속성을 대체하고, 기존 속성이 존재하지 않으면 새로 만든다.
<input type="text" name="n" th:name="thn" />
위와 같이 HTML 코드를 입력하면
<input type="text" name="thn" />
브라우저에서 렌더링되면 이렇게 변환된다.
- 웹 브라우저는 th 속성을 모른다. 타임리프가 처리해주는 것으로, 파일을 직접 열면 th속성은 동작하지 않음
사용 선언
<html xmlns:th="http://www.thymeleaf.org">
타임리프를 사용 선언하기 위해서는 html태그에 위와 같이 th="" 와같이 선언을 해주면 된다.
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
텍스트 출력
@GetMapping("url")
public String text(Model model) {
model.addAttribute("data", "Hello Spring!");
return "htmlFile";
}
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>직접 출력하기 = [[${data}]]</li>
</ul>
</body>
</html>
addAttribute로 넘어간 데이터를 타임리프를 이용하여 출력하기 위해서는
1. th:text="" 와 ${}를 이용하여 데이터를 넘겨받거나,
2. 대괄호 2개 [[]] 안에 ${}를 이용하여 데이터를 직접 받아서 출력할 수 있다.
주의할 점은 HTML은 <,>와 같은 특수문자를 기반으로 정의된다는 점이다. 따라서 뷰 템플릿으로 HTML화면을 생성할 때는 데이터에 이러한 특수문자가 들어갈 때 웹 브라우저는 특수문자를 문자(HTML의 엔티티)로 자동으로 변경(escape)해 준다. 따라서 데이터를 HTML에 들어갈 것을 고려해서 태그를 넣어 보낸다고 실제 HTML에서 태그가 작동하지는 않는다.
이러한 escape기능이 작동하지 않도록 하려면
1. th:utext=""를 이용하거나,
2. 대괄호와 소괄호 [()]를 이용하면 escape기능이 작동하지 않게 된다.
변수
타임리프에서 변수를 사용할 때는 변수 표현식(${...})을 사용한다.
@GetMapping("url")
public String textBasic(Model model) {
Car carA = new Car();
List<Car> list = new ArrayList<>();
list.add(carA);
Map<String, Car> map = new HashMap<>();
map.put("carA", carA);
model.addAttribute("car", carA);
model.addAttribute("cars", list);
model.addAttribute("carMap", map);
return "htmlFile";
}
위와 같이 컨트롤러에서 addAttribute로 다양한 객체들을 넘길 수 있는데, 타임리프에서 이렇게 받은 객체들을 사용할 수 있다.
${car.carname}
${cars[0].carname}
${carMap['carA'].carname}
기본 식 객체
타임리프를 이용하면 request/response, 세션 등의 자주 사용하는 기본 객체를 아래와 같이 바로 접근할 수 있다.
${#request}
${#response}
${#session}
${#servletContext}
${#locale}
기본 객체 뿐만 아니라 요청 파라미터나 세션으로 넘어온 데이터, 스프링 빈에 직접 접근할 수 있다.
${param.paramData}
${session.sessionData}
${@myBean.func('data')}
또한 숫자, 문자, 배열 등을 편리하게 다룰수 있는 유틸리티 객체들도 제공한다.
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expression << 필요할때 들어가서 ctrl+f 'utility' 참고
링크
타임리프에서 URL을 생성할 때는 다음과 같은 문법을 사용하면 된다.
<a th:href="@{url}">link</a>
기본적인 문법에 더해서 매개변수를 이용해서 사용할 수도 있다.
<a th:href="@{/hello}">basic url</a>
<a th:href="@{/hello(param1=${param1}, param2=${param2})}">query param</a>
<a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a>
<a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a>
1. 기초적인 사용법
2. model을 이용해서 받은 데이터를 쿼리 파라미터로 활용하기 - () 안에 변수 설정하고 경로에 {}추가X
3. 받은 데이터로 경로 설정 - () 안에 변수 설정하고 경로에 {}로 추가
4. 받은 데이터로 경로 설정 + 쿼리 파라미터로 활용 - () 안에 변수 설정하고 경로에 {}추가하지 않으면 쿼리 파라미터로 전달된다.
> 경로에 추가하고 파라미터로도 전달하는, 동일한 변수를 이중으로 사용하는 것은 지원하지 않는다고 한다.
리터럴
문자, 숫자, boolean, null값을 리터럴로 지정할 수 있다. 타임리프에서 리터럴 표현을 위해서는 항상 ' '로 감싸주어야 한다. (th:text="text"와 같이 사용해서는 안된다)
<span th:text="'text'"></span>
기본적으로 아래와 같이 변수로 리터럴 치환이 가능한데, || 사이에 문자를 넣으면 ${} 변수를 넘겨받아서 리터럴로 자동 치환해준다. (변수를 사용하지 않더라도 ''를 안쓰고 ||으로 통일해도 되긴 한다)
<span th:text="'text ' + ${data}"></span>
<span th:text="|text ${data}|"></span>
연산
자바의 연산과 크게 다르지 않다. HTML안에서 사용하기 때문에 HTML엔티티를 사용하는 부분을 조심하자.
<li>10 + 2 = <span th:text="10 + 2"></span></li>
<li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
<li>1 > 10 = <span th:text="1 > 10"></span></li>
<li>1 >= 10 = <span th:text="1 >= 10"></span></li>
<li>1 == 10 = <span th:text="1 == 10"></span></li>
<li>1 != 10 = <span th:text="1 != 10"></span></li>
비교연산, 조건식, elvis연산자(삼항연산자) 등의 연산을 활용 가능하다.
타임리프 태그 속성
기본적으로 th:*와 같이 속성을 지정하면 타임리프는 기존 html 속성을 타임리프로 설정한 속성으로 대체한다. 이 때 th:attrappend, th:attrprepend, th:classappend와 같은 속성 태그를 사용하여 기존 속성을 수정할 수도 있다.
th:attrappend = <input type="text" class="text" th:attrappend="class=' large'" /><br/>
th:attrprepend = <input type="text" class="text" th:attrprepend="class='large '" /><br/>
th:classappend = <input type="text" class="text" th:classappend="large" /><br/>
또한 HTML의 checked와 다르게 th:checked옵션을 사용하여 checkbox를 만들면 값이 false인 경우 checked속성 자체를 제거한다(HTML의 checked는 값이 false이던 true이던 어떤 값만 가지면 체크된다)
반복
타임리프에서 반복은 th:each를 사용하며, 반복에서 사용할 수 있는 여러 상태값이 있다.
@GetMapping("/each")
public String each(Model model) {
addUsers(model);
return "basic/each";
}
private void addUsers(Model model) {
List<User> list = new ArrayList<>();
list.add(new User("userA", 10));
list.add(new User("userB", 20));
list.add(new User("userC", 30));
model.addAttribute("users", list);
}
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}">username</td>
<td th:text="${user.username}">username</td>
<td th:text="${user.age}">0</td>
<td>
index = <span th:text="${userStat.index}"></span>
count = <span th:text="${userStat.count}"></span>
size = <span th:text="${userStat.size}"></span>
even? = <span th:text="${userStat.even}"></span>
odd? = <span th:text="${userStat.odd}"></span>
first? = <span th:text="${userStat.first}"></span>
last? = <span th:text="${userStat.last}"></span>
current = <span th:text="${userStat.current}"></span>
</td>
</tr>
th:each태그에서 ${users}의 값을 하나씩 꺼내서 왼쪽 변수 user에 담아서 태그를 반복 실행한다.
th:each는 리스트뿐 아니라 Iterable가능한 모든 자바 객체를 반복에 사용할 수 있다. 또한 반복의 두번째 파라미터(userStat)을 이용하여 반복의 상태(index, count, size, even, odd, first, last, current)를 확인할 수도 있다.
조건식
<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
위와 같이 사용하면 addAttribute로 넘겨준 user의 age가 1. 20 미만일경우 미성년자 표시, 2. 20 이상이 아닐경우 미성년자 표시. (if와 unless를 이용하여 동일한 의미를 나타내었다)
<div th:if="${errors?.containsKey('globalError')}">
<p th:class="field-error" th:text="${errors['globalError']}">전체 오류 메세지</p>
</div>
위처럼 ${객체?.속성}과 같이 사용하면 객체가 null일 경우 속성에 접근하지 않고 종료. 사실상 삼항 연산자인데 ':'부분이 생략될 수 있다고 한다. (https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#conditional-expressions)
블록
<th:block>은 HTML태그가 아닌 타임리프만의 유일한 자체 태그이다. block은 주로 th:each만으로 해결이 어려울 경우 부차적으로 사용한다.
<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:each태그만 이용하면 두개의 <div></div> 안에 넣고 사용할 수 없기 때문에 th:block을 이용하여 두개의 <div></div>태그가 반복되도록 하였다.
템플릿 조각
웹 페이지에는 상/하단 영역이나 카테고리 같은 공통 영역이 많이 있는데., 이런한 부분을 코드를 복사해서 사용한다면 유지보수에 어려움이 있을 수 있으므로 비효율적이다. 타임리프는 이러한 문제를 해결하기 위해 템플릿 조각이라는 기능을 지원한다.
<footer th:fragment="copy">
FOOTER
</footer>
<footer th:fragment="copyParam (param1, param2)">
<p>PARAMETER</p>
<p th:text="${param1}"></p>
<p th:text="${param2}"></p>
</footer>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<div th:replace="template/fragment/footer :: copy"></div>
div태그를 사용하면 template/fragment/footer의 th:fragment - th:replace태그가 붙은<footer></footer>태그의 내용을 끌어와서 사용할 수 있다. (th:fragment가 붙은 태그는 다른곳에 포함될(넘겨질) 코드 조각으로 이해하면 된다)
- insert: div태그 유지
- replace: 기존 태그 대체
템플릿 레이아웃
기존에는 일부 코드 조각을 갖고와서 사용했다면, 코드 조각을 레이아웃에 넘겨서 사용할 수도 있다.
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
<title>MAIN TITLE</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
위 코드는 <head>에서 공통으로 사용하는 css, javascript같은 정보들이 있는데, 이러한 공통정보들을 한 곳에 모아두고 관리하기 위해서 이런 식으로 관리한다.
정리
기본적으로 라이브러리인 만큼 홈페이지에서 필요할 때 찾아쓰면서 익숙해지도록 적응하면 된다.
'[ Backend ] > 기타' 카테고리의 다른 글
application.yml 설정값 노출 관리하기 (0) | 2023.09.15 |
---|---|
Java 17(Spring 3.x)에서 Swagger가 동작하지 않을때.. (0) | 2023.09.04 |
[JS] 자바스크립트 기초 (0) | 2022.09.10 |
AJAX (0) | 2022.07.16 |
[Thymeleaf] 타임리프와 스프링 통합 (0) | 2022.06.30 |