Spring/mvc

[Spring] Thymeleaf 정리

미소여우 2022. 3. 16. 11:26
728x90

사전 지식

타임리프는

  • 서버사이드렌더링

화면을 렌더링하는 방식은 크게 두 가지가 있다. 첫 번째는 브라우저단에서 그리는 CSR(Client Side Rendering)이다. 이것은 리액트나 vue.js 등을 활용하여 동적으로 내용을 갖고와 브라우저에서 직접 화면을 생성할 때 사용한다. 두 번째는 SSR(Server Side Rendering)이다. 서버에서 화면을 그리고 그 html내용을 클라이언트에 보내면 클라이언트(브라우저)는 출력만 하면 된다. 그리고 스프링에서는 대표적으로 이 타임리프를 밀어주고 있고, 백엔드 개발자는 이것을 잘 활용해서 화면을 출력하자.

리액트나, Vue.js도 공부하면 좋겠지만 백엔드만 해도 공부할게 너무 방대하다..
  • 네츄럴 템플릿

타임리프의 목적은 순수 HTML을 유지하게 해준다. 과거 JSP를 사용했던 시절에는 JSP 소스코드와 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 operatos: 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: _

선언

타임리프를 사용하려면 아래와 같이 html 태그에 선언해주어야 한다.

<html xmlns:th="http://www.thymeleaf.org">

 

텍스트 대체하기

태그 내의 텍스트의 값을 설정하려면 th:text를 사용하면 된다. 여기서 ${data}는 모델에 저장된 data라는 값을 가져올 때 사용되는 구문이다.

<span th:text="${data}"></span>

텍스트 내에서 바로 출력하려면 다음과 같이 [[]] 를 활용한다.

<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>

간혹, 모델에서 값을 <b>내용</b>으로 넣어서 화면에서 강조되게 하려면 잘 안 될 것이다. 그럴때는 위 태그들을 다음과 같이 바꿔보자.

  • th:utext
  • [()]

이렇게 하면 언이스케이프 방식으로서, 내가 지정한 태그들을 읽게 해준다. 하지만 이런식으로 서비스를 지원하면 악용될 우려도 있고, 깨질 수도 있기에 기본적으로는 이스케이프 형태로 처리하자. 

객체 접근하기 (SpringEL)

컨트롤러에 username과 age를 가진 인스턴스를 넣어줬다.

첫 번째는 인스턴스, 두 번째는 리스트, 세 번째는 map으로 넣어주었다. 그리고 접근을 다음과 같이 하면 된다.

<ul>Object
    <li>${user.username} = <span th:text="${user.username}"></span></li>
    <li>${user['username']} = <span th:text="${user['username']}"></span></li>
    <li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>
<ul>List
    <li>${users[0].username} = <span th:text="${users[0].username}"></span>
    </li>
    <li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span>
    </li>
    <li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span></li>
</ul>
<ul>Map
    <li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
    <li>${userMap['userA']['username']} = <span th:text="${userMap['userA']['username']}"></span></li>
    <li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>

타임리프에서 지역 변수를 사용할 수 있다. 자바의 지역변수처럼 태그 내에서만 사용할 수 있게 된다.

<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>

 

기본 객체

스프링에서 제공하는 request, response 등 여러 객체도 접근할 수 있다.

심지어 빈도 접근 가능하다!

두 번째 ul 내용을 보면 param, session 등 파라미터값과 세션값을 이런식으로 불러 쓸 수 있다.

<ul>
    <li>request = <span th:text="${#request}"></span></li>
    <li>response = <span th:text="${#response}"></span></li>
    <li>session = <span th:text="${#session}"></span></li>
    <li>servletContext = <span th:text="${#servletContext}"></span></li>
    <li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1>
<ul>
    <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
    <li>session = <span th:text="${session.sessionData}"></span></li>
    <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>

 

URL 링크

url 링크는 @{} 형식으로 사용하며, 파라미터 인자값을 줄 수 있다.

<ul>
  <li><a th:href="@{/hello}">basic url</a></li>
  <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li>
  <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
  <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li>
</ul>

 

리터럴

리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.

String a = "Hello"나 int a = 10 * 20에서 Hello는 문자 리터럴, 10, 20은 숫자 리터럴이다.

타임리프에서는 리터럴은 항상 작은 따옴표로 감싸야 한다.

<span th:text="'hello'">

근데 사실 매 리터럴마다 작은 따옴표를 쓰는 것은 상당히 복잡한 일이기에, 타임리프에서는 공백 없이 이어지는 문자면 하나의 의미있는 토큰으로 인정해 생략할 수 있다.

'A-Z', 'a-z', '0-9', '[]', '.', '-', '_'

근데 띄어쓰면 문제가 된다. 아래와 같이 쓰면 오류가 발생한다.

<span th:text="hello world">

이 번거로움을 해결해주기 위한 구문으로 좋은게 나타났는데 바로 ||를 사용하면 된다.

