ymmooot

Nuxt におけるランダム表示の整合性

概要

何かをランダムで表示するときに、SSR と CSR でそれぞれランダムにしてしまうと、 Mismatching childNodes vs. VNodes に遭遇してしまう。
VDOM にズレが生じてよろしくない。

対策

対策は多分二つある。

1. asyncData

asyncData はサーバーでしか実行されないので、コンポーネントに密な要素はここで作ってしまえば一番手っ取り早い。

2. リクエストごとにランダム用のシードを用意する

Vuex の getter を computed 経由で呼び出すような場合は、クライアントでもしっかり再実行されてしまう。
そこで以下のように randomSeed を生成する。

export const state = () => ({
  randomSeed: new Date().getTime().toString(),
});
export type RootState = ReturnType<typeof state>;

state 関数はリクエストごとに実行され、初期化された新しい state を返す。
ここになんでも良いのでシードを入れておく。
これをランダム生成の際に使えば、リクエストごとに異なる結果を得られるが、サーバーとクライアントで同じ結果を得られるようになる。

以下は、配列をシャッフルする例。

shuffled.tsimport cloneDeep from 'clone-deep';
import seedrandom from 'seedrandom';

export default <T>(ary: Array<T>, seed?: string): Array<T> => {
  const cp = cloneDeep(ary);
  const random = seedrandom(seed);

  // Fisher-Yates
  for (let i = cp.length - 1; i > 0; i -= 1) {
    const r = Math.floor(random() * (i + 1));
    const tmp = cp[i];
    cp[i] = cp[r];
    cp[r] = tmp;
  }

  return cp;
};
hogeStoreModule.tsexport const getters: GetterTree<HogeState, RootState> = {
  randomArray(state: HogeState, _, rootState: RootState) {
    return shuffled(state.hoges, rootState.randomSeed);
  },
}

まとめ

シードをとって再現性のあるランダムが作れるようにしておけば、テストもしやすいし良い。