Javascript Vue.js 计算属性未更新
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/42678983/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Vue.js computed property not updating
提问by Jared Loomis
I'm using a Vue.js computed property but am running into an issue: The computed method ISbeing called at the correct times, but the value returned by the computed method is being ignored!
我使用的是Vue.js计算的属性,但正在运行到一个问题:计算方法IS被称为在正确的时间,但计算方法的返回值被忽略!
My method
我的方法
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
}
The values printed out by the console.logstatement are correct, but when I use filteredClassesin template, it just uses the first cached valueand never updates the template. This is confirmed by Vue chrome devtools (filteredClassesnever changes after the initial caching).
console.log语句打印出的值是正确的,但是当我filteredClasses在模板中使用时,它只使用第一个缓存的值并且从不更新模板。Vue chrome devtools 证实了这一点(filteredClasses在初始缓存后永远不会改变)。
Could anyone give me some info as to why this is happening?
谁能给我一些关于为什么会发生这种情况的信息?
Project.vue
项目.vue
<template>
<div>
<div class="card light-blue white-text">
<div class="card-content row">
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.name" id="filter-name">
<label for="filter-name">Name</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.status" id="filter-status">
<label for="filter-status">Status (PASS or FAIL)</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.apkVersion" id="filter-apkVersion">
<label for="filter-apkVersion">APK Version</label>
</div>
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.executionStatus" id="filter-executionStatus">
<label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>
</div>
</div>
</div>
<div v-for="(klass, classIndex) in filteredClasses">
<ClassView :klass-raw="klass"/>
</div>
</div>
</template>
<script>
import ClassView from "./ClassView.vue"
export default {
name: "ProjectView",
props: {
projectId: {
type: String,
default() {
return this.$route.params.id
}
}
},
data() {
return {
project: {},
filter: {
name: "",
status: "",
apkVersion: "",
executionStatus: ""
}
}
},
async created() {
// Get initial data
const res = await this.$lokka.query(`{
project(id: "${this.projectId}") {
name
classes {
name
methods {
id
name
reports
executionStatus
}
}
}
}`)
// Augment this data with latestReport and expanded
const reportPromises = []
const reportMeta = []
for(let i = 0; i < res.project.classes.length; ++i) {
const klass = res.project.classes[i];
for(let j = 0; j < klass.methods.length; ++j) {
res.project.classes[i].methods[j].expanded = false
const meth = klass.methods[j]
if(meth.reports && meth.reports.length) {
reportPromises.push(
this.$lokka.query(`{
report(id: "${meth.reports[meth.reports.length-1]}") {
id
status
apkVersion
steps {
status platform message time
}
}
}`)
.then(res => res.report)
)
reportMeta.push({
classIndex: i,
methodIndex: j
})
}
}
}
// Send all report requests in parallel
const reports = await Promise.all(reportPromises)
for(let i = 0; i < reports.length; ++i) {
const {classIndex, methodIndex} = reportMeta[i]
res.project.classes[classIndex]
.methods[methodIndex]
.latestReport = reports[i]
}
this.project = res.project
// Establish WebSocket connection and set up event handlers
this.registerExecutorSocket()
},
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
},
methods: {
isFiltered(method, klass) {
const nameFilter = this.testFilter(
this.filter.name,
klass.name + "." + method.name
)
const statusFilter = this.testFilter(
this.filter.status,
method.latestReport && method.latestReport.status
)
const apkVersionFilter = this.testFilter(
this.filter.apkVersion,
method.latestReport && method.latestReport.apkVersion
)
const executionStatusFilter = this.testFilter(
this.filter.executionStatus,
method.executionStatus
)
return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter
},
testFilter(filter, item) {
item = item || ""
let outerRet = !filter ||
// Split on '&' operator
filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>
// Split on '|' operator
seg.split("|").map(x => x.trim()).map(segment => {
let quoted = false, postOp = x => x
// Check for negation
if(segment.indexOf("!") === 0) {
if(segment.length > 1) {
segment = segment.slice(1, segment.length)
postOp = x => !x
}
}
// Check for quoted
if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {
if(segment[segment.length-1] === segment[0]) {
segment = segment.slice(1, segment.length-1)
quoted = true
}
}
if(!quoted || segment !== "") {
//console.log(`Item: ${item}, Segment: ${segment}`)
//console.log(`Result: ${item.toLowerCase().includes(segment)}`)
//console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)
}
let innerRet = quoted && segment === "" ?
postOp(!item) :
postOp(item.toLowerCase().includes(segment))
//console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)
return innerRet
}).reduce((x, y) => x || y, false)
).reduce((x, y) => x && y, true)
//console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)
return outerRet
},
execute(methID, klassI, methI) {
this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"
// Make HTTP request to execute method
this.$http.post("/api/Method/" + methID + "/Execute")
.then(response => {
}, error =>
console.log("Couldn't execute Test: " + JSON.stringify(error))
)
},
registerExecutorSocket() {
const socket = new WebSocket("ws://localhost:4567/api/Executor/")
socket.onmessage = msg => {
const {methodID, report, executionStatus} = JSON.parse(msg.data)
for(let i = 0; i < this.project.classes.length; ++i) {
const klass = this.project.classes[i]
for(let j = 0; j < klass.methods.length; ++j) {
const meth = klass.methods[j]
if(meth.id === methodID) {
if(report)
this.project.classes[i].methods[j].latestReport = report
if(executionStatus)
this.project.classes[i].methods[j].executionStatus = executionStatus
return
}
}
}
}
},
prettyName: function(name) {
const split = name.split(".")
return split[split.length-1]
}
},
components: {
"ClassView": ClassView
}
}
</script>
<style scoped>
</style>
回答by peaceman
I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret. Official docs.
我以前遇到过类似的问题,并通过使用常规方法而不是计算属性来解决它。只需将所有内容移动到一个方法中并返回您的 ret。 官方文档。
回答by So You're A Waffle Man
If your intention is for the computed property to update when project.classes.someSubPropertychanges, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.
如果您的意图是计算属性在project.classes.someSubProperty更改时更新,则在定义计算属性时必须存在该子属性。Vue 无法检测属性的添加或删除,只能检测对现有属性的更改。
This has bitten me when using a Vuex store with en empty stateobject. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.
当使用带有空state对象的 Vuex 存储时,这让我很头疼。我对状态的后续更改不会导致重新评估依赖于它的计算属性。向 Veux 状态添加具有空值的显式键解决了这个问题。
I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.
我不确定在您的情况下显式键是否可行,但它可能有助于解释计算属性过时的原因。
Vue reactiviy docs, for more info: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Vue reactiviy 文档,了解更多信息:https: //vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
回答by steeleb88
You need to assign a unique key value to the list items in the v-for. Like so..
您需要为 v-for 中的列表项分配一个唯一的键值。像这样..
<ClassView :klass-raw="klass" :key="klass.id"/>
Otherwise, Vue doesn't know which items to udpate. Explanation here https://vuejs.org/v2/guide/list.html#key
否则,Vue 不知道要更新哪些项目。这里解释https://vuejs.org/v2/guide/list.html#key
回答by Diamond
If you add console.log before returning, you may be able to see computed value in filteredClasses.
如果您在返回之前添加 console.log,您可能能够在filteredClasses.
But DOM will not updated for some reason.
但是 DOM 由于某种原因不会更新。
Then you need to force to re-render DOM.
然后你需要强制重新渲染DOM。
The best way to re-render is just adding keyas computed valuelike below.
重新渲染的最佳方法是将键添加为计算值,如下所示。
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Caution:
注意:
Don't use non-primitive values like objects and arrays as keys. Use string or numeric values instead.
不要使用非原始值(如对象和数组)作为键。请改用字符串或数字值。
That is why I converted array filteredClassesto string. (There can be other array->string convert methods)
这就是我将数组转换filteredClasses为字符串的原因。(可以有其他数组->字符串转换方法)
And I also want to say that "It is recommended to provide a key attribute with v-for whenever possible".
而且我还想说“建议尽可能提供带有 v-for 的关键属性”。
回答by Han Wang
I had this issue when the value was undefined, then computed cannot detect it changing anymore. I fixed it by giving it an empty value, or random value since it will get updated. Hope it helps.
当值未定义时我遇到了这个问题,然后计算无法检测到它的变化。我通过给它一个空值或随机值来修复它,因为它会得到更新。希望能帮助到你。
回答by Abraham Brookes
If you are adding properties to your returned object aftervue has registered the object for reactivity then it won't know to listen to those new properties when they change. Here's a similar problem:
如果在vue 为响应性注册对象后向返回的对象添加属性,那么它不会知道在这些新属性更改时监听它们。这是一个类似的问题:
let classes = [
{
my_prop: 'hello'
},
{
my_prop: 'hello again'
},
]
If I load up this array into my vue instance, vue will add those properties to its reactivity system and be able to listen to them for changes. However, if I add new properties from within my computed function:
如果我将这个数组加载到我的 vue 实例中,vue 会将这些属性添加到它的反应系统中,并能够监听它们的变化。但是,如果我从计算函数中添加新属性:
computed: {
computed_classes: {
classes.map( entry => entry.new_prop = some_value )
}
}
Any changes to new_propwon't cause vue to recompute the property, as we never actually added classes.new_propto vues reactivity system.
任何更改new_prop都不会导致 vue 重新计算属性,因为我们实际上从未添加classes.new_prop到 vues 反应系统中。
To answer your question, you'll need to construct your objects with all reactive properties present beforepassing them to vue - even if they are simply null. Anyone struggling with vues reactivity system really should read this link: https://vuejs.org/v2/guide/reactivity.html
要回答您的问题,您需要在将对象传递给 vue之前构造具有所有反应性属性的对象- 即使它们只是null. 任何在 vues 反应系统中挣扎的人都应该阅读这个链接:https: //vuejs.org/v2/guide/reactivity.html

