我有一个系统,它使用大量表单组件来根据输入的信息实时更新 View 。当我第一次构建应用程序时,我主要使用 v-model、本地组件数据和 vue 保存状态来将数据保存在本地存储中,以便在重新加载页面时数据得以保留。
然而,当我随着程序扩展而转向 vuex 时,这并不是那么简单,所以我决定使用带有 getter 和 setter 的计算属性的 v-model,这样我就不必编写超过 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)
}
},
这工作得很好,商店更新每个属性的数据,而无需创建自己单独的计算变量,但由于某种原因,它不会在 vue chrome 开发工具上显示为提交的突变“updateInfo”,并且当我使用 vuex 的本地存储插件(如 vuex-persistedstate 或 vuex-persist)时,它不会更改本地存储数据,直到我提交另一个正常结构的突变。现在我的解决方法是在组件中创建属性的本地副本,然后观察该属性的更改并提交到商店,这让我可以再次使用组件级别的本地存储混合,但我觉得必须有一个更好的执行此操作的方法不涉及为 info 中的每个属性编写更改函数或计算变量,因为这在此应用程序中会非常冗长。
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
,而是它的深层嵌套属性(例如 名称
)。
最简单的解决方案是放弃 v-model
并使用 :value
和@input
绑定(bind)。示例:
<textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" >
请注意,它使用 updateProf
方法,该方法提交突变(见下文)并进入 mixin。
这样所有修改都在突变内完成。最后一点,如果您使用 :value
和 @input
verbose,您可以创建一个 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
的一种方法。这个替代方案的关键点是进行深度克隆和深度等于的函数。我提供了两个简单/幼稚的实现,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>
关于javascript - 突变未在 Vuex 中使用计算属性进行注册,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49565655/