Ich verwende eine berechnete Eigenschaft von Vue.js, stoße jedoch auf ein Problem: Die berechnete Methode IST wird zum richtigen Zeitpunkt aufgerufen, aber der Wert wird von zurückgegeben Die berechnete Methode wird ignoriert!
Meine Methode
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
}
}
Die Werte, die mit dem console.log
-Anweisungen sind korrekt, aber wenn ich filteredClasses
in der Vorlage verwende, wird nur erster zwischengespeicherter Wert verwendet, und die Vorlage wird nie aktualisiert. Dies wird durch Vue chrome devtools (filteredClasses
ändert sich nie nach dem anfänglichen Caching)) bestätigt.
Kann mir jemand sagen, warum das so ist?
Project.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>
Ich bin bereits auf ein ähnliches Problem gestoßen und habe es mithilfe einer regulären Methode anstelle einer berechneten Eigenschaft gelöst. Verschieben Sie einfach alles in eine Methode und geben Sie Ihren ret zurück. Offizielle Dokumente
Sie müssen den Listenelementen im v-for einen eindeutigen Schlüsselwert zuweisen. Wie so ..
<ClassView :klass-raw="klass" :key="klass.id"/>
Andernfalls weiß Vue nicht, welche Elemente aktualisiert werden sollen. Erläuterung hier https://vuejs.org/v2/guide/list.html#key
Wenn die berechnete Eigenschaft aktualisiert werden soll, wenn project.classes.someSubProperty
ändert sich, diese Untereigenschaft muss existieren, wenn die berechnete Eigenschaft definiert wird. Vue kann das Hinzufügen oder Löschen von Eigenschaften nicht erkennen , nur Änderungen an vorhandenen Eigenschaften.
Dies hat mich bei der Verwendung eines Vuex-Speichers mit einem leeren state
-Objekt gebissen. Meine nachfolgenden Statusänderungen würden nicht dazu führen, dass berechnete Eigenschaften, die davon abhängen, erneut ausgewertet werden. Das Hinzufügen expliziter Schlüssel mit Nullwerten zum Veux-Status löste dieses Problem.
Ich bin mir nicht sicher, ob in Ihrem Fall explizite Schlüssel möglich sind, aber es kann hilfreich sein, zu erklären, warum die berechnete Eigenschaft veraltet ist.
Vue Reaktivitätsdokumente für weitere Informationen: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Wenn Sie console.log hinzufügen, bevor Sie zurückkehren, wird der berechnete Wert möglicherweise in filteredClasses
angezeigt.
DOM wird jedoch aus irgendeinem Grund nicht aktualisiert.
Dann müssen Sie erzwingen, DOM neu zu rendern.
Der beste Weg zum erneuten Rendern ist einfach Schlüssel hinzufügen als berechneter Wert wie unten.
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Achtung:
Verwenden Sie keine nicht-primitiven Werte wie Objekte und Arrays als Schlüssel. Verwenden Sie stattdessen Zeichenfolgen oder numerische Werte.
Aus diesem Grund habe ich das Array filteredClasses
in einen String konvertiert. (Es kann auch andere Array-> String-Konvertierungsmethoden geben.)
Und ich möchte auch sagen: "Es wird empfohlen, wann immer möglich ein Schlüsselattribut mit v-for bereitzustellen.".