Thymeleaf의 장점
Dependency에 Thymeleaf를 추가한 프로젝트를 생성한 뒤, application.properties에 아래 문장을 추가합니다.
spring.thymeleaf.cache=false
위의 설정은 타임리프 템플릿을 캐싱해두지 않겠다는 의미입니다. 타임리프는 처음 시작했을때만 템플릿을 파싱하고, 그 내용을 캐시에 저장해두기 때문에 중간에 코드를 수정한다고해서 바로 템플릿에 반영되지는 않습니다.
이 기능은 실제 서비스를 운영할때 불필요한 파싱을 줄여주기 때문에 이득이지만, 개발을 진행하는 중에는 오히려 계속 재시작을 시켜야 변경사항을 볼 수 있게 되어 불편합니다.
기본 사용법
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> //html 태그에 타임리프 html임을 명시
<head>
...
위와 같이 html 태그에 명시적으로 thymeleaf를 사용할 것임을 적어둔 뒤, th:
를 적고 속성값을 넣어 사용할 수 있습니다.
몇가지 예를 들자면
<body>
<ul>
<li th:each="dto: ${list}"> //Controller에 있는 list를 전달 받음, th:each로 반복문 수행
[[${dto}]] //인라인표현식, 태그 지정 없이 쓰고싶을 때 사용
</li>
</ul>
</body>
또한 반복문에는 state를 사용할 수 있습니다. state를 통해 index를 지정하거나(index), 반복문의 현재까지 반복횟수(count), 반복문 크기(size), 현재 가리키고 있는 객체(current)를 알 수 있습니다.
<li th:each="dto, state : ${list}">
[[${state.index}]] --- [[${dto}]]
</li>
<li th:each="dto, state : ${list}" th:if="${dto.sno % 5 == 0}"> //th:if 사용
[[${dto}]]
</li>
위와 같이 하나의 태그 안에 여러 th:
속성을 넣을수도 있습니다. 반복문은 th:if
와 th:unless
가 있는데, 다른 언어들처럼 if~else로 묶여야만 하는것이 아니라, 별도로 처리할 수 있습니다.
<li th:each="dto, state : ${list}">
<span th:if="${dto.sno % 5 == 0}" th:text="${'-----------------'+dto.sno}"></span>
<span th:unless="${dto.sno % 5 == 0}">[[${dto.first}]]</span>
</li>
삼항연산자도 이용할 수 있습니다. 특이한 점은 삼항연산자의 마지막행을 생략해서 if문처럼 사용도 가능하다는 점입니다.
<li th:each="dto, state : ${list}" th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}">
<li th:each="dto, state : ${list}" th:text="${dto.sno % 5 == 0} ? ${dto.sno}">
위의 방법을 응용해서 특정 상황에서만 클래스를 지정하여 CSS를 넣는 등의 작업을 할 수 있습니다.
<li th:each="dto, state : ${list}" th:class="${dto.sno % 5 == 0} ? 'target'"> //th:class 사용
th:inline=
은 인라인 설정인데, 뒤에 text 뿐만이 아니라 javascript도 올 수 있습니다. 인라인 속성에 관한 설명은 아래 링크에서 잘 정리되어 있으니 참고바랍니다.
https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html
https://www.concretepage.com/thymeleaf/thymeleaf-javascript-inline-example-with-variable
<script th:inline="javascript">
var msg = [[${result}]];
var dto = [[${dto}]];
</script>
th:block
은 태그 없이 사용할 수 있는 타임리프 속성으로 실제 화면에서는 html로 처리되지 않기 때문에 루프등을 한번에 처리하기에 좋습니다.
<th:block th:each="dto: ${list}">
<li th:each="dto, state : ${list}" th:text="${dto}">
</li>
</th:block>
th:href=@{}
로 링크 처리를 할 수 있습니다.
<a th:href="@{/sample/exView/{sno}(sno=${dto.sno})}">[[${dto}]]</a>
이 외에도 Thymeleaf는 여러가지 기본 객체와 Expression Utility 객체를 지원합니다. 하나하나 설명하기에는 너무 많기 때문에 아래 공식문서를 참고해주세요.
https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#appendix-a-expression-basic-objects
교재에는 LocalDateTime관련 처리가 복잡하기 때문에 별도의 API를 추가하라고 되어있지만, 현재에는 Expression Utility 객체 기능에 포함이 되어 있는 것 같아 따로 기술하지 않겠습니다.
th:fragment
는 HTML에서 fragment를 나타냅니다. 쉽게 생각하면 CSS를 사용할 때 HTML에 #id를 지정해주는 것과 비슷하다고 볼 수 있습니다.
<div th:replace="~{/fragments/fragment1 :: part1}"></div>
이것을 사용하고 싶다면 th:replace
나 th:insert
를 이용하면 됩니다.
<h1>Layout 1 - 1</h1>
<div th:replace="~{/fragments/fragment1 :: part1}"></div>
<h1>Layout 1 - 2</h1>
<div th:insert="~{/fragments/fragment1 :: part2}"></div>
<h1>Layout 1 - 3</h1>
<th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>
셋의 차이는 생성된 HTML 소스에서 확인할 수 있습니다.
첫번째와 세번째와 같이 th:replace
를 사용하는 경우, 해당 자리의 div를 대체하여 생성하는 반면에, th:insert
를 사용하는 경우, div안에 또다시 div를 생성하여 내용을 넣습니다.
th:replace
나 th:insert
를 사용할 때 ::
뒤에 fragment이름 뿐만이 아니라 #id의 선택자를 넣어도 동작합니다.
::
이 생략된다면 해당 파일 전체를 의미합니다.
th:fragment
의 대상을 target부분에 파라미터를 받을 수 있습니다.
fragment3.html
<div th:fragment="target(first, second)">
exLayout2.html
<th:block th:replace="~{/fragments/fragment3::target(~{this:: #ulFirst},~{this::#ulSecond})}">
~{ } 표시는 fragment 경로를 가리키고 있음을 의미합니다.