ruby-on-rails - Vue.JS 和 Rails-UJS/Jquery-UJS 冲突 - Vuex 突变不起作用

标签 ruby-on-rails vue.js vuejs2 ruby-on-rails-5 vuex

我正在学习一个简单的教程,出于某种原因,我的 2 个 View 突变(addCard 和 addList)工作正常......但是,我的第三个突变(editCard)似乎在 Vue 中不起作用。当我单击卡片时,会弹出一个停留点,您可以在其中编辑名称...保存后,它会正确保存在 Rails 中,但不会立即在浏览器中更新。您必须刷新页面才能看到更改。我最初认为这是与 Vuex 和 Rails-ujs 的冲突,但为什么 2 个突变不起作用而第 3 个突变不起作用?在此感谢 Vue 专家的任何帮助...

应用程序/javascript/app.vue

<template>
  <div id="app" class="row">

    <div class="col-12">

      <!-- Button trigger modal -->
      <button type="button" data-toggle="modal" data-target="#exampleModal">New List</button>

      <!-- Bootstrap Modal -->
      <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog" role="document">
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
              <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
              </button>
            </div>
            <div class="modal-body">

              <textarea ref="message" v-model="message" class="form-control mb-1">
              </textarea>

            </div>
            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
              <button v-on:click="createList" class="btn btn-secondary">Add</button>
            </div>
          </div>
        </div>
      </div>

    </div><br/><br/>

    <hr /><hr />

    <div class="col-2">
      <div class="list">
        <a v-if="!editing" v-on:click="startEditing">
          <h1 style="padding: 20px 20px;">
            <span style="font-style: italic;">+ Add a List</span>
          </h1>
        </a>
        <textarea v-if="editing" ref="message" v-model="message" class="form-control mb-1">
        </textarea>
        <button v-if="editing" v-on:click="createList" class="btn btn-secondary">Add</button>
        <a v-if="editing" v-on:click="editing=false">cancel</a>
      </div>
    </div>

    <list v-for="(list, index) in lists" :list="list"></list>

  </div>
</template>

<script>
import list from 'components/list'
export default {
  components: { list },
  data: function() {
    return {
      editing: false,
      message: "",
    }
  },
  computed: {
    lists: {
      get() {
        return this.$store.state.lists;
      },
      set(value) {
        this.$store.state.lists = value
      },
    },
  },
  methods: {
    startEditing: function () {
      this.editing = true
      this.$nextTick(() => { this.$refs.message.focus() })
    },
    createList: function() {
      var data = new FormData // -> {}
      data.append("list[name]", this.message)// -> { "list[name]" => this.message }
      Rails.ajax({
        url: "/lists",
        type: "POST",
        data: data,
        dataType: "json",
        beforeSend: () => true,// 2xx, 3xx (SUCCESS), 4xx, 5xx (ERROR)
        success: (data) => {
          this.$store.commit('addList', data)
          this.message = ""
          this.editing = false
          $('#exampleModal').modal('hide');
            return false;
        }
      });
    }
  }
}
</script>

<style scoped>
.list {
  background-color: #e2e4e6;
  padding: 8px;
  border-radius: 3px;
  margin-bottom: 8px;
}
.card {

}
p {
  font-size: 2em;
  text-align: center;
}
</style>

应用程序/javascript/packs/application.js

import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex'

// import BootstrapVue from 'bootstrap-vue' || These are for bootstrap vue removing for now
import App from'../app.vue'
import TurbolinksAdapter from 'vue-turbolinks'

// import 'bootstrap/dist/css/bootstrap.css'; || These are for bootstrap vue removing for now
// import 'bootstrap-vue/dist/bootstrap-vue.css'; || These are for bootstrap vue removing for now

// Vue.use(BootstrapVue); || These are for bootstrap vue removing for now

Vue.use(Vuex)
Vue.use(TurbolinksAdapter)

window.store = new Vuex.Store({
    state: {
        lists: []
    },

    mutations: {
        addList(state, data) {
            state.lists.unshift(data)
        },
        addCard(state, data) {
            const index = state.lists.findIndex(item => item.id == data.list_id)
        state.lists[index].cards.push(data)
        },
        editCard(state, data) {
            const list_index = state.lists.findIndex((item) => item.id == data.list_id)
            const card_index = state.lists[list_index].cards.findIndex((item) => item.id == data.id)
            state.lists[list_index].cards.splice(card_index, 1, data)
        },
    }
})

