Spring View Templates - Thymeleaf

YUNU·2023년 10월 20일
0

스프링

목록 보기
29/33
post-thumbnail

🪴 Spring MVC


🌱 Thymeleaf 타임리프

🟦 타임리프 특징

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

백엔드 서버에서 HTML을 동적으로 렌더링하는 용도로 사용

🔷 Natural Templates

순수 HTML을 최대한 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 말함

Thymeleaf vs JSP

  • Thymeleaf
    순수 HTML 파일을 웹브라우저에서 열면 내용을 확인할 수 있고,
    서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있음
  • JSP
    JSP 파일은 웹브라우저에서 열면 JSP 소스코드와 HTML이 뒤죽박죽 섞여 확인이 어려움
    -> 서버를 통해 JSP를 열어야 함

🔷 스프링 통합 지원

타임리프는 스프링을 위해 만들어진 것처럼 자연스럽게 통합됨
스프링의 다양한 기능을 편리하게 사용할 수 있도록 지원함


🟦 타임리프 사용 선언

xmlns:th="http://www.thymeleaf.org"

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<!-- -->
</html>  

🟦 속성 변경 // th:xxx

HTML의 속성을 th:xxx로 변경, 기존 HTML 속성값이 없다면 새로 생성

th:xxx가 붙은 부분은 서버사이드에서 렌더링 됨
➡️ 기존 HTML 속성을 대체

HTML 파일을 직접 연다면 th:xxx가 있더라도 웹 브라우저는 이를 무시함
➡️ HTML 파일 보기를 유지하면서 템플릿 기능이 가능

Natural Templates
➡️ 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용 가능한 타임리프의 특징

ex)
<!-- href=xxx 를 th:href=xxx로 변경 -->
<link href="../ABC"
      th:href="@{/abc}">

<button onclick="location.href='ABC.html'"
        th:onclick="|location.href='@{/abc/def/hi}'|"
        type="button"></button>

🔷 속성 변경 - th:value

🔹 th:value="${속성 값}"

모델에 있는 member 정보를 획득하고 프로퍼티 접근법으로 출력 (member.getName())
value 속성을 th:value 속성으로 변경

ex)
th:value="${member.name}"

<button onclick="location.href='ABC.html'"
        th:onclick="|location.href='@{/abc/def/hi/patch(memberId=${member.id)}'|"
        type="button"></button>

🔷 속성 변경 - th:action

HTML form에서 action에 값이 없으면 현재 URL에 데이터 전송

ex)
등록 폼을 호출하는 URL과 실제 등록을 처리하는 URL을 동일하게 하고 HTTP 메서드로 두 기능을 구분

  • 멤버 등록 폼 호출 : GET /abc/def
  • 멤버 등록 처리 : POST / abc/def

🔷 속성 추가

  • th:attrappend : 속성 값의 뒤에 값을 추가
    ex) name="name" th:attrappend="name= 'hi'"
  • th:attrprepend : 속성 값의 앞에 값을 추가
    ex) name="name" th:attrappend="name='hi '"
  • th:classappend : class 속성에 자연스럽게 추가
    ex) name="name" th:attrappend="name='hi'"

🔷 checked 처리

  • th:checked="false"

HTML에서 checked 속성은 checked 속성의 값과 상관없이 checked라는 속성만 있으면 체크박스에 체크됨

타임리프의 th:checked는 값이 false라면 checked 속성 자체를 제거함
th:checked 값이 true라면 체크, false라면 체크되지 않을 것


🟦 URL 링크 표현식 // @{...}

🔹 단순한 URL
th:href="@{URL}"

타임리프는 URL 링크를 사용하는 경우 @{URL링크}를 사용함

🔹 쿼리 파라미터, 경로 변수
th:href="@{URL/{_path variable}(path variable=${경로변수 값}[,query='쿼리 파라미터 값'])_}"

쿼리 파라미터
th:href="@{/URL(param1=${param1}, param2=${param2})}
() 안에 부분은 쿼리 파라미터로 처리

경로 변수
th:href="@{/URL/{param1}/{param2}(param1=${param1}, param2=${param2})}
URL 경로상에 변수가 있으면 () 부분은 경로 변수로 처리

ex
th:href="@{/abc/def/hi/{memberId}(memberId=${member.id})}"
-> 생성 링크 : http://localhost:8080/abc/def/hi/member.name값

th:href="@{/ABC/{memberName}(memberName=${member.name},query='hi')}"
-> 생성 링크 : http://localhost:8080/ABC/member.name값?query=hi

