타임리프의 특징으로 크게 3가지가 있다.
내츄럴 템플릿
: 타임리프는 순수 HTML을 최대한 유지하려는 특성을 가지고 있어, 웹 브라우저에서 파일을 열어도 내용을 확인할 수 있으며, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다.
순수 HTML을 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 내츄럴 템플릿이라 한다.
스프링 통합 지원
: 타임리프는 스프링과 유연하게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.
간단한 표현:
◦ 변수 표현식 : ${. . .}
◦ 선택 변수 표현식 : *{. . .}
◦ 메시지 표현식 : #{. . .}
◦ 링크 URL 표현식 : @{. . .}
◦ 조각 표현식 : ~{. . .}
리터럴
◦ 텍스트 : 'one text', 'Another one!',…
◦ 숫자 : 0, 34, 3.0, 12.3,…
◦ 불린 : true, false
◦ 널 : null
◦ 리터럴 토큰 : one, sometext, main,…
문자 연산 :
◦ 문자 합치기 : +
◦ 리터럴 대체 : |The name is ${name}|
산술 연산 :
◦ Binary operators : +, -, *, /, %
◦ Minus sign (unary operator) : -
불린 연산 :
◦ Binary operators : and, or
◦ Boolean negation (unary operator) : !, not
비교와 동등 :
◦ 비교 : >
, <
, >=
, <=
(gt, lt, ge, le)
◦ 동등 연산 : ==
, !=
(eq, ne)
조건 연산 :
◦ If-then : (if) ? (then)
◦ If-then-else : (if) ? (then) : (else)
◦ Default : (value) ?: (defaultvalue)
특별한 토큰 :
◦ No-Operation : _
ex) <span th:text="${data}?: _">데이터가 없습니다.</span>
참고 : www.thymeleaf.org/doc/tutorials
HTML 콘텐츠에 데이터 출력 시 - th:text
<span th:text="${data}">
HTML 태그의 속성이 아닌 HTML 콘텐츠 영역 안에서 데이터 출력 시 - [[. . .]]
컨텐츠 안에서 직접 출력하기 = [[${data}]]
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
HTML 문서는 <
, >
같은 특수문자를 기반으로 정의된다.
"Hello <b>Velog</b>"
, <b>
태그를 사용하여 굵은 글씨로 표현하였을 때 ,
Hello <b>Spring!</b>
Hello <b>Spring!</b>
<b>
태그를 사용하여 강조하는 것이 목적이었지만, 그대로 출력되고 소스를 보면 <
부분이 <
로 변경됨을 확인할 수가 있다.
HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape) 라고 한다. 그리고 타임리프가 제공하는 th:text
, [[...]]
는 기본적으로 이스케이스(escape)를 제공한다.
그렇다면 이스케이프(escape) 기능을 사용하지 않으려면 어떻게 해야할까?
타임리프는 다음과 같은 Unescape 기능을 제공한다.
th:text → th:utext
[[...]]
→ [(...)]
1. text vs utext
<ul>
<li>th:text = <span th:text="${data}"></span></li>
<li>th:utext = <span th:utext="${data}"></span></li>
</ul>
2. [[...]] vs [(...)]
<ul>
<li><span th:inline="none"> </span>[[${data}]]</li>
<li><span th:inline="none"> </span>[(${data})]</li>
</ul>
th:inline="none" : 타임리프는 [[...]]를 해석하기에 해석하지 말라는 옵션
HTML 엔티티
: 웹 브라우저는<
를 HTML의 태그의 시작으로 인식하여, 문자로 표현할 수 있는 방법을 필요로 하는데 이를, HTML 엔티티라 한다.
타임리프에서 변수를 사용할 때, 변수 표현식을 사용한다.
SpringEL 다양한 표현식 사용
Object
"${user.username}"
: user의 username을 프로퍼티 접근 → user.getUsername()
"${user['username']}"
: 위와 동일 → user.getUsername()
"${user.getUsername()}"
: user의 getUsername()
을 직접 호출
List
"${users[0].username}"
: List
에서 회원을 찾고 username 프로퍼티 접근 → list.get(0).getUsername()
"${users[0]['username']}"
: 위와 동일
"${users[0].getUsername()}"
: List에서 첫 번째 회원을 찾고 메서드 직접 호출
Map
"${userMap['userA'].username}"
: Map
에서 userA를 찾고, username 프로퍼티 접근 → map.get("userA").getUsername()
"${userMap['userA']['username']}"
: 위와 동일
"${userMap['userA'].getUsername()}"
: Map
에서 userA를 찾고 메서드 직접 호출
#message
: 메시지, 국제화 처리#uris
: URI 이스케이프 지원#dates
: java.util.Date 서식 지원#calendars
: java.util.Calendar 서식 지원#temporals
: 자바8 날짜 서식 지원#numbers
: 숫자 서식 지원#strings
: 문자 관련 편의 기능#objects
: 객체 관련 기능 제공#bools
: boolean 관련 기능 제공#arrays
: 배열 관련 기능 제공#lists
, #sets
, #maps
: 컬렉션 관련 기능 제공#ids
: 아이디 처리 관련 기능 제공유틸리티 객체들은 필요할 때 찾아서 사용하면 된다.
타임리프에서 URL을 생성할 때는 @{...}
문법을 사용
단순한 URL
@{/hello}
→ /hello
쿼리 파라미터
@{/hello(param1=${param1}, param2=${param2})}
/hello?param1=data1¶m2=data2
()
에 있는 부분은 쿼리 파라미터로 처리된다.경로 변수
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
/hello/data1/data2
()
에 있는 부분은 쿼리 파라미터로 처리된다.경로 변수 + 쿼리 파라미터
@{/hello/{param1}(param1=${param1}, param2=${param2})}
/hello/data1?param2=data2
리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.
타임리프에서 문자 리터럴은 항상 '
(작은 따옴표)로 감싸야 하지만, 항상 '
(작은 따옴표)로 감싸는 것은 쉽지 않은 일이다.
이 때, 리터럴 대체 문법을 사용하면 템플릿을 사용하는 것과 같이 편리하다.
<!-- 주석 부분, 예외 발생 코드 -->
<!--<li>"hello world!" = <span th:text="hello world!"></span></li>-->
<li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li>
<li>'hello world!' = <span th:text="'hello world!'"></span></li>
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
타임리프는 주로 HTML 태그에 th:*
속성을 지정하는 방식으로 동작한다. th:*
로 속성을 적용하면 기존 속성을 대체한다. 기존 속성이 없으면 새로 만든다
th:checked = "false"/"true"
th:attrappend
: 속성 값의 뒤에 값을 추가한다.
th:attrprepend
: 속성 값의 앞에 값을 추가한다.
th:classappend
: class 속성에 자연스럽게 추가한다.
HTML에서
checked
속성은checked
속성의 값과 상관없이checked
라는 속성만 있어도 체크가 된다. 타임리프의th:checked
의 값이false
일 경우, 속성 자체를 제거 !
자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.
<script th:inline="javascript">
var username = [[${user.username}]];
var username = userA;
var username = "userA";
인라인 사용 전 렌더링 결과는 우리가 기대한 "userA"
가 아닌 userA
변수 이름이 그대로 남아있다. 결과적으로 userA
가 변수명으로 사용되어 자바스크립트 오류가 발생한다.
인라인 사용 후 렌더링 결과는 문자 타입인 경우"
포함해주며, 문제가 될 수 있는 문자가 포함되어 있을 경우, 이스케이프 처리도 해준다.
var user = [[${user}]];
var user = Controller.User(username=userA, age=10);
var user = {"username":"userA","age":10};
인라인 사용 전은 객체의 toString()
이 호출된 값이며, 인라인 사용 후는 객체를 JSON
으로 변환해준다
자바스크립트 인라인 기능을 사용하면 주석을 활용해서 이 기능을 사용할 수 있다
var username2 = /*[[${user.username}]]*/ "test username";
var username2 = /*userA*/ "test username";
var username2 = "userA";
인라인 사용 전 결과는 내추럴 템플릿 기능이 동작하지 않고, 심지어 렌더링 내용이 주석처리 되어 버린다.
인라인 사용 후 결과를 보면 주석 부분이 제거되고, 기대한 "userA"
가 정확하게 적용된다.
웹 페이지에는 공통 영역들이 존재한다. 상단 영역, 하단 영역 그리고 카테고리를 구현한 영역들이 이에 해당한다고 할 수 있다. 이런 영역들은 매번 사용하여 구현한다면 비효율적이기에 타임리프는 템플릿 조각과 레이아웃 기능을 지원한다.
<footer th:fragment="copy">
Footer Area.
</footer>
th: fragment
: 다른 영역에 포함되는 코드 조각<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 insert</h2>
<div>
<footer>
Footer Area.
</footer>
</div>
th:insert
를 사용하면 현재 태그(div
) 내부에 추가한다.
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<footer>
Footer Area.
</footer>
th:replace
를 사용하면 현재 태그(div
)를 대체한다.
<div th:replace="template/fragment/footer :: copy"></div>
<h2>부분 포함 단순 표현식</h2>
<footer>
Footer Area.
</footer>
~{...}
사용하는 것이 원칙이다. 템플릿 조각을 사용하는 코드가 단순하면 이 부분을 생략할 수 있다
📌 본 포스트는 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 통해 학습한 내용을 요약 및 정리한 것입니다.
타임리프까지... 웹프 100점 예약~