document.addEventListener("turbolinks:load", function() {
    var element = document.querySelector("#boards")
    if (element != undefined) {

        window.store.state.lists = JSON.parse(element.dataset.lists)

        const app = new Vue({
            el: element, 
            store: window.store,
            template: "<App />",
            components: { App }
        })
    }
});

应用程序/javascript/components/card.vue

    <template>
         <div>
        <div @click="editing=true" class="card card-body mb-3">
          {{card.name}}
        </div>

        <div v-if='editing' class="modal-backdrop show"></div>

        <div v-if='editing' @click="closeModal" class="modal show" style="display: block">
          <div class="modal-dialog">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title">{{ card.name }}</h5>
              </div>
              <div class="modal-body">
                <input v-model="name" class="form-control"></input>
              </div>
              <div class="modal-footer">
                <button @click="save" type="button" class="btn btn-primary">Save changes</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>

    <script>
    export default {
        props: ['card', 'list'],
        data: function () {
            return {
                editing: false,
                name: this.card.name,
            }
        },
        methods: {
            closeModal: function(event) {
                if (event.target.classList.contains("modal")) {
                    this.editing = false
                }
            },
            save: function() {
                var data = new FormData
                data.append("card[name]", this.name)
                Rails.ajax({
                    url: `/cards/${this.card.id}`,
                    type: "PATCH",
                    data: data,
                    dataType: "json",
                    beforeSend: function() { return true },
                    success: (data) => {
                        this.$store.commit('editCard', data)
                        this.editing = false
                    }
                })
            },
        }
    }
    </script>

    <style scoped>
    </style>

应用/javascript/components/list.vue

<template>
    <div class="col-2">

    <div class="list">
      <h6>{{ list.name }}</h6>

      <card v-for="card in list.cards" :card="card" :list="list"></card>

      <div class="card card-body">

        <a v-if="!editing" v-on:click="startEditing">Add a Card</a>

        <textarea v-if="editing" ref="message" v-model="message" class="form-control mb-1"></textarea>
        <button v-if="editing" v-on:click="createCard" class="btn btn-secondary">Add</button>

        <a v-if="editing" v-on:click="editing=false">cancel</a>

      </div>
    </div>

  </div>
</template>

<script>
import card from 'components/card'
export default {
    components: { card },
    props: ["list"],
    data: function () {
        return {
            editing: false,
            message: ""
        }
    },
    methods: {
        startEditing: function () {
            this.editing = true
            this.$nextTick(() => { this.$refs.message.focus() })
        },
        createCard: function() {
        var data = new FormData
        data.append("card[list_id]", this.list.id)
        data.append("card[name]", this.message)
        Rails.ajax({
            url: "/cards",
            type: "POST",
            data: data, 
            dataType: "json",
            beforeSend: function() { return true },
            success: (data) => {
          this.$store.commit('addCard', data)
                this.message = ""
                this.$nextTick(() => { this.$refs.message.focus() })
            }
        });
    }
    }
}
</script>

<style scoped>
.list {
  background-color: #e2e4e6;
  padding: 8px;
  border-radius: 3px;
  margin-bottom: 8px;
}
.btn.btn-secondary {
  width: 75px;
}
</style>

更新:当我编辑和保存卡片时,我已经根据要求更新了控制台和终端日志。

终端日志:

Started GET "/lists/" for 127.0.0.1 at 2018-04-24 21:51:47 -0500
Processing by ListsController#index as HTML
  Rendering lists/index.html.erb within layouts/application
  List Load (11.3ms)  SELECT "lists".* FROM "lists" ORDER BY "lists"."position" DESC
  Card Load (0.1ms)  SELECT "cards".* FROM "cards" WHERE "cards"."list_id" = ? ORDER BY "cards"."position" ASC  [["list_id", 235]]
  Card Load (0.1ms)  SELECT "cards".* FROM "cards" WHERE "cards"."list_id" = ? ORDER BY "cards"."position" ASC  [["list_id", 234]]
  Card Load (0.1ms)  SELECT "cards".* FROM "cards" WHERE "cards"."list_id" = ? ORDER BY "cards"."position" ASC  [["list_id", 233]]
  Card Load (0.1ms)  SELECT "cards".* FROM "cards" WHERE "cards"."list_id" = ? ORDER BY "cards"."position" ASC  [["list_id", 232]]
  Card Load (0.1ms)  SELECT "cards".* FROM "cards" WHERE "cards"."list_id" = ? ORDER BY "cards"."position" ASC  [["list_id", 231]]
  Rendered lists/index.html.erb within layouts/application (17.8ms)
  Rendered shared/_head.html.erb (203.0ms)
  Rendered shared/_navbar.html.erb (0.6ms)
  Rendered shared/_notices.html.erb (0.3ms)