아래 코드의 마지막을 보면 |내용|으로 감쌌고, 안에 모델 값도 꺼내 쓸 수 있다.

<ul>
    <!--주의! 다음 주석을 풀면 예외가 발생함-->
    <!-- <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>
</ul>

 

연산자

타임리프 내에서 연산자는 다음과 같이 쓸 수 있다.

<ul>
    <li>산술 연산
        <ul>
            <li>10 + 2 = <span th:text="10 + 2"></span></li>
            <li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li>
        </ul>
    </li>
    <li>비교 연산
        <ul>
            <li>1 > 10 = <span th:text="1 &gt; 10"></span></li>
            <li>1 gt 10 = <span th:text="1 gt 10"></span></li>
            <li>1 >= 10 = <span th:text="1 >= 10"></span></li>
            <li>1 ge 10 = <span th:text="1 ge 10"></span></li>
            <li>1 == 10 = <span th:text="1 == 10"></span></li>
            <li>1 != 10 = <span th:text="1 != 10"></span></li>
        </ul>
    </li>
    <li>조건식
        <ul>
            <li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)?'짝수':'홀수'"></span></li>
        </ul>
    </li>
    <li>Elvis 연산자
        <ul>
            <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가없습니다.'"></span></li>
            <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li>
        </ul>
    </li>
    <li>No-Operation
        <ul>
            <li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li>
            <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가없습니다.</span></li>
        </ul>
    </li>
</ul>

Elivs연산자라 불리는 이유는 ?: 이게 엘비스 프레슬리의 머리와 닮아 그렇다 한다. ㅎㅎ

아무튼 저렇게 값이 있을 때 없을 때를 편하게 분기해서 해줄 수 있다.

No-Operation은 값이 없을 때 아무것도 수행시키지 않는다는 의미가 된다. 그래서 기본 태그 안의 내용이 출력된다.

속성값 설정

th:***을 활용해서 태그의 속성들을 설정할 수 있다.

<input type="text" name="mock" th:name="userA" />
<h1>속성 추가</h1>
- 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/>
<h1>checked 처리</h1>
- checked o <input type="checkbox" name="active" th:checked="true" /><br/>
- checked x <input type="checkbox" name="active" th:checked="false" /><br/>
- checked=false <input type="checkbox" name="active" checked="false" /><br/>

html에서 checked="false"여도 체크 표시가 되어있는데, 타임리프에서 checked가 false이면 체크 안 되게 렌더링 해준다.

반복

반복은 다음과 같이 사용한다.

반복이 필요한 태그에 th:each="user : ${users}" 같이 값을 넣어주면 된다.

반복은 java.util.Iterable, java.util.Enumeration을 구현한 반복이 가능한 객체들이면 모두 사용할 수 있다.

Map도 되는데 이 경우 변수에 담기는 값은 Map.Entry가 된다.

<table border="1">
    <tr>
        <th>username</th>
        <th>age</th>
    </tr>
    <tr th:each="user : ${users}">
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
    </tr>
</table>
<h1>반복 상태 유지</h1>
<table border="1">
    <tr>
        <th>count</th>
        <th>username</th>
        <th>age</th>
        <th>etc</th>
    </tr>
    <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>
</table>

두 번째 each문에서 userStat같이 넣었는데, 이거는 현재 반복문의 상태에 대한 값을 담을 수 있다.
두 번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 된다.
여기서는 user + Stat = userStat 이므로 생략 가능하다.

다음과 같은 값들을 가져다 쓸 수 있다.

  • index : 0부터 시작하는 값
  • count : 1부터 시작하는 값
  • size : 전체 사이즈
  • even , odd : 홀수, 짝수 여부( boolean )
  • first, last :처음, 마지막 여부( boolean )
  • current : 현재 객체

조건

if, unless를 활용하여 조건문을 쓸 수 있다. 조건에 만족하지 않으면 해당 태그 자체가 렌더링 되지 않는다.

<td>
  <span th:text="${user.age}">0</span>
  <span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
  <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
</td>

<td th:switch="${user.age}">
  <span th:case="10">10살</span>
  <span th:case="20">20살</span>
  <span th:case="*">기타</span>
</td>

스위치 문도 쓸 수 있고, *은 위에 조건에 부합하지 않을 때 사용된다.

주석

주석 기능이다.  1은 표준, 2는 타임리프 주석이라 렌더링 시 아예 안 보인다.

3은 좀 독특한데, html파일로 직접 열었을 때는 주석 처리하고, 타임리프로 열었을 때는 렌더링 되게 하는 것이다.

<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>
/*/-->

 

블록

여러 태그에 대해 반복을 돌리고 싶을 때가 있다. 근데 태그마다 반복시키기엔 노가다이니 블록이란 개념이 도입되었다.

<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은 렌더링 시 제거된다.

 

자바스크립트 인라인

타임리프에서는 자바스크립트도 편하게 바꿔준다.

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