突变未在 Vuex 中使用计算属性注册
Mutations not registering using computed properties in Vuex
我有一个系统使用大量表单组件根据输入的信息实时更新视图。当我第一次构建应用程序时,我主要使用 v-model、本地组件数据和 vue 保存状态来将数据保存在本地存储中,以便在他们重新加载页面时保持数据不变。
然而,当我随着程序的扩展而转移到 vuex 时,这并不是那么简单,所以我决定使用 v-model 和使用 getter 和 setter 的计算属性,这样我就不必编写更改函数了50 种不同的输入。我还意识到,您可以为一个对象创建一个计算变量,并仍然使用 v-model 来访问和更新该对象的属性,如下所示:
<div v-for="prof in info.profs">
<textarea v-model="prof.name" class="code-input uk-input" rows="1" cols="20"></textarea>
<textarea v-model="prof.email" class="code-input uk-input" rows="1" cols="25"></textarea>
<textarea v-model="prof.office" class="code-input uk-input" rows="1" cols="50"></textarea> <br>
</div>
info: {
get () {
return this.$store.getters.getInfo
},
set (payload) {
this.$store.commit('updateInfo', payload)
}
},
这非常有效,商店为每个 属性 更新数据,而无需创建自己单独的计算变量,但由于某种原因,它不会显示为已提交的突变 "updateInfo" 在 vue chrome 开发工具上,当我使用 vuex 的本地存储插件(如 vuex-persistedstate 或 vuex-persist)时,它不会更改本地存储数据,直到我提交另一个结构正常的突变。现在我的解决方法是在组件中创建 属性 的本地副本,然后观察 属性 的更改并提交到商店,这让我可以再次使用组件级别的 localstorage mixin,但我感觉必须有更好的方法来做到这一点,它不涉及为信息中的每个 属性 编写更改函数或计算变量,因为这在这个应用程序中会非常冗长。
data () {
return {
info: this.$store.getters.getInfo
}
},
watch: {
info: function(payload){
this.$store.commit('updateInfo', payload)
}
},
其实这两种方式都是错误的。将 strict: true
添加到您的商店,您将看到在这两种情况下都抛出错误。
在这两种选择中,prof
的 name
、email
和 office
属性都被直接修改(这违反了 Vuex 原则,该原则规定每次更改都应该通过突变发生)。
同样,不是计算 setter(第一种情况),也不是观察者(第二种情况)被触发,因为你没有修改 item
,而是它的深层嵌套属性(例如name
).
允许您仍然使用 mixin 的最简单的解决方案是放弃 v-model
并使用 :value
和 @input
绑定。示例:
<textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" >
请注意,它使用了一个 updateProf
方法,该方法提交突变(见下文)并进入混合。
这样所有修改都在突变中完成。最后一点,如果您发现 :value
和 @input
的使用过于冗长,您可以创建一个 custom directive 来处理它。
JSFiddle link 或下面的演示(相同代码)。
const store = new Vuex.Store({
strict: true,
state: {
info: {
profs: [
{name: "Alice", email: "alice@example.com", office: "NY"},
{name: "Bob", email: "bob@example.com", office: "CA"}
]
}
},
mutations: {
updateProf(state, {prof, prop, value}) {
prof[prop] = value;
}
},
getters: {
getInfo: state => {
return state.info
}
}
});
const mixin = {
computed: {
info() {
return this.$store.getters.getInfo
}
},
methods: {
updateProf(prof, prop, e) {
this.$store.commit('updateProf', {prof, prop, value: e.target.value})
}
}
}
new Vue({
store,
mixins: [mixin],
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
{{ info }}
<div v-for="prof in info.profs">
<hr>
name: <textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" class="code-input uk-input" rows="1" cols="20"></textarea> <br>
email: <textarea :value="prof.email" @input="updateProf(prof, 'email', $event)" class="code-input uk-input" rows="1" cols="25"></textarea> <br>
office: <textarea :value="prof.office" @input="updateProf(prof, 'office', $event)" class="code-input uk-input" rows="1" cols="50"></textarea>
</div>
</div>
保持v-model
只是为了让没有人知道我没有说这是可能的,这是您可以继续使用 v-model
的一种方法。这个替代方案的关键点是执行深度克隆和深度相等的功能。我提供了两个 simple/naive 实现,YMMV:
JSFiddle link。演示(与fiddle相同的代码)如下:
const store = new Vuex.Store({
strict: true,
state: {
info: {
profs: [
{name: "Alice", email: "alice@example.com", office: "NY"},
{name: "Bob", email: "bob@example.com", office: "CA"}
]
}
},
mutations: {
updateInfo(state, data) {
state.info = data
}
},
getters: {
getInfo: state => {
return state.info
}
}
});
// these two functions are key here
// consider using other implementations if you have more complicated property types, like Dates
function deepClone(o) { return JSON.parse(JSON.stringify(o)); }
function deepEquals(o1, o2) { return JSON.stringify(o1) === JSON.stringify(o2) }
const mixin = {
data() {
return {
info: deepClone(this.$store.getters.getInfo),
}
},
computed: {
getInfo() {
return this.$store.getters.getInfo;
}
},
watch: {
getInfo: {
deep: true,
handler(newInfo) {
if (!deepEquals(newInfo, this.info)) { // condition to prevent infinite loops
this.info = deepClone(newInfo);
}
}
},
info: {
deep: true,
handler(newInfo) {
this.$store.commit('updateInfo', deepClone(newInfo))
}
}
}
}
new Vue({
store,
mixins: [mixin],
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
{{ info }}
<div v-for="prof in info.profs">
<hr>
name: <textarea v-model="prof.name" class="code-input uk-input" rows="1" cols="20"></textarea> <br>
email: <textarea v-model="prof.email" class="code-input uk-input" rows="1" cols="25"></textarea> <br>
office: <textarea v-model="prof.office" class="code-input uk-input" rows="1" cols="50"></textarea>
</div>
</div>
我有一个系统使用大量表单组件根据输入的信息实时更新视图。当我第一次构建应用程序时,我主要使用 v-model、本地组件数据和 vue 保存状态来将数据保存在本地存储中,以便在他们重新加载页面时保持数据不变。
然而,当我随着程序的扩展而转移到 vuex 时,这并不是那么简单,所以我决定使用 v-model 和使用 getter 和 setter 的计算属性,这样我就不必编写更改函数了50 种不同的输入。我还意识到,您可以为一个对象创建一个计算变量,并仍然使用 v-model 来访问和更新该对象的属性,如下所示:
<div v-for="prof in info.profs">
<textarea v-model="prof.name" class="code-input uk-input" rows="1" cols="20"></textarea>
<textarea v-model="prof.email" class="code-input uk-input" rows="1" cols="25"></textarea>
<textarea v-model="prof.office" class="code-input uk-input" rows="1" cols="50"></textarea> <br>
</div>
info: {
get () {
return this.$store.getters.getInfo
},
set (payload) {
this.$store.commit('updateInfo', payload)
}
},
这非常有效,商店为每个 属性 更新数据,而无需创建自己单独的计算变量,但由于某种原因,它不会显示为已提交的突变 "updateInfo" 在 vue chrome 开发工具上,当我使用 vuex 的本地存储插件(如 vuex-persistedstate 或 vuex-persist)时,它不会更改本地存储数据,直到我提交另一个结构正常的突变。现在我的解决方法是在组件中创建 属性 的本地副本,然后观察 属性 的更改并提交到商店,这让我可以再次使用组件级别的 localstorage mixin,但我感觉必须有更好的方法来做到这一点,它不涉及为信息中的每个 属性 编写更改函数或计算变量,因为这在这个应用程序中会非常冗长。
data () {
return {
info: this.$store.getters.getInfo
}
},
watch: {
info: function(payload){
this.$store.commit('updateInfo', payload)
}
},
其实这两种方式都是错误的。将 strict: true
添加到您的商店,您将看到在这两种情况下都抛出错误。
在这两种选择中,prof
的 name
、email
和 office
属性都被直接修改(这违反了 Vuex 原则,该原则规定每次更改都应该通过突变发生)。
同样,不是计算 setter(第一种情况),也不是观察者(第二种情况)被触发,因为你没有修改 item
,而是它的深层嵌套属性(例如name
).
允许您仍然使用 mixin 的最简单的解决方案是放弃 v-model
并使用 :value
和 @input
绑定。示例:
<textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" >
请注意,它使用了一个 updateProf
方法,该方法提交突变(见下文)并进入混合。
这样所有修改都在突变中完成。最后一点,如果您发现 :value
和 @input
的使用过于冗长,您可以创建一个 custom directive 来处理它。
JSFiddle link 或下面的演示(相同代码)。
const store = new Vuex.Store({
strict: true,
state: {
info: {
profs: [
{name: "Alice", email: "alice@example.com", office: "NY"},
{name: "Bob", email: "bob@example.com", office: "CA"}
]
}
},
mutations: {
updateProf(state, {prof, prop, value}) {
prof[prop] = value;
}
},
getters: {
getInfo: state => {
return state.info
}
}
});
const mixin = {
computed: {
info() {
return this.$store.getters.getInfo
}
},
methods: {
updateProf(prof, prop, e) {
this.$store.commit('updateProf', {prof, prop, value: e.target.value})
}
}
}
new Vue({
store,
mixins: [mixin],
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
{{ info }}
<div v-for="prof in info.profs">
<hr>
name: <textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" class="code-input uk-input" rows="1" cols="20"></textarea> <br>
email: <textarea :value="prof.email" @input="updateProf(prof, 'email', $event)" class="code-input uk-input" rows="1" cols="25"></textarea> <br>
office: <textarea :value="prof.office" @input="updateProf(prof, 'office', $event)" class="code-input uk-input" rows="1" cols="50"></textarea>
</div>
</div>
保持v-model
只是为了让没有人知道我没有说这是可能的,这是您可以继续使用 v-model
的一种方法。这个替代方案的关键点是执行深度克隆和深度相等的功能。我提供了两个 simple/naive 实现,YMMV:
JSFiddle link。演示(与fiddle相同的代码)如下:
const store = new Vuex.Store({
strict: true,
state: {
info: {
profs: [
{name: "Alice", email: "alice@example.com", office: "NY"},
{name: "Bob", email: "bob@example.com", office: "CA"}
]
}
},
mutations: {
updateInfo(state, data) {
state.info = data
}
},
getters: {
getInfo: state => {
return state.info
}
}
});
// these two functions are key here
// consider using other implementations if you have more complicated property types, like Dates
function deepClone(o) { return JSON.parse(JSON.stringify(o)); }
function deepEquals(o1, o2) { return JSON.stringify(o1) === JSON.stringify(o2) }
const mixin = {
data() {
return {
info: deepClone(this.$store.getters.getInfo),
}
},
computed: {
getInfo() {
return this.$store.getters.getInfo;
}
},
watch: {
getInfo: {
deep: true,
handler(newInfo) {
if (!deepEquals(newInfo, this.info)) { // condition to prevent infinite loops
this.info = deepClone(newInfo);
}
}
},
info: {
deep: true,
handler(newInfo) {
this.$store.commit('updateInfo', deepClone(newInfo))
}
}
}
}
new Vue({
store,
mixins: [mixin],
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
{{ info }}
<div v-for="prof in info.profs">
<hr>
name: <textarea v-model="prof.name" class="code-input uk-input" rows="1" cols="20"></textarea> <br>
email: <textarea v-model="prof.email" class="code-input uk-input" rows="1" cols="25"></textarea> <br>
office: <textarea v-model="prof.office" class="code-input uk-input" rows="1" cols="50"></textarea>
</div>
</div>