타임리프

바그다드·2023년 5월 5일
0
post-thumbnail
  • 프로젝트를 할 때 뷰 템플릿으로 타임리프를 사용했는데, 그때는 간단한 기능만 사용한 정도였다. 이번에는 타임리프의 특징과 주요 기능들을 알아보자.

특징

  1. 서버 사이드 HTML 렌더링 (SSR)
  2. 네츄럴 템플릿
  3. 스프링 통합 지원

1. 서버 사이드 HTML 렌더링 (SSR)

  • HTML을 서버에서 동적으로 렌더링하고 완성된 결과를 클라이언트에 반환

2. 네츄럴 템플릿

  • 타임리프는 순수 html을 최대한 유지하는 특징이 있다.
    JSP의 경우 서버를 거치지 않고 jsp 파일을 열어보면 본래의 html 형태를 확인할 수 없는 반면에, 타임리프를 활용하면 서버를 따로 거치지 않아도 본래의 html 형태를 확인할 수 있다.
  • 이처럼 HTML의 형태를 유지하면서 뷰 템플릿을 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿이라고 한다.

3. 스프링 통합 지원

  • 타임리프는 스프링과 통합되어 스프링의 다양한 기능을 편리하게 사용할 수 있도록 지원해준다.

기본문법

1. text vs utext

  • 텍스트를 출력하는 기능
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
    <li>th:text = <span th:text="${data}"></span></li>
    <li>th:utext = <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
    <li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
    <li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>
</body>
</html>
  • html은 <를 태그가 시작하는 것으로 인식한다. 따라서 '<>'를 그대로 출력하고 싶을 경우에는 text를, html 엔티티를 넣고 싶을 경우에는 utext를 사용하면 된다.
  • 태그 내에 변수를 직접 출력하고 싶을 경우에는 [[]]와 [()]를 사용하면 되는데, [[]]는 text와 같은 역할을, [()]는 utext와 같은 역할을 한다.

2. 변수 - SpringEL

<h1>SpringEL 표현식</h1>
<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>
  • 객체의 경우 ${객체이름}
    리스트의 경우 ${객체이름[index]}
    map의 경우 ${객체이름['key값']}
    이런 식으로 접근을 하고, 각 객체의 필드로 접근하는 방법은
    .property
    ['poperty']
    .getProperty()
    로 접근할 수 있다.