리터럴 대체 문법을 활용하여 간단히 사용할 수도 있음

ex)
th:href="@{/abc/def/hi/{memberId}(memberId=${member.id})}"
<=>
th:href="@{|/abc/def/hi/${member.name}|}"

🟦 리터럴

Literlas : 소스 코드상에 고정된 값을 말하는 용어

  • '문자'
    A-Z, a-z, 0-9, [], . ,-, _ 문자들이 공백없이 이어진다면 ' ' 생략 가능
  • 숫자
  • boolean
  • null

🟦 리터럴 대체(Literal substitutions) // |...|

|문자 표현식 문자 ...|

타임리프에서 문자와 표현식은 분리되어 있으나
'+'를 통해 더해서 사용하는 것이 아닌 리터럴 대체 문법을 사용할 수 있음

ex)
th:onclik="location.href=' + '\' + @{/abc/def/hi} + '\'"
<=>
th:onclick="|location.href='@{/abc/def/hi}'|"

🟦 반복 출력 // th:each

th:each="변수 : ${컬렉션 데이터}"

오른쪽 컬렉션(${컬렉션 데이터})의 값을 하나씩 '변수'에 담아 태그를 반복

List, 배열, java.util.Iterable, java.util.Enumeration을 구현한 모든 객체를 반복에 사용 가능
Map도 사용 가능, 이 때 변수에 담기는 값은 Map.Entry

모델에 포함된 컬렉션 데이터가 변수에 하나씩 포함되고, 반복문 안에서 변수를 사용할 수 있음

ex)
<tr th:each="member : ${members}">
    <td></td>
    <!--  -->
</tr>

컬렉션의 수 만큼 <tr>..</tr>이 하위 태그를 포함해서 생성됨

🔹 반복 상태 유지 기능
두 번째 파라미터를 설정해서 반복의 상태를 확인할 수 있음

  • index : 0부터 시작하는 값
  • count : 1부터 시작하는 값
  • size : 전체 사이즈
  • even , odd : 홀수, 짝수 여부( boolean )
  • first , last :처음, 마지막 여부( boolean )
  • current : 현재 객체
ex)
<tr th:each="member, memberStat : ${members}">
    <td>
      <span th:text="${memberStat.count}"></span>
  	</td>
    <!--  -->
</tr>

두 번째 파라미터인 memberStat은 생략 가능
두 번째 파라미터를 생략하면 지정 변수명(member) + Stat으로 설정됨

🟦 변수 표현식 // ${...}

SpringEL
변수 표현식에서는 스프링이 제공하는 표현식인 스프링 EL을 사용함

  • Object
    member.name ➡️ user.getMembername()
    member['membername'] ➡️ user.getMembername() //동적으로 꺼내기 쉬움
    member.getMembername ➡️ member의 getMembername() 직접 호출

  • List
    member[0].membername ➡️ List에서 첫 번째 회원을 찾아 프로퍼티 접근
    member[0]['membername'] ➡️ list.get(0).getMembername()
    member[0].getMembername() ➡️ List에서 첫 번째 회원을 찾고 메서드 직접 호출

  • Map
    memberMap['A'].membername : ➡️ Map에서 A를 찾아 프로퍼티 접근
    memberMap['A']['membername'] ➡️ map.get("A").getMembername()
    memberMap['A'].getMembername() ➡️ Map에서 A를 찾고 메서드 직접 호출

  • 지역 변수
    th:with를 사용하여 지역 변수 선언해서 사용 가능

ex)
<div th:with="name=${member.name}">
  <li th:text="${name.membername}"></li>
</div>

모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있음

ex)
<td th:text="${member.name}">이름</td>

🟦 연산

  • 비교연산
    > : gr
    < : lt
    >= : ge
    <= : le
    ! : not
    == : eq
    != : neq, ne
  • 조건식
    "? :"
  • Elvis 연산자
    "${data}?:'data null일 때 출력문'" // data가 not null이라면 data 출력
  • No-Operation
    _ -> 아무 동작 하지 않음, 타임리프 태그 렌더링 안하는 것과 동일

🟦 타임리프가 제공하는 객체들

