Vuex - 不要在突变处理程序之外改变 vuex 存储状态
Vuex - Do not mutate vuex store state outside mutation handlers
为什么会出现此错误:
Error [vuex] Do not mutate vuex store state outside mutation handlers.
什么意思?
当我尝试在 编辑输入文件 中输入时发生这种情况。
pages/todos/index.vue
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" v-on:change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button class="destroy" v-on:click="remove(todo)">delete</button>
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
<li><input placeholder="What needs to be done?" autofocus v-model="todo" v-on:keyup.enter="add"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data () {
return {
todo: '',
editedTodo: null
}
},
head () {
return {
title: this.$route.params.slug || 'all',
titleTemplate: 'Nuxt TodoMVC : %s todos'
}
},
fetch ({ store }) {
store.commit('todos/add', 'Hello World')
},
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
},
methods: {
add (e) {
var value = this.todo && this.todo.trim()
if (value) {
this.$store.commit('todos/add', value)
this.todo = ''
}
},
toggle (todo) {
this.$store.commit('todos/toggle', todo)
},
remove (todo) {
this.$store.commit('todos/remove', todo)
},
doneEdit (todo) {
this.editedTodo = null
todo.text = todo.text.trim()
if (!todo.text) {
this.$store.commit('todos/remove', todo)
}
},
cancelEdit (todo) {
this.editedTodo = null
todo.text = this.beforeEditCache
},
},
directives: {
'todo-focus' (el, binding) {
if (binding.value) {
el.focus()
}
}
},
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
stores/todos.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
有什么办法可以解决这个问题吗?
在属于 Vuex 的一段状态上使用 v-model 可能有点棘手。
并且您在 todo.text
上使用了 v-model
:
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
使用 :value
读取值并使用 v-on:input
或 v-on:change
执行在显式 Vuex 突变处理程序中执行突变的方法
这里处理这个问题:https://vuex.vuejs.org/en/forms.html
你好,我遇到了同样的问题,使用以下方法之一克隆我的对象来解决它:
{ ...obj} //spread syntax
Object.assign({}, obj)
JSON.parse(JSON.stringify(obj))
对于你的代码,我认为你需要替换这部分
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
}
有了这个
computed: {
todos () {
// console.log(this)
return {...this.$store.state.todos.list}
}
}
我不确定这是否是最好的方法,但希望这对遇到同样问题的其他人有所帮助。
会用lodash就不头疼了
computed: {
...mapState({
todo: (state) => _.cloneDeep(state.todo)
})
}
export default new Vuex.Store({
...
strict: true
})
尝试评论“严格”
以防万一还有人被这个困扰,
我通过制作商店状态的 duplicate/clone 让我的代码工作。
在你的情况下,尝试这样的事情......
computed: {
todos () {
return [ ...this.$store.state.todos.list ]
}
}
它基本上是一个扩展运算符,它会导致 todos.list 数组的克隆。这样,您就不会直接更改状态的值,只是不要忘记提交,这样您的变更就会保存在商店中。
这个错误可能是因为你 shallow cloned an object.
这意味着您已尝试复制一个对象,但该对象不是原始类型(如 String
或 Number
),因此它是通过引用而不是值传递的。
在这里,您认为您将一个对象克隆到另一个对象中,而您仍在引用旧对象。由于您要改变旧版本,您收到了这个很好的警告。
这是来自 Vue3's documentation 的 GIF(在我们的案例中仍然相关)。
在左边,它显示了一个对象(杯子)没有被正确克隆 >> 通过引用传递。
在右侧,它被正确克隆 >> 按值传递。改变这个不会改变原来的
处理此错误的正确方法是使用 lodash
,这是在 Nuxt 中高效加载它的方法:
- 安装
lodash-es
,例如:yarn add lodash-es
,这是一个优化的tree-shakable lodash ES module
- 您可能还需要在您的
nuxt.config.js
中使用以下内容转译它
build: {
transpile: ['lodash-es'],
}
- 像这样将其加载到您的
.vue
组件中
<script>
import { cloneDeep } from 'lodash-es'
...
const properlyClonedObject = cloneDeep(myDeeplyNestedObject)
...
</script>
几个关键点:
- lodash 优于
JSON.parse(JSON.stringify(object))
,因为它确实处理 some edge-cases
- 由于这个设置,我们只从 lodash 加载小函数而不是整个库,所以在性能方面没有损失
- lodash有很多久经考验的实用功能,这是JS严重缺乏的(无核心库)
如果你正在使用 Vuex Modules,如果你的模块的 data
属性 是一个对象,而不是一个函数,你可能会遇到这个错误returns 一个对象,并且您在多个商店之间共享此模块。
所以代替:
// In stores/YourModule.js
export default {
state: { name: 'Foo' },
}
改为:
// In stores/YourModule.js
export default {
state: () => {
return { name: 'Foo' };
},
}
这实际上是有记录的 here:
Sometimes we may need to create multiple instances of a module, for
example:
Creating multiple stores that use the same module (e.g. To avoid
stateful singletons in the SSR (opens new window)when the
runInNewContext option is false or 'once'); Register the same module
multiple times in the same store. If we use a plain object to declare
the state of the module, then that state object will be shared by
reference and cause cross store/module state pollution when it's
mutated.
This is actually the exact same problem with data inside Vue
components. So the solution is also the same - use a function for
declaring module state (supported in 2.3.0+):
为什么会出现此错误:
Error [vuex] Do not mutate vuex store state outside mutation handlers.
什么意思?
当我尝试在 编辑输入文件 中输入时发生这种情况。
pages/todos/index.vue
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" v-on:change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button class="destroy" v-on:click="remove(todo)">delete</button>
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
<li><input placeholder="What needs to be done?" autofocus v-model="todo" v-on:keyup.enter="add"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data () {
return {
todo: '',
editedTodo: null
}
},
head () {
return {
title: this.$route.params.slug || 'all',
titleTemplate: 'Nuxt TodoMVC : %s todos'
}
},
fetch ({ store }) {
store.commit('todos/add', 'Hello World')
},
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
},
methods: {
add (e) {
var value = this.todo && this.todo.trim()
if (value) {
this.$store.commit('todos/add', value)
this.todo = ''
}
},
toggle (todo) {
this.$store.commit('todos/toggle', todo)
},
remove (todo) {
this.$store.commit('todos/remove', todo)
},
doneEdit (todo) {
this.editedTodo = null
todo.text = todo.text.trim()
if (!todo.text) {
this.$store.commit('todos/remove', todo)
}
},
cancelEdit (todo) {
this.editedTodo = null
todo.text = this.beforeEditCache
},
},
directives: {
'todo-focus' (el, binding) {
if (binding.value) {
el.focus()
}
}
},
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
stores/todos.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
有什么办法可以解决这个问题吗?
在属于 Vuex 的一段状态上使用 v-model 可能有点棘手。
并且您在 todo.text
上使用了 v-model
:
<input class="edit" type="text" v-model="todo.text" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
使用 :value
读取值并使用 v-on:input
或 v-on:change
执行在显式 Vuex 突变处理程序中执行突变的方法
这里处理这个问题:https://vuex.vuejs.org/en/forms.html
你好,我遇到了同样的问题,使用以下方法之一克隆我的对象来解决它:
{ ...obj} //spread syntax
Object.assign({}, obj)
JSON.parse(JSON.stringify(obj))
对于你的代码,我认为你需要替换这部分
computed: {
todos () {
// console.log(this)
return this.$store.state.todos.list
}
}
有了这个
computed: {
todos () {
// console.log(this)
return {...this.$store.state.todos.list}
}
}
我不确定这是否是最好的方法,但希望这对遇到同样问题的其他人有所帮助。
会用lodash就不头疼了
computed: {
...mapState({
todo: (state) => _.cloneDeep(state.todo)
})
}
export default new Vuex.Store({
...
strict: true
})
尝试评论“严格”
以防万一还有人被这个困扰, 我通过制作商店状态的 duplicate/clone 让我的代码工作。
在你的情况下,尝试这样的事情......
computed: {
todos () {
return [ ...this.$store.state.todos.list ]
}
}
它基本上是一个扩展运算符,它会导致 todos.list 数组的克隆。这样,您就不会直接更改状态的值,只是不要忘记提交,这样您的变更就会保存在商店中。
这个错误可能是因为你 shallow cloned an object.
这意味着您已尝试复制一个对象,但该对象不是原始类型(如 String
或 Number
),因此它是通过引用而不是值传递的。
在这里,您认为您将一个对象克隆到另一个对象中,而您仍在引用旧对象。由于您要改变旧版本,您收到了这个很好的警告。
这是来自 Vue3's documentation 的 GIF(在我们的案例中仍然相关)。
在左边,它显示了一个对象(杯子)没有被正确克隆 >> 通过引用传递。
在右侧,它被正确克隆 >> 按值传递。改变这个不会改变原来的
处理此错误的正确方法是使用 lodash
,这是在 Nuxt 中高效加载它的方法:
- 安装
lodash-es
,例如:yarn add lodash-es
,这是一个优化的tree-shakable lodash ES module - 您可能还需要在您的
nuxt.config.js
中使用以下内容转译它
build: {
transpile: ['lodash-es'],
}
- 像这样将其加载到您的
.vue
组件中
<script>
import { cloneDeep } from 'lodash-es'
...
const properlyClonedObject = cloneDeep(myDeeplyNestedObject)
...
</script>
几个关键点:
- lodash 优于
JSON.parse(JSON.stringify(object))
,因为它确实处理 some edge-cases - 由于这个设置,我们只从 lodash 加载小函数而不是整个库,所以在性能方面没有损失
- lodash有很多久经考验的实用功能,这是JS严重缺乏的(无核心库)
如果你正在使用 Vuex Modules,如果你的模块的 data
属性 是一个对象,而不是一个函数,你可能会遇到这个错误returns 一个对象,并且您在多个商店之间共享此模块。
所以代替:
// In stores/YourModule.js
export default {
state: { name: 'Foo' },
}
改为:
// In stores/YourModule.js
export default {
state: () => {
return { name: 'Foo' };
},
}
这实际上是有记录的 here:
Sometimes we may need to create multiple instances of a module, for example:
Creating multiple stores that use the same module (e.g. To avoid stateful singletons in the SSR (opens new window)when the runInNewContext option is false or 'once'); Register the same module multiple times in the same store. If we use a plain object to declare the state of the module, then that state object will be shared by reference and cause cross store/module state pollution when it's mutated.
This is actually the exact same problem with data inside Vue components. So the solution is also the same - use a function for declaring module state (supported in 2.3.0+):