web-dev-qa-db-ja.com

Vue.js計算プロパティが更新されない

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>
13
Jared Loomis

以前に同様の問題に遭遇し、計算されたプロパティの代わりに通常のメソッドを使用して解決しました。すべてをメソッドに移動して、retを返します。 公式ドキュメント

11
peaceman

V-forのリストアイテムに一意のキー値を割り当てる必要があります。そのようです..

<ClassView :klass-raw="klass" :key="klass.id"/>

それ以外の場合、Vueはどのアイテムを更新するかを知りません。ここでの説明 https://vuejs.org/v2/guide/list.html#key

3
steeleb88

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を指定することをお勧めします」とも言いたいと思います。

0
Antonio