Misskey: リアクションの正しい表示順序は?

Created on 6 Jan 2020  ·  15Comments  ·  Source: syuilo/misskey

投稿についたリアクションはnote.reactions のオブジェクトで得られますが、それを

<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>

で表示する際にv-forは「オブジェクトを反復処理するとき、順序は Object.keys() の列挙順のキーに基づいており、全ての JavaScript エンジンの実装で一貫性が保証されていません。」となっています。

実際には実装依存で https://qiita.com/suguru03/items/bf48610225fefcb0f45e によるとnodeとchromeでは「まず数値順、それ以外は挿入順」と書かれています。

サーバ側ではクエリ時に ID降順で取得しているようなので、新しく付与されたリアクションが先頭に来るのかもしれません。

しかし実装依存な方法で実現されると クライアント側はJSONパーサから書き起こさないと対応できません。

⚙️Server 🖥️Client 🧩API

Most helpful comment

最初はリアクションピッカーの初期10種類の表示順でした

他の絵文字も使えるようになったタイミングでその順番には出来なくなりました (しなくなった)
その時点から事実上挿入順になっているはず (keyに数値扱いされそうな文字列は登場しないので)

その際、挿入順以外だと新しい種類のリアクションが到着した時に位置が動いてしまうので こっちの方がいいかみたいな温度感だった気がします。

All 15 comments

特に決めてはないですが、一応アルファベット順を想定しています

misskey.io を見る限りAPI応答も表示もアルファベット順には見えません…。

https://misskey.io/notes/824if6bvac
image

https://misskey.io/api/notes/show {"noteId":"824if6bvac","i":"*"}

{"id":"824if6bvac","createdAt":"2020-01-05T11:41:23.275Z","userId":"7rkzefvejx","user":{"id":"7rkzefvejx","name":"れいわびーむ:erait:","username":"reiju","host":null,"avatarUrl":"https://s3.arkjp.net/misskey/pdg1/2bf75c22-3a6f-4c54-8569-beb597ed5454.png","avatarColor":"rgba(226,217,180,0)","emojis":[{"name":"erait","host":null,"url":"https://emoji.arkjp.net/misskey/erait.svg","aliases":[]}]},"text":"いやれいじゅぼっと落ちんなや","cw":null,"visibility":"public","renoteCount":0,"repliesCount":0,"reactions":{"rip":1,"like":1,"love":1,"🤯":1,"pudding":1},"emojis":[],"fileIds":[],"files":[],"replyId":null,"renoteId":null}

最初はリアクションピッカーの初期10種類の表示順でした

他の絵文字も使えるようになったタイミングでその順番には出来なくなりました (しなくなった)
その時点から事実上挿入順になっているはず (keyに数値扱いされそうな文字列は登場しないので)

その際、挿入順以外だと新しい種類のリアクションが到着した時に位置が動いてしまうので こっちの方がいいかみたいな温度感だった気がします。

挿入順になることを保証する場合は、配列にするか日付やインデックス等の何らかのソート可能なプロパティを含めたオブジェクトの連想配列にする必要が生じますね

Noteに添付されているreactionsのソースは
NoteReactionテーブルではなく、Noteテーブルのreactionsカラム (jsonb型) のようなので
おそらくDB上では挿入順が保持されていて、APIでもそのまま保持されていると思われます。

挿入順になることを保証する場合は、配列にするか日付やインデックス等の何らかのソート可能なプロパティを含めたオブジェクトの連想配列にする必要が生じますね

いろんな言語でのクライアント実装を考慮するとAPIの応答形式はそうであるべきかもしれません

情報ありがとうございます。
こちらとしてはAPI応答の仕様変更はあってもなくても構わない感じです。
挿入順表示に依存したネタ(たとえば 🅿ℹ🆖 とか)を再現できないとなると少し残念ですね…。

JSONパーサ書くか…

あちこちに自前JSONパーサーを書かせるのもあれだしちゃんとクライアントで順番がわかるような形で返したほうがよさそう

もう書いちゃったよ…

まず補足から失礼します。ES2015 以降において、オブジェクトのキー順序は Object.getOwnPropertyNames などにおいては定義されています