Completed 200 OK in 370ms (Views: 346.5ms | ActiveRecord: 11.9ms)


Started PATCH "/cards/106" for 127.0.0.1 at 2018-04-24 21:51:59 -0500
Processing by CardsController#update as JSON
  Parameters: {"card"=>{"name"=>"Card C30006"}, "id"=>"106"}
  Card Load (0.2ms)  SELECT  "cards".* FROM "cards" WHERE "cards"."id" = ? LIMIT ?  [["id", 106], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  List Load (0.1ms)  SELECT  "lists".* FROM "lists" WHERE "lists"."id" = ? LIMIT ?  [["id", 231], ["LIMIT", 1]]
  SQL (0.2ms)  UPDATE "cards" SET "name" = ?, "updated_at" = ? WHERE "cards"."id" = ?  [["name", "Card C30006"], ["updated_at", "2018-04-25 02:51:59.753283"], ["id", 106]]
   (1.7ms)  commit transaction
  Rendering cards/show.json.jbuilder
  Rendered cards/_card.json.jbuilder (0.6ms)
  Rendered cards/show.json.jbuilder (2.5ms)
Completed 200 OK in 28ms (Views: 21.7ms | ActiveRecord: 2.2ms)

浏览器控制台:

{id: 106, list_id: 231, name: "Card C30006", position: 3, created_at: "2018-04-24T20:39:06.150Z", …}
created_at:(...)
id:(...)
list_id:(...)
name:(...)
position:(...)
updated_at:(...)
url:(...)
__ob__:Observer
dep:Dep {id: 86, subs: Array(0)}
value:{…}
vmCount:0
__proto__:Object
get created_at:ƒ reactiveGetter()
set created_at:ƒ reactiveSetter(newVal)
get id:ƒ reactiveGetter()
set id:ƒ reactiveSetter(newVal)
get list_id:ƒ reactiveGetter()
set list_id:ƒ reactiveSetter(newVal)
get name:ƒ reactiveGetter()
set name:ƒ reactiveSetter(newVal)
get position:ƒ reactiveGetter()
set position:ƒ reactiveSetter(newVal)
get updated_at:ƒ reactiveGetter()
set updated_at:ƒ reactiveSetter(newVal)
get url:ƒ reactiveGetter()
set url:ƒ reactiveSetter(newVal)
__proto__:Object

更新 2:(添加 Vuex 面板输出)

enter image description here

最佳答案

Vue 对可以自动检测到的数据更新有警告:https://v2.vuejs.org/v2/guide/list.html#Caveats

另外, react 规则有一些信息:https://vuex.vuejs.org/en/mutations.html#mutations-follow-vues-reactivity-rules

我认为警告页面实际上反射(reflect)了您的情况。引用:

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue When you modify the length of the array, e.g. vm.items.length = newLength

To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:

// Vue.set Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)

基于此,我会尝试将您的代码更新为:

editCard(state, data) {
            const list_index = state.lists.findIndex((item) => item.id == data.list_id)
            const card_index = state.lists[list_index].cards.findIndex((item) => item.id == data.id)
            var updata = state.lists[list_index].cards[card_index] =  data;
            state.lists.splice(list_index, 1, updata);

将拼接移动到列表的顶层现在应该会触发更新。

请注意,如果您有任何反馈,我很乐意适当更新此答案。

关于ruby-on-rails - Vue.JS 和 Rails-UJS/Jquery-UJS 冲突 - Vuex 突变不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49933491/

相关文章:

mysql - 在新 Controller 方法中使用 form_for 时未定义方法

javascript - 如何将单击/选定的行数据从一个表传递到并排放置的另一个表

javascript - 2 个组件几乎完全相同。唯一的区别是每个组件都有不同的 GET 请求。合并他们?

ruby-on-rails - 在 Rails 3 中通过关联使用 has_many 防止孤立对象

ruby-on-rails - 有什么方法可以判断在运行时为 Rails 进程加载了哪些 gem 和插件?

ruby-on-rails - 有没有人能够让 attachment_fu 与 rails 3 一起工作?

vue.js - Nuxt 动态 Assets 未加载

javascript - 测试库 - 测试依赖于 CSS 的逻辑

javascript - 如何在 VueJs 中显示数组中的单个对象?

vue.js - Vue Cli 3 和 Firebase service worker 注册