🔷 기본 객체

  • ${#request}
  • ${#response}
  • ${#session}
  • ${#servletContext}
  • ${#locale}
    ( ${#locale}을 제외한 나머지는 스프링 부트 3.0부터 제공하지 않음 )

🔷 편의 객체

#request는 HttpServletRequest 객체가 그대로 제공됨
-> 데이터를 조회하려면 request.getParameter("data")와 같이 접근하기 불편함

  • HTTP 요청 파라미터 접근: param
    ex) ${param.paramData}
  • HTTP 세션 접근: session
    ex) ${session.sessionData}
  • 스프링 빈 접근: @

🔷 유틸리티 객체와 날짜

  • #message : 메시지, 국제화 처리
  • #uris : URI escape 지원
  • #dates : java.util.Date 서식 지원
  • #calendars : java.util.Calendar 서식 지원
  • #temporals : 자바8 날짜 서식 지원
    (thymeleaf-extras-java8time 라이브러리 필요)
  • #numbers : 숫자 서식 지원
  • #strings : 문자 관련 편의 기능
  • #objects : 객체 관련 기능 제공
  • #bools : boolean 관련 기능 제공
  • #arrays : 배열 관련 기능 제공
  • #lists , #sets , #maps : 컬렉션 관련 기능 제공
  • #ids : 아이디 처리 관련 기능 제공
- 필요할때 찾아서 사용
타임리프 유틸리티 객체
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utilityobjects
유틸리티 객체 예시
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expressionutility-objects

🟦 내용 변경 // th:text, th:utext

내용의 값을 th:text의 값으로 변경

ex)
<td th:text="${member.name}">내용</td>

'내용'을 ${member.name}의 값으로 변경

위와 달리 직접 출력하는 방법도 존재
[[${member.name]]

<li>멤버 이름은 = [[${member.name]]</li>

멤버 이름은 = member.name값 으로 출력됨

th:text, [[...]]는 escape 제공
(escape : '<' 같은 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것)
< ➡️ & l t ;
> ➡️ & g t ;

escape 기능 사용하지 않으려면
th:text ➡️ th:utext
[[...]] ➡️ [(...)]
변경하여 사용

단 unescape는 꼭 필요한 상황에만 사용
escape 처리가 되어있지 않으면 HTML이 정상 렌더링 되지 않는 문제가 발생할 수 있음
의도적으로 따로 지원하는 경우에만 사용할 것


🟦 조건부 평가

  • if
  • unless
  • switch
  <td th:switch=" ">
  	<span th:case=" "> </span>
  	<!-- -->
    <span th:case="*"> </span> // 만족하는 조건이 없을 때 * 사용

th:if(unless)=" " 조건이 true여야 렌더링, false면 해당 태그 소멸


🟦 주석

  • 표준 HTML 주석
    자바스크립트 표준 HTML 주석은 타임리프가 렌더링하지 않음
    <!-- -->

  • 타임리프 파서 주석
    타임리프의 진짜 주석, 렌더링에서 주석 부분을 제거
    <!--/* */-->

  • 타임리프 프로토타입 주석
    HTML 파일을 웹 브라우저에서 열어보면 HTML 주석이기 때문에 웹브라우저가 렌더링하지 않음
    타임리프 렌더링 과정을 거치면 해당 주석 부분이 정상 렌더링됨
    ➡️ 타임리프를 렌더링 한 경우에만 보이는 부분
    <!--/*/ /*/-->


🟦 블록

<th:block>
타임리프는 태그가 아닌 속성으로 동작하지만 해결하기 어려운 부분을 해결하기 위한
타임리프의 유일한 자체 태그

ex) <div> 태그를 2개씩 짝지어 반복으로 사용하는 경우 -> each만으로 해결 힘듦
<th:block th:each="member : ${members}">
  <div>
    ...
  </div>
  <div>
    ...
  </div>
</th:block>

🟦 자바스크립트 인라인

자바스크립트에서 타임리프를 편리하게 사용할 수 있도록 제공하는 기능

<script th:inline="javascript">

🔷 자바스크립트 인라인을 사용하지 않는 경우

  • 문자 -> 개발자가 직접 " "를 달아줘야 함
    ex)var membername = "[[${member.membername]]";

  • natural templates를 사용하기 어려움
    ex) var first_name = /[[$member.first_name]]/ "test info";
    원하는 결과 : first_name = member.first_name값;

    동작 결과 : first_name = "test info";
    주석이 무시됨

  • 객체 -> 제대로 동작 안함
    ex) var member = [[${member}]];
    원하는 결과 : var member = {"membername":"이름", "age":나이};

    동작 결과 : var member = 클래스명.Member(membername=이름, age=나이);
    member객체.toString()이 호출된 결과를 가져옴

🔷 자바스크립트 인라인을 사용하면

  • 문자 타입인 경우 ""를 포함해줌
    자바스크립트에서 문제가 될 수 있는 문자가 포함되어 있다면 escape 처리

  • natural tempates 기능 제공

  • 객체를 JSON으로 자동 변환

  • each 지원함

    ex)
    [# th:each="member : ${members}"]
    var member[[${member.index}]] = [[${member}]];
    [/]

🟦 템플릿 조각 & 레이아웃

웹 페이지를 개발할 때 여러 페이지에서 함께 사용하는 공통 영역들이 존재할 것

이러한 부분들을 복사하여 각각의 페이지에서 사용한다면 유지보수에 매우 비효율적임

➡️ 타임리프는 이러한 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원함

🔷 템플릿 각

th:fragment="이름"
"~{파일 경로 :: 이름}"
해당 경로의 템플릿에 있는 th:fragment="이름" 이라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미

  • th:insert
    해당 태그 안에 fragment를 삽입
  • th:replace
    해당 태그를 fragment로 교체
ex)
파일1 A/B/footer.html
<footer th:fragment="fr"> 푸터 </footer>

파일2 A/B/main.html
<div th:repalce="~{A/B/footer :: fr}"></div>

템플릿 조각을 사용하는 코드가 단순하면 ~{ } 생략 가능
-><div th:replace="A/B/footer :: fr"></div>
  • 파라미터 사용
    파라미터를 전달해서 동적으로 조각을 렌더링할 수 있음
ex)
파일1 A/B/footer.html
<footer th:fragment="fr (param1, param2)"> 
	<p th:text="${param1}"></p>
    <p th:text="${param2}"></p>
