*ngForのtrackByについて

*ngForディレクティブでのtrackByオプションに対して、以下のような理解を持っています。

  • 回すデータを一意に特定できる値を返す関数を食わせることで、パフォーマンスの最適化を図る

しかし、IDを持たないなどの理由で、回すデータを一意に特定する方法がない場合があると思います。({ count: number, name: string }[]のようなデータ)

この時、以下のようにtrackByが提供するインデックスをそのまま返す関数を食わせてやるようにしています。

  • template:
<div *ngFor="let item of items: trackBy: trackByFn">{{ item.name }}</div>
  • ts:
trackByFn = (i: number) => i; // 意味あるのか?

しかし、インデックスは「特定のデータに対して常に同一といわけではない」ため、適切ではない気がしていますが、どうなのでしょうか。

むしろ配列の順番が変えられたときに、誤った順番をngForに認識させるような気がするのですが、そのような挙動に出くわしたことがないので、なにか理解を間違っているような気がしています……。

2 Likes

indexでtrackByした場合、たとえば

items = [{key: "foo"}, {key: "bar"}, {key: "baz"}]

という配列があったとき、それぞれをトラッキングするキーが、 [0, 1, 2] となります。

この配列がたとえば次のように変化したとき、問題があります。

items = [{key: "foo"}, {key: "bar"}, {key: "boo"}, {key: "baz"}]

この配列はトラッキングするキーは [0, 1, 2, 3] になります。
このとき、trackByの結果が3というのは前回存在しなかったものなので、 {key: "baz"}] は新たに追加された要素とみなされます。4つの要素に対応するDOMレンダリングは、[更新、更新、更新、追加] となります。
これでよいならいいのですが、 :enter など使ったアニメーションが絡むと本当に追加されたのは {key: "boo"} だと思われるので、不都合があります。

trackByをitem.key を返すようにしておくと、4つの要素に対応するDOMレンダリングは、[更新、更新、追加、更新] となります。

trackByがitem を返すと、オブジェクトの厳密比較で判断するようになります。配列の変更がmutableなら、これでも[更新、更新、追加、更新]となるでしょう

3 Likes

なるほど。

trackBy関数の目的を「配列に対して、データの更新/追加の位置を特定するための関数」と理解したらしっくりきました。

また、mutableな配列に対してはtrackByFn = (i: number, item: Item) => item;というコードが汎用的だという発見がありました! ありがとうございました!

ところで、この話題は先日配信された Angular質問室 でもlacoさんに言及してもらっているので、
興味がある方は合わせてご覧いただくと、理解がより一層深まるかと思います!

ついでに、カンタンなものなのですが、上述の配信内で私が発言した
ぼくのかんがえたさいきょうのKeyTrackByPipeを公開したので、良ければご覧ください。