🐳

Thymeleafでth:include, th:replace, th:insert, th:fragmentの組み合わせ別使用法

2024/12/31に公開

前置き

追記(2019/6/29)

th:includeはthymeleaf3.0から非推奨になっています。コメントにて指摘いただきました。ありがとうございます。
現時点では廃止になるような記載はありませんが、th:includeは使用しないほうがよさそうです。

Thymeleafって何?

javaのフレームワークであるSpringをやろう!とした場合、おそらくよく使われるであろうViewテンプレートがThymeleafです。

このThymeleafではパーツごとにテンプレートファイルを作成することもでき、th:includeやth:replaceやth:insertを使って読み込みます。
初めて使用していたときはth:include? th:replace? th:insert? どっち使えばいいんだ?とかなっていたのでまとめておきます。

環境

  • java : 1.8
  • Thymeleaf : 3.0.11

前提知識

th:include, th:replace, th:insertの基本的な使い方

次のような部分Viewがあったとして

part.html
<div id="children">
  part
</div>

includeの場合は小要素として追加されます

include.html
<div id="parent" th:include="part">text</div>
<!--結果-->
<div id="parent">
  <div id="children">
    part
  </div>
</div>

replaceの場合は要素が入れ替わります

replace.html
<div id="parent" th:replace="part">text</div>
<!--結果-->
<div id="children">
  part
</div>

insertの場合は要素が挿入されます. includeと同じ結果になっていますね。

insert.html
<div id="parent" th:insert="part">text</div>
<!--結果-->
<div id="parent">
  <div id="children">
    part
  </div>
</div>

テンプレートの指定方法には以下の方法があります

  • templateName
  • templateName::selector
  • templateName::fragmentName

templateNameはテンプレートディレクトリ群からのパスになります。下記の場合components/partになります。

 templates
   └── components
         └── part.html

またthis::fragmentName::fragmentNameという指定方法もあり、thisは自身のテンプレートを指します。

htmlに複数のパーツを定義するth:fragment

定義例

fragment.html
<div id="fragment1" th:fragment="template1()">
  <span>template1</span>
</div>
<div id="fragment2" th:fragment="template2(text)">
  引数も渡せます
  <span th:text="${text}"></span>
</div>

本題

上記のfragmentを使わない読み込みに関しては以下の動作でした。
th:includeとth:insertに違いはありませんね。

  • th:include -> 読み込んだhtmlを小要素に追加
  • th:replace -> 読み込んだhtmlと要素が入れ替わる
  • th:insert -> 読み込んだhtmlを小要素に追加

fragmentを使用する場合には注意が必要です。使用するfragmentは上記の定義例とします。

th:includeとth:fragment

include.html
<div id="include" th:include="fragment::template1()">
  <span>text</span>
</div>
<!--結果-->
<div id="include">
  <span>template1</span>
</div>

th:fragmentの内部が渡されます。th:includeした際のid等の要素も残ります。
なので、下記のような使い方ができます。

<div th:if=${isDisplay} th:include="fragment::template1()"></div>

パーツ自体の表示は親側のテンプレートで行うほうが自然です。適用するclassも反映されるのでうまく使えばコンポーネントとして適切に使い回せます。

th:replaceとth:fragment

replace.html
<div id="replace" th:replace="fragment::template1()"></div>
<!--結果-->
<div id="fragment1">
  <span>template1</span>
</div>

注意する点はth:fragmentが定義されている箇所と入れ替わる点です。th:replace側で定義したidやifは判定されません。

th:insertとth:fragment

insert.html
<div id="insert" th:insert="fragment::template1()"></div>
<!--結果-->
<div id="insert">
  <div id="fragment1">
    <span>template1</span>
  </div>
</div>

th:fragmentの要素が挿入されます。
th:includeと同様にidが残るので、th:ifなどが利用できます。

まとめ

th:fragmentを使用した場合、th:includeとth:replaceやth:insertで出力結果が異なります。
classやth:if等の要素を親側で使用したい場合は、th:insertを使用したほうが簡潔になります。
th:includeは3.0から非推奨になっています。これを見てわかる通り、fragmentを使用した時の挙動が分かりづらいからでしょうか。

th:replaceはどんなときに使う?

共通のテンプレートを作成するときに使えそう。

fragment.html
<head th:fragment="head(link, script)">
  <meta content="text/html; charset=UTF-8"/>
  <link href="common-link"/>
  <script src="common-src"/>
 
  <th:block th:if="${link != null}" th:include="${link}"></th:block>
  
  <th:block th:if="${script != null}" th:include="${script}"></th:block>
</head>
<!-- 使用側 -->
<html>
<head th:replace="fragment::head(~{head/link}, ~{head/script})">
  <link href="xxx"/>
  <link href="yyy"/>
  <script src="xxx"/>
  <script src="yyy"/>
</head>
<body>
  content
</body>
</html>
<!--結果-->
<html>
<head>
  <meta content="text/html; charset=UTF-8"/>
  <link href="common-link"/>
  <script src="common-src"/>

  <link href="xxx"/>
  <link href="yyy"/>

  <script src="xxx"/>
  <script src="yyy"/>
</head>
<body>
  content
</body>
</html>

~{}はフラグメント式と呼ばれる記述方法です。~{}を使うことで実際のタグの内容を引数で渡すことができます。
th:replaceを使用している箇所がheadタグなので、fragmentの引数にheadタグの内容を渡していることが見た目でわかります。
また、th:replaceを使用することでfragmentのタグをheadに強制しています。知らない人が見てもheadタグの内容を書けばいいのかとなります。

includeだとfragmentのタグはなんでもいいので、「divの中にmetaタグが入っている、、、」とかならなくて済みそうです。そもそもhtmlファイルなのでthymeleafの解釈なしでもhtmlとして正しい文法であるべきですよね。

細かいことを気にしたくない人向け

th:blockを使用することで結果として適用範囲を同じにしてしまえばいいと思います。

<th:block th:fragment="template()">
  <div>
    <span>template</span>
  </div>
</th:block>

参考サイト

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html

Discussion