</footer>

파일2 A/B/main.html
<div th:insert="~{A/B/footer :: fr ('data1', 'data2')}"></div>

🔷 템플릿 레이아웃

일부 코드 조각을 가져와서 사용하는 것이 아닌 코드 조각을 레이아웃에 넘겨서 사용하는 방법

공통 정보들을 한 곳에 모아두고, 각 페이지마다 공통 정보에 필요한 정보들을 추가해서 사용 가능

th:fragment="이름(태그1, 태그2)"
"경로 :: 이름(~{::태그1}, ~{::태그2})..."
해당 경로의 페이지의 '태그1' 태그들과 '태그2' 태그들을 전달

파일1 A/B/main.html
<head th:fragment="common(title,links)">
  	<!-- 바뀌는 부분 -->
 	<title th:replace="${title}">Title</title>
  
 	<!-- 바뀌지 않는 부분 -->
 	<link rel="stylesheet" th:href="@{/URL0}">
  
 	<!-- 바뀌는 부분 -->
 	<th:block th:replace="${links}"/>
</head>

파일2 A/B/layout.html
<head th:replace="A/B/layout :: common(~{::title},~{::link})">
    <title>타 이 틀</title>
    <link rel="stylesheet" th:href="@{/URL1}">
    <link rel="stylesheet" th:href="@{/URL2}">
</head>

파일1은 다음과 같이 렌더링 됨
<head>
<title>타 이 틀</title>
  
	<link rel="stylesheet" href="/URL0">
  	
  	<link rel="stylesheet" href="/URL1">
    <link rel="stylesheet" href="/URL2">
</head>  

html태그에 th:fragment 속성 정의해서 사용 가능

파일1 A/B/main.html
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
  	<!-- 바뀌는 부분 -->
 	<title th:replace="${title}">Title</title>
</head>
<body>
  <div th:replace="${content}">
      <p>Content</p>
  </div>
  <p> Hello </p>
</body>
</html>

파일2 A/B/layout.html
<html th:replace="~{A/B/main :: layout(~{::title}, ~{::section})}" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>타 이 틀</title>
</head>
<body>
  <section>
    <p>컨 텐 츠</p>
  </section>
</body>
</html>

파일1은 다음과 같이 렌더링됨
<html>
<head>
 	<title>타 이 틀</title>
</head>
<body>
  <section>
      <p>콘 텐 츠</p>
  </section>
  <p> Hello </p>
</body>
</html>

장점 : 레이아웃만 바꾸어서 여러 페이지를 한꺼번에 수정 가능
단점 : 체계적으로 관리해야 함


🟦 리다이렉트

🔹 redirect/...
스프링은 redirect/... 형식으로 편리하게 리다이렉트를 사용할 수 있도록 지원함

ex)
@PostMapping("/URL")
public String 메서드명(//...) {
	//...
    return "redirect:/..."
}

인프런 스프링 MVC 1편, 2편 - 백엔드 웹 개발 핵심 기술 (김영한) 참조

profile
DDeo99

0개의 댓글