[스프링 MVC - 2편] 타임리프 - 기본 기능

지현·2021년 12월 28일
0

스프링

목록 보기
22/32

타임리프 소개

타임리프 특징

  • 서버 사이드 HTML 렌더링 (SSR)
    타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용

  • 네츄럴 템플릿
    타임리프는 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있음
    타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있음
    타임리프로 작성된 파일은 해당 파일을 그대로 웹 브라우저에서 열어도 정상적인 HTML 결과를 확인할 수 있음 > 이 경우 동적으로 결과가 렌더링 되지는 않지만 HTML 마크업 결과가 어떻게 되는지 파일만 열어도 바로 확인 가능

  • 스프링 통합 지원
    타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원

  • 타임리프 사용 선언

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

텍스트 - text, utext

<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
    <li>th:text 사용 <span th:text="${data}"></span></li>
    <li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
  • HTML의 콘텐츠(content)에 데이터를 출력할 때 > th:text 사용
  • HTML 태그의 속성이 아니라 HTML 콘텐츠 영역안에서 직접 데이터를 출력할 때 > [[...]] 사용

Escape와 Unescape

"Hello <b>Spring!</b>"<b> 태그를 사용해서 Spring! 단어가 진하게 나오게 하고 싶을 때

  • 웹 브라우저 출력 : Hello <b>Spring!</b>
  • 소스보기 : Hello &lt;b&gt;Spring!&lt;/b&gt;

< 부분이 &lt; 로 변경되어 화면에 문자 그대로 출력됨 > HTML 엔티티

HTML 엔티티

  • < 를 태그의 시작이 아니라 문자로 표현할 수 있는 방법

이스케이프 (Escape)

  • HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것
  • 타임리프가 제공하는 th:text , [[...]] 는 기본적으로 이스케이프(escape)를 제공

언이스케이프 (Unescape)

  • 이스케이프 기능을 사용하지 않으려면 ? unescape 사용
  • 타임리프는 다음 두 기능을 제공
    • th:utext
    • [(...)]
<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>

기본으로는 항상 escape 처리를 해야하고 필요할때만 unescape를 사용


변수 - SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식을 사용 ${...}
변수 표현식에는 스프링 EL(스프링이 제공하는 표현식)을 사용할 수 있음

SpringEL 다양한 표현식

  • Object
    • user.username : user의 username을 프로퍼티 접근
    • user['username'] : 위와 같음
    • 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를 찾고 메서드 직접 호출

지역 변수 선언

  • th:with 를 사용하면 지역 변수를 선언해서 사용
  • 지역변수는 선언한 태그 안에서만 사용 가능
<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
  <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>

기본 객체들