3. 기본 객체

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1>
<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>
</body>
</html>
  • 하지만 스프링 부트 3.0 부터는
    {#request}{#response}
    {#session}{#servletContext}는 지원하지 않는다.
  • 파라미터의 경우 ${param}을 이용하여 접근이 가능하고,
    빈의 경우에는 ${@빈이름}를 이용하여 빈에 직접 접근이 가능하다.

4. URL링크

<ul>
	<!--단순 url-->
    <li><a th:href="@{/hello}">basic url</a></li>
    <!--url + 쿼리 파라미터-->
    <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li>
    <!--url/pathvariable/pathvariable-->
    <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
    <!--url/pathvariable?쿼리파라미터-->
    <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li>
</ul>
  1. 단순 url
    th:href="@{/hello}
    '/hello'
  2. url + 쿼리 파라미터
    th:href="@{/hello(param1=param1,param2={param1}, param2={param2})}"
    '/hello?param1=param1¶m2=param2
  3. url + path variable
    th:href="@{/hello/{param1}/{param2}(param1=param1,param2={param1}, param2={param2})}"
    '/hello/param1/para2'
  4. url + path variable + 쿼리 파라미터
    th:href="@{/hello/{param1}(param1=param1,param2={param1}, param2={param2})}"
    '/hello/param1?param2=param2'

5. 리터럴

  1. 문자: 'hello'
    • 작은 따옴표 ''를 사용해야 함!!!
      다만, 공백 없이 문자열이 이어진다면 작은 따옴표를 생략할 수 있다.
<ul>
    <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>
  1. 숫자: 10
  2. 불린: true , false
  3. null: null

6. 연산

<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>
  • Elvis연산자는 삼항 연산자와 비슷한데, 파라미터의 값이 존재하면 파라미터 값을 그대로 출력하고, 없을 시에는 : 뒤에 오는 값을 출력한다.
    - '?:'이 표현식 사이에는 띄어쓰기가 들어가면 안된다.
  • No Operation은 값이 존재하지 않으면 타임리프가 적용되지 않은 원래의 html이 그대로 출력된다.

7. 속성 설정

  • th:*를 설정하면 태그의 속성을 타임리프의 속성으로 대체를 한다.
    예를 들어 name과 함께 th:name을 사용하면 name을 th:name의 값으로 대체된다.
<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/>
  • th:attrappend="class='large'"
    이런식으로 설정을 해주면 기존의 속성을 대체하는 것이 아니라 기존의 속성에 덧붙여서 사용할 수 있다.
    - th:attrappend : 뒤에 붙이기
    - th:attrprepend : 앞에 붙이기
    - th:classappen : class 속성에 추가
  • 기본적으로 html에서는 checked 속성을 사용하면 속성의 값이 true든 false든 기본적으로 체크가 되어 있는데, 타임리프를 활용해 th:checked를 사용하면 설정 값에 따라 체크의 여부를 설정할 수 있다.

8. 반복문

	<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을 이용해 반복문을 사용할 수 있다.
  • th:each="user, userStat : ${users}"
    자바의 반복문처럼 users의 각 객체는 user에 담기게 되고, 두 번째 변수 userStat을 이용해서 반복의 상태를 확인할 수 있다.
    - 두 번째 변수를 생략할 경우에는 지정한 변수명(여기서는 user) + Stat을 붙여 사용할 수 있다.

9. 조건문

	<tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <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>
    </tr>
    
    <tr th:each="user, userStat : ${users}">
        <td th:text="${userStat.count}">1</td>
        <td th:text="${user.username}">username</td>
        <td th:switch="${user.age}">
            <span th:case="10">10살</span>
            <span th:case="20">20살</span>
            <span th:case="*">기타</span>
        </td>
    </tr>
  • th:if는 조건이 참일 때 값을 출력하고,
    th:unless는 조건이 거짓일 때 값을 출력한다.
  • th:switch는 자바의 switch-case문으로 조건에 만족하는 값만 출력을 한다.
    - th:case="*" 이것처럼 디폴트 값을 설정할 수 있다.
  • 타임리프의 조건문은 조건을 만족하지 않을 경우에는 태그 자체를 렌더링하지 않는다.

10.주석

<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>
/*/-->
  1. 표준 thml주석
    html 소스코드를 볼 때 확인이 가능하다
  2. 타임리프 파서 주석
    렌더링 할 때 해당 주석을 렌더링하지 않기 때문에 html 소스코드에서도 확인할 수 없다.
  3. 타임리프 프로토타입 주석
    html파일로 확인할 경우에는 html주석의 형태로 소스코드에서만 확인이 가능한데,
    서버에서 렌더링될 경우에는 일반 타임리프 값처럼 해당 값이 화면에 표시된다.

11. 블록

  • 타임리프에서 제공하는 유일한 자체 태그
<body>
<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>
</body>
  • 타임리프의 기능을 적용하기 위한 범위를 설정할 때 사용한다.
  • 위의 경우처럼 두개의 div태그를 반복할 경우에 하나의 기능 단위로 묶기 위해 사용된다
  • th:block의 경우 렌더링 시에 제거된다.

12. 자바 스크립트 인라인

  • 자바스크립트 안에서 타임리프를 활용할 수 있다.
<!-- 자바스크립트 인라인 사용 전 -->
<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>
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>

변수

  • js의 변수에 텍스트를 넣을 경우 ""로 감싸서 넣어야 하는데, 자바 인라인을 사용하지 않을 경우 따로 개발자가 ""를 넣어줘야 하는 번거로움이 생기는데 인라인을 사용하면 값에 ""를 붙여 변수에 담아준다.

주석

  • 타임리프는 내추럴 템플릿으로 html을 직접 열어도 동작을 하는데, 인라인을 적용하기 전의 사상태를 봐보면 userA라는 값이 주석처리된 상태 그대로 주석처리되어 소스코드에 노출되어 있다.
    반면에 인라인을 사용하면 "test username"이라는 문자열이 "userA"로 대체되어 있는 것을 확인할 수 있다.

객체 값

  • js의 변수에 객체를 담을 경우에 인라인을 사용하지 않으면 toString()메서드를 호출한 값이 들어가는데, 인라인을 사용하면 객체를 json으로 변환해준다.

반복문

  • js에서 반복문을 사용해야하는 경우가 있을 수 있는데 인라인으로 타임리프 반복문을 사용하여 각 변수를 선언하고, 값을 넣어줄 수 있다.

13. 템플릿 조각

  • 프로젝트를 진행할 때 div에 들어가는 요소와 같이 각 페이지에서 공통된 부분이 있었는데, 타임리프를 활용하면 템플릿 조각을 이용하여 공통된 부분을 한번에 처리할 수 있다.
  • 템플릿 조각을 만들기 위해 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>
  • 템플릿 조각을 이용할 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의 경우 해당 태그를 템플릿 조각으로 대체한다.
  • 또한 자바에서 파라미터를 넘겨주는 것처럼 템플릿 조각에 파라미터를 넘겨줄 수 있다.

14. 템플릿 레이아웃

  • 13번에서는 일부 템플릿 조각을 단순히 가지고 왔다면, 이번에는 코드 조각을 이용해 레이아웃을 변경하거나 추가하는 방법을 알아보자
  • 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>
  • 템플릿 조각을 가져다 쓰기 위한 레이아웃(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>

  • 소스 코드를 확인해보면 layoutMain에서 ~{::title},~{::link}를 이용해 base.html에 파라미터로 넘겼다.
    base.html에서는 이 파라미터를 받아 title태그를 파라미터로 넘어온 title로 대체하고, th:block부분을 파라미터로 넘어온 link로 대체하였다.
    그리고 base.html에 있던 나머지 태그는 그대로 유지하여 layoutMain에서 띄운 것을 확인할 수 있다.
  • 이처럼 기존 레이아웃에서 유지할 부분을 템플릿 조각으로 파라미터로 넘기고, 템플릿 조각에서는 레이아웃에서 유지해야할 태그에 추가 태그를 더해 레이아웃에 띄울 수 있다.

15. 템플릿 레이아웃2

  • 이번에는 레이아웃에서 일부 파라미터만 유지하고 나머지 태그를 모두 수정하는 방법을 알아보자
  • 전체적인 레이아웃을 유지하고 일부분만 수정해야 할 때 사용한다.
  • 먼저 템플릿 조각을 생성하자(layoutFile.html)
<!DOCTYPE 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>
  • 템플릿 조각을 이용할 레이아웃을 생성하자(layoutExtendMain.html)
<!DOCTYPE html>
<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>

  • 서버를 구동하고 소스코드를 확인해보면, 레이아웃에서 템플릿 조각에 넘긴 title과 section태그를 제외한 모든 태그가 템플릿 조각으로 대체된 것을 확인할 수 있다.
  • 이 방식은 적은 페이지를 구성하거나 공통된 부분이 많을 때는 사용할 수 있으나, 페이지별로 수정을 하기가 어렵다는 단점이 있다.

출처 : 김영한님의 스프링MVC2편 인프런

profile
꾸준히 하자!

0개의 댓글