Vue.Draggable で作った nested tree の操作結果を vuex に反映させたい

Vue.Draggable で nested tree と vuex を組み合わせるサンプル (下記) が有ったので試してみました。

「tree を操作したら、その結果が vuex に反映される」という期待を持っていたのですが、その通りではないように見えます。

Vue.Draggable
Nested (v-model & vuex)
http://bit.ly/2RFcEF7

下図のように、state.elements を反映した tree があります。

一層目の tree nodes を操作すると、vuex にそれが反映されるのは期待通りなので、その入れ子になっており、図中で色付けしている二層目の tree nodes を上下入れ替えてみました。

その結果が以下です。

図中では二層目の tree nodes が上下入れ替えられているし、画面右側にある JSON も上下入れ替わった状態を反映しています。

しかし、devtools の state.elements では、入れ替えの結果が反映されておらず、期待通りではありません。

nested tree の nodes 操作結果を vuex state に反映させるには、このサンプルをどう書き直したら良いでしょう?

ちなみに、dev tools の設定は以下のようにしています。

上記サンプルとは別に、vue cli 3 で改めて作ってみました。

  • state.bs (1) を
  • Vue.Draggable で nested tree (3) を作成
  • 一層目の tree nodes (4) を変更してみると…

  • 一層目を (5) のように並べ替えると
  • JSON (6) もそれを反映している
  • state.bs (7) も同じく反映している
  • 続いて、二層目の tree nodes (8) を並べ替えると…

  • 二層目を (9) のように並べ替えると
  • JSON はそれを反映しているが
  • state.bs は並べ替える前のまま、反映されていない

サンプルと同じ結果でした。

以下、ソースコードです。

$ vue --version
3.8.4
$ vue create vue_cli
$ cd vue_cli
$ yarn add vuedraggable
...
$ yarn serve

src/App.vue

<template>
  <div id="app">
    <div class="container">
      <div class="tree">
        <Tree v-model="bs" /> ............................. (a)
      </div>

      <div class="json">
        <pre>{{ json }}</pre>
      </div>
    </div>
  </div>
</template>

<script>
  import Tree from './components/Tree.vue'

  export default {
    name: 'app',
    components: { Tree },
    computed: {
      bs: {
        get () {
          return this.$store.state.bs.list;
        },
        set (bs) {
          this.$store.commit('bs/SET_LIST', bs);
        }
      },
      json () {
        return JSON.stringify(this.bs, null, 2);
      }
    },
  }
</script>

src/components/Tree.vue

<template>
  <draggable
    tag="ul"
    :list="list"
    :value="value"
    @input="emitter">

    <li v-for="(node, i) in realValue" :key="node.id">
      {{ node.name }}

      <Tree v-if="node.children" :list="node.children" /> ... (b)
    </li>
  </draggable>
</template>

<script>
  import draggable from 'vuedraggable'

  export default {
    name: 'Tree',
    components: { draggable },
    props: {
      value: {
        type: Array,
        required: false,
        default: null,
      },
      list: {
        type: Array,
        required: false,
        default: null,
      },
    },
    computed: {
      realValue () {
        return this.value? this.value: this.list;
      }
    },
    methods: {
      emitter (value) {
        this.$emit('input', value);
      }
    }
  }
</script>

src/store/bs.js

export const state = () => ({
  list: [
    { id: 1, name: 'b1' },
    { id: 2, name: 'b2' },
    { id: 3, name: 'b3', children: [
      { id: 4, name: 'b4' },
      { id: 5, name: 'b5' },
      { id: 6, name: 'b6' },
    ] },
  ]
})

export const mutations = {
  SET_LIST (state, list) {
    state.list = list;
  }
}

export default {
  namespaced: true,
  state,
  mutations
}

Nuxt で作ってみました。

  • 一層目 (12) を並べ替えてみると…

  • 一層目の並べ替えが
  • JSON にも反映されているし
  • state.bs にも反映されている
  • 二層目 (13) を並べ替えてみると…

  • 二層目を並べ替えた直後にエラー発生
  • Error: [vuex] do not mutate vuex store state outside mutation handlers

この結果の方が想定通りなんだけど、どうやって解決したら良いのでしょう?

そもそも、何故 vue cli 版では、同エラーが出ないのか?

ソースコードは、vue cli 版とほぼ同じです。

そもそも、何故 vue cli 版では、同エラーが出ないのか?

strict mode にすると vue cli 版でもエラーは出ました。

src/store/index.js

export default new Vuex.Store({
  strict: true, ........................................... (3)
  modules: {
    bs
  },

サンプルも確認してみると、strict mode になっていないようです。

vuex の部分的な strict mode って無かったと思うので、全体的に strict=false にするのは嫌だけど、取り敢えず動作確認のためにそうしておきます。

store/index.js

export const strict = false

pages/index.vue

<template lang="html">
  ...
  <Tree :list="bs" />
  ...
</template>

<script>
  ...
  export default {
    computed: {
      ...mapState('bs', {
        bs: 'list'
      }),
      ...
    },
    watch: {
      bs: {
        deep: true,
        handler (bs) {
          this.$store.commit('bs/SET_LIST', bs);
        }
      }
      ...
</script>

components/Tree.vue

<template>
  <draggable
    tag="ul"
    group="nested"
    :list="list">

    <li v-for="(node, i) in list" :key="node.id">
      {{ node.name }}

      <Tree
        v-if="node.children"
        :list="node.children"
      />
    </li>
  </draggable>
</template>

<script>
  import draggable from 'vuedraggable'

  export default {
    name: 'Tree',
    components: { draggable },
    props: {
      list: {
        type: Array,
        required: false,
        default: null,
      },
    },
  }
</script>

以下、結果です。

以下のような並びの tree を

すべての層で昇降順を逆にしてみます。

  • Vue.Draggable の為だけに strict mode を解除するのは嫌だ
  • list をどんと一括で置換しているので、大きな list だと負荷大きそう
  • 並び替えた箇所のみの state を置換したい