타임리프는 기본 객체들을 제공

  • ${#request}
  • ${#response}
  • ${#session}
  • ${#servletContext}
  • ${#locale}

편의 객체

자주 사용하는 것들은 thymeleaf가 사용하기 편리하게 제공

  • HTTP 요청 파라미터 접근 : param
    • 원래대로 사용하려면 컨트롤러에서 모델에 담은 다음에 모델에서 뷰 템플릿으로 넘겨서 렌더링 해야함
    • 예) ${param.paramData}
  • HTTP 세션 접근 : session
    • 예) ${session.sessionData}
  • 스프링 빈 접근 : @
    • 스프링 빈에 직접 접근 가능
    • 예) ${@helloBean.hello('Spring!')}

유틸리티 객체와 날짜

타임리프 유틸리티 객체

필요할 때 메뉴얼에서 찾아서 사용하기

날짜 사용 예시

<ul>
  <li>default = <span th:text="${localDateTime}"></span></li>
  <li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>

<ul>
  <li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
  <li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
  <li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
  <li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
  <li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
  <li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
  <li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
  <li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
  <li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
  <li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
  <li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
  <li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>

컨트롤러에서 받은 localDateTime을 원하는대로 형식을 바꾸어 사용


URL 링크

타임리프에서 URL을 생성할 때는 @{...} 사용

  • 단순한 URL

    • @{/hello}
    • /hello
  • 쿼리 파라미터

    • @{/hello(param1=${param1}, param2=${param2})}
    • /hello?param1=data1&param2=data2
    • () 에 있는 부분은 쿼리 파라미터로 처리됨
  • 경로 변수

    • @{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
    • /hello/data1/data2
    • URL 경로상에 변수가 있으면 () 부분은 경로 변수로 처리
  • 경로 변수 + 쿼리 파라미터

    • @{/hello/{param1}(param1=${param1}, param2=${param2})}
    • /hello/data1?param2=data2
    • 경로 변수와 쿼리 파라미터를 함께 사용할 수 있음
    • 치환하고 남는애는 자동으로 쿼리 파라미터로 붙게 됨

경로를 만드는 부분과 데이터가 있는 부분이 분리되어있어 유지보수 하기 좋음


리터럴

리터럴 : 소스 코드상에서 고정된 값 (문자, 숫자, 불린, null)

문자

  • 타임리프에서 문자 리터럴은 항상 ' (작은 따옴표)로 감싸야 함
    <span th:text="'hello'">
  • 하지만 공백 없이 쭉 이어진다면 작은 따옴표를 생략할 수 있음
    <span th:text="hello"
  • 주의
    • <span th:text="hello world!"></span> > 중간에 공백이 있어서 오류
    • <span th:text="'hello world!'"></span> > ' 로 감싸면 정상 동작
<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>

리터럴 대체 문법을 쓰면 한번에 깔끔하게 변수를 넣을 수 있음
<span th:text="|hello ${data}|">


연산

HTML안에서 사용하기 때문에 HTML 엔티티를 사용하는 부분만 주의하면 됨

  • 비교연산 : HTML 엔티티를 사용해야 하는 부분을 주의
    ! (not), == (eq), != (neq, ne), > (gt), < (lt), >= (ge), <= (le)
  • 조건식 : 자바의 조건식과 유사
  • Elvis 연산자 : 조건식의 편의 버전
    <span th:text="${data}?: '데이터가 없습니다.'"></span>
    data가 있으면 data 값을 출력, 없으면 '데이터가 없습니다.' 출력
  • No-Operation : _ 인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작
    <span th:text="${nullData}?: _">데이터가 없습니다.</span>
    HTML 의 내용 그대로 활용 가능
    아무것도 하지 않음, thymeleaf 태그 효과가 무효화가 됨 > 기본이 출력
    thymeleaf 태그를 렌더링 하지 않는것과 비슷한 효과

속성 값 설정

속성 설정

  • th: 속성을 지정하면 타임리프는 기존 속성을 th: 로 지정한 속성으로 대체함
  • 기존 속성이 없다면 새로 만듦
<h1>속성 설정</h1>
<input type="text" name="mock" th:name="userA" />
  • 타임리프 렌더링 후 <input type="text" 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/>
  • th:attrappend : 속성 값의 뒤에 값을 추가
  • th:attrprepend : 속성 값의 앞에 값을 추가
  • th:classappend : class 속성에 자연스럽게 추가(띄어쓰기 안해줘도 됨)

checked 처리

  • HTML에서 checked 속성은 checked 속성의 값과 상관없이 checked라는 속성만 있어도 체크가 됨
    <input type="checkbox" name="active" checked="false" /> > 이 경우에도 checked 속성이 있기 때문에 checked 처리가 되어버림
  • 타임리프의 th:checked 는 값이 false 인 경우 checked 속성 자체를 제거함
    <input type="checkbox" name="active" th:checked="false" />
    타임리프 렌더링 후 : <input type="checkbox" name="active" />
  • th:checked="false"로 지정하면 체크가 되지 않음

반복

타임리프에서 반복은 th:each 를 사용, 추가로 반복에서 사용할 수 있는 여러 상태 값을 지원

반복 기능

  • <tr th:each="user : ${users}">
  • 반복시 오른쪽 컬렉션( ${users} )의 값을 하나씩 꺼내서 왼쪽 변수( user )에 담아 태그를 반복 실행
    <tr th:each="user : ${users}">
        <td th:text="${user.username}">username</td>
        <td th:text="${user.age}">0</td>
    </tr>

반복 상태 유지

  • 반복을 할 때 현재 반복에 대한 상태를 알려주는게 지원 됨
  • <tr th:each="user, userStat : ${users}">
  • 반복의 두번째 파라미터를 설정해서 반복의 상태를 확인 할 수 있음
  • 두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 됨
  • 반복 상태 유지 기능
    • index : 0부터 시작하는 값
    • count : 1부터 시작하는 값
    • size : 전체 사이즈
    • even , odd : 홀수, 짝수 여부( boolean )
    • first , last :처음, 마지막 여부( boolean )
    • current : 현재 객체

조건부 평가

  • 조건을 만족하면 출력, 만족하지 않으면 해당 태그 자체가 출력되지 않음 (태그 자체를 렌더링하지 않음)
  • if, unless(if의 반대), switch
        <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>

주석

<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->

표준 HTML 주석
자바스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둠

<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]]  한 줄일 때*/-->

<!--/*-->
<span th:text="${data}">html data</span> 여러줄일 때
<!--*/-->

타임리프 파서 주석
타임리프의 진짜 주석, 렌더링에서 주석 부분을 제거
페이지 소스 보기를 해도 보여지지 않음

<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->

타임리프 프로토타입 주석
HTML 주석에 약간의 구문을 더한것
HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 렌더링 한 경우에만
보이는 기능


블록

  • <th:block> 은 HTML 태그가 아닌 타임리프의 유일한 자체 태그
  • 타임리프의 특성상 HTML 태그안에 속성으로 기능을 정의해서 사용하는데, 사용하기
    애매한 경우에 <th:block> 사용
  • <th:block>은 렌더링시 제거됨
  • 사용하지 않는 편이 좋음, 어쩔수 없이 사용해야 할 때만 사용하기
<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>

자바스크립트 인라인

  • 타임리프에서 자바스크립트를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공
  • 적용 : <script th:inline="javascript">
<script th:inline="javascript">
    var username = [[${user.username}]];
    var age = [[${user.age}]];

    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";

    var user = [[${user}]];
</script>
  • 텍스트 렌더링
    • 인라인 사용 후 렌더링 결과를 보면 문자 타입인 경우 " 를 포함해줌
    • 추가로 자바스크립트에서 문제가 될 수 있는 문자가 포함되어 있으면 이스케이프 처리도 해줌
  • 자바스크립트 내추럴 템플릿
    • 자바스크립트 인라인 기능을 사용하면 주석을 활용해서 내추럴 템플릿 기능 사용 가능
  • 객체
    • 타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환

자바 스크립트 인라인 each

<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
    [# th:each="user, stat : ${users}"]
    var user[[${stat.count}]] = [[${user}]];
    [/]
</script>

템플릿 조각

  • 전체적인 모양은 나에게 있고 코드를 부분부분 가져다 쓸 때
  • ~{경로::이름}
    • ~{template/fragment/footer :: copy} : template/fragment/footer.html 템플릿에 있는 th:fragment="copy" 라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미

부분 포함 insert

<div th:insert="~{template/fragment/footer :: copy}"></div>

태그를 유지한 상태로 안에 가져와서 넣음, 현재 태그 내부에 추가

부분 포함 replace

<div th:replace="~{template/fragment/footer :: copy}"></div>

태그 자체를 가져온 것으로 교체, 현재 태그를 대체

부분 포함 단순 표현식

<div th:replace="template/fragment/footer :: copy"></div>

템플릿 조각을 사용하는 코드가 단순하면 ~{..} 부분 생략 가능

파라미터 사용

<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div></body>

footer.html 의 copyParam 부분

<footer th:fragment="copyParam (param1, param2)">
 <p>파라미터 자리 입니다.</p>
 <p th:text="${param1}"></p>
 <p th:text="${param2}"></p>
</footer>

파라미터를 전달해서 동적으로 조각을 렌더링 할 수도 있음


템플릿 레이아웃

  • 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만, 각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶을 때
  • 큰 모양이 정해져있고, 내 코드를 모양에 맞춰서 넣고 싶을 때
  • <head>정도에만 적용하는게 아니라 <html>전체에 적용할 수도 있음
  • 레이아웃을 사용하면 공통부분을 수정해야 할 일이 생길 때, 레이아웃 파일만 고치면 내용이나 컨텐츠 부분을 보호하고 레이아웃 구성을 다 바꿀 수 있음

layoutMain.html

<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>
  • ::title은 현재 페이지의 title 태그들을 template/layout/base 파일의 common_header에 전달
  • ::link는 현재 페이지의 link 태그들을 전달

base.html

<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>
  • base는 하나의 레이아웃이고, 여기에 일부분은 내가 원하는 값으로 채움
  • ${title}와 ${links}를 다른 html에서 전달된 값으로 채움


출처
[인프런] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

0개의 댓글