타임 리프 기본 기능
타임 리프
- 백엔서드 동적으로 HTML을 만들어주는 JSP와 같은 템플릿 엔진
특징
- 네츄럴 템플릿
- 타임리프는 작성한 파일은 순수 HTML을 유지 한다. 타임리프 파일을 그대로 열어도 정상적으로 브라우저에서 렌더링 된다
- JSP와 같은 템플릿 엔진은 브라우저에서 열면 해당 JSP 코드를 브라우저가 해석하지 못해 HTML이 깨진다
- 스프링 통합 지원
- 스프링과 자연스러운 통합되고, 스프링의 다양한 편리한 기능을 사용할 수 있다
사용 선언
<html xmlns:th="http://www.thymeleaf.org">
텍스트 출력 - text, utext
- text
<li>th:text 사용 <span th:text="${data}"></span></li> // html 엘리먼트에 삽입
<li>[[${data}]]</li> // 인라인 텍스트
- utext (Unescape text)
-
HTML에서 사용하는 특수 문자를 문자로 볼 수 있게 처리하는걸 Escape라고 함 기본적으 text는 excape 처리 함
-
utext는 태그를 문자가 아닌 태그로 처리할 수 있게 함 즉 escape 처리를 하지않은 unescape임
<li>th:text 사용 <span th:utext="${data}"></span></li> // utext
<li>[(${data})]</li> // [()]
변수 - Spring El
- Object
<span th:text="${user.username}"></span> // 필드 접근
<span th:text="${user.getUsername()}"></span> // 게터 접근
<span th:text="${user['username']}"></span> // 자바스크립트처럼 프로퍼티 접근
- List
- 인덱스에 접근 할 수 있다
<span th:text="${users[0].username}"></span>
<span th:text="${users[0].getUsername()}"></span>
<span th:text="${users[0]['username']}"></span>
- Map
-
자바스크립트 객체 프로퍼티 접근 처럼 할 수 있다
<span th:text="${userMap['userA'].username}"></span>
<span th:text="${userMap['userA'].getUsername()}"></span>
<span th:text="${userMap['userA']['username']}"></span>
- 지역 변수
-
th:with
를 사용하면 지역변수는 선언된 태그안에서만 사용 가능하다
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
타임리프에서 제공하는 기본 객체들
- 식 기본 객체 (Expression Basic Objects)
<li>request = <span th:text="${#request}"></span></li>
<li>request = <span th:text="${#response}"></span></li>
<li>request = <span th:text="${#session}"></span></li>
<li>request = <span th:text="${#servletContext}"></span></li>
<li>request = <span th:text="${#locale}"></span></li>
- 편의 객체
<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> // 빈 접근
유틸리티 객체와 날짜
- 너무 많다 문서 보자
- 타임리프 유틸리티 객체
- 유틸리티 객체 예시
URL 링크
- 타임리프에서 URL 생성할 때 @{...} 문법을 사용하면 된다
- 단순한 URL
@{/hello}
→ /hello
- 쿼리 파라미터
@{/hello(param1=${param1}, param2='babo')}
→ /hello?param1=data1¶m2=babo
- 경로 변수
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
→ /hello/data1/data2
- 경로 변수 + 쿼리 파라미터
@{/hello/{param1}(param1=${param1}, param2=${param2})}
→ /hello/data1?param2=data
리터럴
- 리터럴은 변수가 아닌 그 값 자체를 리터럴이라고 함
- 타임리프에는 4가지의 리터럴 값이 있음
- 문자 :
'hello'
- 숫자 :
10
- 불린 :
true
, false
- null :
null
- 주의할 점은 타임리프에서 문자는
'
(작음 따옴표)로 깜싸야 함
- but 공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해 작은 따옴표 생략
- ex)
<span th:text="hello">
- but 중간에 공백이 있으면 하나의 토큰으로 인식 되지 않아 작은 따옴표로 감싸야 한다
- ex)
<span th:text="hello world">
→ <span th:text="'hello world'">
- 리터럴 대체
- 리터럴 대체 문법을 사용하면 작은 따옴표 없이도 편하게 사용가능 하다
- ex)
<span th:text="|hello ${data}|">
- 이게 가장 깔끔하게 실수를 방지하는기 좋은거 같다 내 개인적인 생각에는 모든 텍스트에 이걸 적용하는게 좋을거 같다
연산
- 산술 연산
>
(gt)
<
(lt)
>=
(ge)
<=
(le)
!
(not)
==
(eq)
!=
(neq, ne)
- 삼항 연산
- ex)
(10 % 2 == 0) : '짝수' : '홀수'
- Elvis 연산자
- ex)
${data}?: '데이터가 없습니다'
data
가 null
이아니면 data
를 출력하고 null
이면 '데이터가 없습니다'
출력
- 자바스크립트
data || '데이터가 없습니다'
버전 인거 같다.
- No - Operation
- ex)
${data}?:_
data
가 있으면 data
출력 없으면 아무것도 안함
_
언더바 임
속성값 설정
- 타임리프 속성
- 타임리프 속성은
th:*
를 이용해 속성을 지정함
th:*
속성을 지정하면 기존 속성을 지정한 속성으로 대체 한다. 기존 속성이 없다면 새로 만든다
- ex)
<input name="mock" th:name="userA"/>
→ <input name="userA" />
- 속성 추가
th:attrappend
- html의 기존 속성 뒤에 추가하는 방식이다
- 주의점은 기존 속성과 겹치치지 않게 속성 값 앞에 띄워쓰기를 해줘야 한다
- ex)
<input type="text" class="text" th:attrappend="class=' large'" />
→ <input type="text" class="text" large />
th:attrprepend
- html의 기존 속성 앞에 추가하는 방식이다
- 마찬가지로 기존 속성값과 겹치지 않게 뒤에 띄워쓰기를 해줘야 한다
- ex)
<input type="text" class="text" th:attrprepend="class='large '" />
→ <input type="text" class="large text" />
th:classappend
- ex)
<input type="text" class="text" th:classappend="large">
→ <input type="text" class="large text" />
- 클래스를 추가할 일이 있으면 위에 속성보다 그냥 이거 쓰자 실수를 할 확률도 줄고 무엇보다 보기도 편하고 간편하다
- chcked 처리
<input type="checkbox" name="active" th:checked="true">
반복
<tr th:each="user: ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
</tr>
- 타임리프는 반복 루프에 대한 상태를 확인할 수 있음
index
: 0부터 시작하는 값
count
: 1부터 시작하는 값
size
: 전체 사이즈
event
, odd
: 홀수 짝수 여부 boolean
값으로 제공
first
, last
: 처음, 마지막 여부 boolean
값으로 제공
current
: 현재 객체
<tr th:each="user, stat: ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td th:text="${stat.index}"></td>
</tr>
조건부 평가
- if, unless
-
th:if
는 조건을 만족하면 해당 태그를 렌더링 한다
-
th:unless
는 th:if
의 부정이라고 생각하면 된다
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td>
<td th:text="${user.username}"></td>
<td>
<span th:text="${user.age}"></span>
<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>
</td>
</tr>
- switch
-
switch
는 case
조건에 부합되는 태그만 렌더링 한다
<tr th:each="user, userStat : ${users}">
<td th:text="${userStat.count}"></td>
<td th:text="${user.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>
주석
- 타임리프 파서 주석
-
타임리프 기본 주석
-
타임리프 파서 주석을 사용할 경우 아예 렌더링에서 제거 함
<!--/*-->
<span th:text="${data}"></span>
<!--*/-->
<!--/* [[${data}]] */-->
- 프로토타입 주석
-
HTML 파일을 통짜로 열 경우 주석처리가 되지만
-
타임리프 렌더링을 거치면 정상 렌더링 된다
-
강사님이 쓸일이 없다는데 내 생각도 별로 쓸 때까 생각이 안난다
<!--/*/
<span th:text="${data}"></span>
/*/-->
블록
- 타임리프의 유일한 자체 태그이다
- 타임리프 특성상 태그안에
th:*
를 정의해서 사용하는데 태그 없이 사용하기 좋다
<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>
const username = [[${user.username}]];
const age = [[${user.age}]];
const user = "[[${user}]]";
</script>
- 문자열 같은 경우 따로 자바스크립트 문법에 맞게
""
쌍 따옴표로 감싸야 한다. 깜빡하고 쌍따옴표로 안 감싼다면 바로 자바스크립트 문법 오류 가 난다 굉장히 불편하다
- 또한 객체의 경우
toString
으로 변환되서 나오기 때문 아예 사용이 불가능하다
- 인라인 사용 후
<script th:inline="javascript">
const username = [[${user.username}]];
const age = [[${user.age}]];
const user = [[${user}]];
</script>
- 인라인을 사용하면 문자열 같은 경우 자동적으로 자바스크립트 문법에 맞게 변환 시켜준다
- 객체같은 경우 자바스크립트 객체(JSON)로 변환 시켜준다
- 또한 자바스크립트에서 문제가 될 수 있는 문자가 있으면 이스케이프 처리도 해준다
- 인라인 each
<script th:inline="javascript">
[# th:each="user, stat: ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
- 자바스크립트에서 타임리프 반복문을 사용할 때 사용하면 된다
템플릿 조각
- 웹 페이지를 개발할 때 공통 영역이 많이 있다. 공통 된 부분의 코드를 복사해서 사용하면 유지보수에 어려움에 있다. 이런 문제를 해결하기 위해 타임리프는 템플릿 조각과, 레이아웃 기능을 지원한다
- 템플릿 선언
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<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>
- 조각 파일을 만들면 사용하는 곳에서 함수 처럼 불러다 사용할 수 있다
- 함수 처럼 파라미터도 적용할 수 있다
- 템플릿 사용
<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 ('data1', 'data2')"></div>
템플릿 레이 아웃
- 이전에는 일부 코드 조각을 만들어 함수 처럼 만들어 사용했다면 레이 아웃을 만들어 사용할 수 있다
- 레이아웃 만들기
<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>
common_header
매개변수로 받은 title
과 link
- 레이아웃 사용하기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<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>
th:replace
를 이용하여 템플릿을 사용한다 인수로 받을때는 교체되는 태그를 ~{::**}
방식으로 선언한다 인수의 이름과 레이아웃의 교체되는 태그가 같아야 한다
Author And Source
이 문제에 관하여(타임 리프 기본 기능), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@senglo/타임-리프
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
- 타임리프는 작성한 파일은 순수 HTML을 유지 한다. 타임리프 파일을 그대로 열어도 정상적으로 브라우저에서 렌더링 된다
- JSP와 같은 템플릿 엔진은 브라우저에서 열면 해당 JSP 코드를 브라우저가 해석하지 못해 HTML이 깨진다
- 스프링과 자연스러운 통합되고, 스프링의 다양한 편리한 기능을 사용할 수 있다
<html xmlns:th="http://www.thymeleaf.org">
<li>th:text 사용 <span th:text="${data}"></span></li> // html 엘리먼트에 삽입
<li>[[${data}]]</li> // 인라인 텍스트
-
HTML에서 사용하는 특수 문자를 문자로 볼 수 있게 처리하는걸 Escape라고 함 기본적으 text는 excape 처리 함
-
utext는 태그를 문자가 아닌 태그로 처리할 수 있게 함 즉 escape 처리를 하지않은 unescape임
<li>th:text 사용 <span th:utext="${data}"></span></li> // utext <li>[(${data})]</li> // [()]
<span th:text="${user.username}"></span> // 필드 접근
<span th:text="${user.getUsername()}"></span> // 게터 접근
<span th:text="${user['username']}"></span> // 자바스크립트처럼 프로퍼티 접근
- 인덱스에 접근 할 수 있다
<span th:text="${users[0].username}"></span>
<span th:text="${users[0].getUsername()}"></span>
<span th:text="${users[0]['username']}"></span>
-
자바스크립트 객체 프로퍼티 접근 처럼 할 수 있다
<span th:text="${userMap['userA'].username}"></span> <span th:text="${userMap['userA'].getUsername()}"></span> <span th:text="${userMap['userA']['username']}"></span>
-
th:with
를 사용하면 지역변수는 선언된 태그안에서만 사용 가능하다<div th:with="first=${users[0]}"> <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p> </div>
<li>request = <span th:text="${#request}"></span></li>
<li>request = <span th:text="${#response}"></span></li>
<li>request = <span th:text="${#session}"></span></li>
<li>request = <span th:text="${#servletContext}"></span></li>
<li>request = <span th:text="${#locale}"></span></li>
<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> // 빈 접근
@{/hello}
→ /hello
@{/hello(param1=${param1}, param2='babo')}
→ /hello?param1=data1¶m2=babo
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
→ /hello/data1/data2
@{/hello/{param1}(param1=${param1}, param2=${param2})}
→ /hello/data1?param2=data
- 문자 :
'hello'
- 숫자 :
10
- 불린 :
true
,false
- null :
null
'
(작음 따옴표)로 깜싸야 함- but 공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해 작은 따옴표 생략
- ex)
<span th:text="hello">
- ex)
- but 중간에 공백이 있으면 하나의 토큰으로 인식 되지 않아 작은 따옴표로 감싸야 한다
- ex)
<span th:text="hello world">
→<span th:text="'hello world'">
- ex)
- 리터럴 대체 문법을 사용하면 작은 따옴표 없이도 편하게 사용가능 하다
- ex)
<span th:text="|hello ${data}|">
- 이게 가장 깔끔하게 실수를 방지하는기 좋은거 같다 내 개인적인 생각에는 모든 텍스트에 이걸 적용하는게 좋을거 같다
- ex)
>
(gt)<
(lt)>=
(ge)<=
(le)!
(not)==
(eq)!=
(neq, ne)
- ex)
(10 % 2 == 0) : '짝수' : '홀수'
- ex)
${data}?: '데이터가 없습니다'
data
가null
이아니면data
를 출력하고null
이면'데이터가 없습니다'
출력- 자바스크립트
data || '데이터가 없습니다'
버전 인거 같다.
- ex)
${data}?:_
data
가 있으면data
출력 없으면 아무것도 안함_
언더바 임
- 타임리프 속성은
th:*
를 이용해 속성을 지정함 th:*
속성을 지정하면 기존 속성을 지정한 속성으로 대체 한다. 기존 속성이 없다면 새로 만든다- ex)
<input name="mock" th:name="userA"/>
→<input name="userA" />
- ex)
th:attrappend
- html의 기존 속성 뒤에 추가하는 방식이다
- 주의점은 기존 속성과 겹치치지 않게 속성 값 앞에 띄워쓰기를 해줘야 한다
- ex)
<input type="text" class="text" th:attrappend="class=' large'" />
→<input type="text" class="text" large />
th:attrprepend
- html의 기존 속성 앞에 추가하는 방식이다
- 마찬가지로 기존 속성값과 겹치지 않게 뒤에 띄워쓰기를 해줘야 한다
- ex)
<input type="text" class="text" th:attrprepend="class='large '" />
→<input type="text" class="large text" />
th:classappend
- ex)
<input type="text" class="text" th:classappend="large">
→<input type="text" class="large text" />
- 클래스를 추가할 일이 있으면 위에 속성보다 그냥 이거 쓰자 실수를 할 확률도 줄고 무엇보다 보기도 편하고 간편하다
- ex)
<input type="checkbox" name="active" th:checked="true">
<tr th:each="user: ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
</tr>
index
: 0부터 시작하는 값count
: 1부터 시작하는 값size
: 전체 사이즈event
, odd
: 홀수 짝수 여부 boolean
값으로 제공first
, last
: 처음, 마지막 여부 boolean
값으로 제공current
: 현재 객체<tr th:each="user, stat: ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td th:text="${stat.index}"></td>
</tr>
-
th:if
는 조건을 만족하면 해당 태그를 렌더링 한다 -
th:unless
는th:if
의 부정이라고 생각하면 된다<tr th:each="user, userStat : ${users}"> <td th:text="${userStat.count}"></td> <td th:text="${user.username}"></td> <td> <span th:text="${user.age}"></span> <span th:text="'미성년자'" th:if="${user.age lt 20}"></span> <span th:text="'미성년자'" th:unless="${user.age ge 20}"></span> </td> </tr>
-
switch
는case
조건에 부합되는 태그만 렌더링 한다<tr th:each="user, userStat : ${users}"> <td th:text="${userStat.count}"></td> <td th:text="${user.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>
-
타임리프 기본 주석
-
타임리프 파서 주석을 사용할 경우 아예 렌더링에서 제거 함
<!--/*--> <span th:text="${data}"></span> <!--*/--> <!--/* [[${data}]] */-->
-
HTML 파일을 통짜로 열 경우 주석처리가 되지만
-
타임리프 렌더링을 거치면 정상 렌더링 된다
-
강사님이 쓸일이 없다는데 내 생각도 별로 쓸 때까 생각이 안난다
<!--/*/ <span th:text="${data}"></span> /*/-->
th:*
를 정의해서 사용하는데 태그 없이 사용하기 좋다<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>
const username = [[${user.username}]];
const age = [[${user.age}]];
const user = "[[${user}]]";
</script>
- 문자열 같은 경우 따로 자바스크립트 문법에 맞게
""
쌍 따옴표로 감싸야 한다. 깜빡하고 쌍따옴표로 안 감싼다면 바로 자바스크립트 문법 오류 가 난다 굉장히 불편하다 - 또한 객체의 경우
toString
으로 변환되서 나오기 때문 아예 사용이 불가능하다
<script th:inline="javascript">
const username = [[${user.username}]];
const age = [[${user.age}]];
const user = [[${user}]];
</script>
- 인라인을 사용하면 문자열 같은 경우 자동적으로 자바스크립트 문법에 맞게 변환 시켜준다
- 객체같은 경우 자바스크립트 객체(JSON)로 변환 시켜준다
- 또한 자바스크립트에서 문제가 될 수 있는 문자가 있으면 이스케이프 처리도 해준다
<script th:inline="javascript">
[# th:each="user, stat: ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
</script>
- 자바스크립트에서 타임리프 반복문을 사용할 때 사용하면 된다
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<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>
- 조각 파일을 만들면 사용하는 곳에서 함수 처럼 불러다 사용할 수 있다
- 함수 처럼 파라미터도 적용할 수 있다
<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 ('data1', 'data2')"></div>
<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>
common_header
매개변수로 받은title
과link
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<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>
th:replace
를 이용하여 템플릿을 사용한다 인수로 받을때는 교체되는 태그를~{::**}
방식으로 선언한다 인수의 이름과 레이아웃의 교체되는 태그가 같아야 한다
Author And Source
이 문제에 관하여(타임 리프 기본 기능), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@senglo/타임-리프저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)