Vue.js計算プロパティを使用していますが、問題が発生しています:計算メソッド[〜#〜] is [〜#〜]は正しい時刻に呼び出されますが、計算されたメソッドは無視されています!
私の方法
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
}
}
console.log
ステートメントによって出力される値は正しいですが、テンプレートでfilteredClasses
を使用すると、最初にキャッシュされた値が使用され、テンプレートは更新されません。これは、Vue chrome devtools(filteredClasses
初期キャッシュ後に変更されることはありません)によって確認されます。
なぜこれが起こっているのかについて誰かに情報を教えてもらえますか?
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>
以前に同様の問題に遭遇し、計算されたプロパティの代わりに通常のメソッドを使用して解決しました。すべてをメソッドに移動して、retを返します。 公式ドキュメント
V-forのリストアイテムに一意のキー値を割り当てる必要があります。そのようです..
<ClassView :klass-raw="klass" :key="klass.id"/>
それ以外の場合、Vueはどのアイテムを更新するかを知りません。ここでの説明 https://vuejs.org/v2/guide/list.html#key
project.classes.someSubProperty
が変更されたときに計算プロパティを更新する場合は、計算プロパティが定義されたときにそのサブプロパティが存在する必要があります。 Vueはプロパティの追加または削除を検出できません。既存のプロパティへの変更のみです。
空のstate
オブジェクトでVuexストアを使用するとき、これは私に噛みつきました。状態に対するその後の私の変更は、再評価されることに依存する計算されたプロパティにはなりません。 null値を持つ明示的なキーをVeux状態に追加すると、その問題は解決しました。
あなたの場合、明示的なキーが実行可能かどうかはわかりませんが、計算されたプロパティが古くなる理由を説明するのに役立つかもしれません。
詳細については、Vue reactiviyドキュメント: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
戻る前にconsole.logを追加すると、filteredClasses
に計算値が表示される場合があります。
ただし、DOMは何らかの理由で更新されません。
次に、DOMの再レンダリングを強制する必要があります。
再レンダリングする最良の方法は、次のようにキーを追加として計算値とするだけです。
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
注意:
オブジェクトや配列などの非プリミティブ値をキーとして使用しないでください。代わりに文字列または数値を使用してください。
それが、配列filteredClasses
を文字列に変換した理由です。 (他の配列->文字列変換メソッドがあります)
また、「可能な場合はいつでもキー属性にv-forを指定することをお勧めします」とも言いたいと思います。