しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Knockout.js の if と visible は似ているようで違うので注意

Knockout.js の if と visible は見た目的には要素の表示・非表示を切り替えるので大差ないですが、DOM 的には大きく仕組みが異なっています。

簡単な例で挙動の差を見ていきます。

<span data-bind="if: flag">ほげほげ</span>
↓ タグの中身が空っぽの状態になる
<span></span>

if の場合は flag が true になったタイミングで、Knockout.js が初回読み込み時に複製しておいたノードを span の下に追加するようになっています。

<span data-bind="visible: flag">ほげほげ</span>
↓ 要素はそのままだけど、スタイルで非表示にしている
<span style="display: none;">ほげほげ</span>

visible の場合は flag が true になったタイミングで、Knockout.js が display: none の指定を削除します。

まとめると実行時にノードを追加・削除するのか、それとも表示・非表示を CSS で切り替えるかという差があるということになります。if を使うと DOM ツリーに存在しないタイミングが発生するのと、ノードを複製して保持しているのでコンテンツ次第ではメモリ使用量に差が出そうです。

<!-- ノードを複製しているのでメモリを少し食いそう -->
<div data-bind="if: flag">
<!-- とても長い HTML -->
</div>

<!-- こっちのが省メモリ、多分 -->
<div data-bind="visible: flag">
<!-- とても長い HTML -->
</div>

割と現実的な問題として、foreach の afterAdd を使っている場合にちょっとした差異が発生します。

ありがちな項目を追加したタイミングでアニメーションを行う実装を例に見ていきます。

<div data-bind="if: items().length > 0">
    <ul data-bind="foreach:{ data: items, afterAdd: fadeIn }">
        <li data-bind="text: $data"></li>
    </ul>
</div>

<div data-bind="visible: items().length > 0">
    <ul data-bind="foreach: { data: items, afterAdd: fadeIn }">
        <li data-bind="text: $data"></li>
    </ul>
</div>

HTML 的には if か visible を使っているという差しかありませんが、実行してみると初回に差が発生します。

if を使った方では初回にフェードインアニメーションが動作しませんでした。どうやら if を使った場合には初回に afterAdd が発火されないようなので、afterAdd を使う場合には visible で制御するようにしましょう。

ちなみに F12 開発ツールで見た時の DOM ツリーは以下のような感じです。

f:id:shiba-yan:20140624182938p:plain

しかし、if の場合には仮想要素を使ってフローを書くことが出来るので、使い分けが重要かなと思います。

<!-- ko if: flag -->
<p>ほげほげ</p>
<!-- /ko -->
↓ コメントのみ残る
<!-- ko if: flag -->
<!-- /ko -->

この場合 p タグにマージンやパディングなどのスタイルが指定されていても、要素自体が消えるので変な空白などが残らなくなります。