The abstract operation GetOwnPropertyKeys is called with arguments O and Type where O is an Object and Type is one of the ECMAScript specification types String or Symbol. The following steps are taken:

  1. Let obj be ToObject(O).
  2. ReturnIfAbrupt(obj).
  3. Let keys be obj.[[OwnPropertyKeys]]().
  4. ReturnIfAbrupt(keys).
  5. Let nameList be a new empty List.
  6. Repeat for each element nextKey of keys in List order,

    1. If Type(nextKey) is Type, then



      1. Append nextKey as the last element of nameList.



  7. Return CreateArrayFromList(nameList).

— 19.1.2.8.1 Runtime Semantics: GetOwnPropertyKeys ( O, Type )

When the [[OwnPropertyKeys]] internal method of O is called the following steps are taken:

  1. Let keys be a new empty List.
  2. For each own property key P of O that is an integer index, in ascending numeric index order

    1. Add P as the last element of keys.

  3. For each own property key P of O that is a String but is not an integer index, in property creation order

    1. Add P as the last element of keys.

  4. For each own property key P of O that is a Symbol, in property creation order

    1. Add P as the last element of keys.

  5. Return keys.

— 9.1.12 [[OwnPropertyKeys]] ( )

が、 読んでいただくと分かるとおり数値順と挿入順が結合しています。そのため、

他の絵文字も使えるようになったタイミングでその順番には出来なくなりました (しなくなった)
その時点から事実上挿入順になっているはず (keyに数値扱いされそうな文字列は登場しないので)

その際、挿入順以外だと新しい種類のリアクションが到着した時に位置が動いてしまうので こっちの方がいいかみたいな温度感だった気がします。

を満たすためには確かに現在の実装は適切でない場合があります。 数値がプロパティ名に入ってくることはないので実質挿入順で保証されているものとみなせます。ただ、他の実装においてこれを適用できるとは限りません。これに関しては

JSONパーサ書くか…

との意見がありますが、流石にその線で攻めるのは避けるべきでしょう。かといって API の互換性は潰したくないので、別で order の入った配列をぶん投げるのが最適と考えます。
JavaScript においてはクライアント側で受け取った order を Symbol['iterator']ConstructorParameters<typeof Proxy>['ownKeys'] で差し込むことで v-for の列挙順序をコントロールできます。参考までに v-for の列挙実装はこのようになっています。

JSON規格はES2015より古いJavaScriptを元にしたものであり、オブジェクト中の出現順序への依存は規格中で明確に否定されています。
nodeがES2015互換であるからといってクライアントがJSONパース時に出現順序を意識させられる根拠にはなりません。

しかし今のMisskeyのWebUIがそれをやってしまってあるので、挙動を揃えるためにはパーサから用意しないとダメだったのです。

STは既に実装して試験リリースも出してますが、他のクライアント作者からすると困るやつですね。
順序の配列なんて分かりにくいものを追加するより、"reactions2":[["name",count],...]のような新仕様のデータを併記して、古い方は移行期間の後に削除するのをオススメします。

Vue.js の挙動についての補足をしたつもりであり、JSON に対する補足をしたつもりはないです。そもそもその指摘だとパーサーを書いたところで未定義の動作に頼ったままで何も変わらないのでは?ES2015 においても JSON.stringify の順序保証は依然としてありませんし。

サードパーティー開発者側がよしとするなら将来的な破壊的変更を見据えたプロパティ追加を行っても良いかもしれません。(流石にプロパティ名は Cookie2 の二の舞にならないように要検討ですが)

こちら的には「MisskeyのWebUIの挙動に揃えること」が目的なので、それが「未定義の動作に頼っている」だけの話です。

Web UIでもリアクションの順番は保証されてないみたいです

された順
image

実際に取得された順番
image

クライアントでもAPIの実装のせいでもなくDBから以下のJSONを取得した時点で
順番は ずれてました (なおv10は ずれてなかった)
https://github.com/syuilo/misskey/blob/cff91a7674f2e5bbdb9f0eb0a09f11891106f2f1/src/models/entities/note.ts#L106-L109

順番を保証しようとすると、Note取得時に以下のテーブルを見て
createAt順を保持したまま各リアクションの数を集計 or このテーブルから順番の情報だけ持ってきてソート みたいなことをしなければならなそう。めんどくさそう。
https://github.com/syuilo/misskey/blob/cff91a7674f2e5bbdb9f0eb0a09f11891106f2f1/src/models/entities/note-reaction.ts

v12 のタイミングでマイグレーションに組み込みたい感があります。

Was this page helpful?
0 / 5 - 0 ratings