Выпущенное свойство 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>

Ответ 1

Я сталкивался с подобной проблемой раньше и решил ее, используя обычный метод вместо вычисляемого свойства. Просто переместите все в метод и верните ваш ответ. Официальные документы.

Ответ 2

Вам нужно назначить уникальное значение ключа для элементов списка в v-for. Так вот..

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

В противном случае Vue не знает, какие элементы будут удалять. Объяснение здесь https://vuejs.org/v2/guide/list.html#key

Ответ 3

Если вы намерены обновить вычисляемое свойство при изменении project.classes.someSubProperty, то это вспомогательное свойство должно существовать при определении вычисляемого свойства. Vue не может обнаружить добавление или удаление свойств, только изменения существующих свойств.

Это укусило меня при использовании хранилища Vuex с пустым объектом state. Мои последующие изменения в состоянии не приведут к вычисленным свойствам, которые зависят от его переоценки. Добавление явных ключей с нулевыми значениями в состояние Veux решило эту проблему.

Я не уверен, возможны ли явные ключи в вашем случае, но это может помочь объяснить, почему вычисляемое свойство устарело.

Для получения дополнительной информации см. Https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats.

Ответ 4

Если вы добавите console.log перед возвратом, вы можете увидеть вычисленное значение в filteredClasses.

Но DOM не будет обновляться по какой-то причине.

Затем вам нужно принудительно перерисовать DOM.

Лучший способ выполнить рендеринг - просто добавить ключ в виде вычисленного значения, как показано ниже.

<div
  :key="JSON.stringify(filteredClasses)" 
  v-for="(klass, classIndex) in filteredClasses"
>
  <ClassView
    :key="classIndex"
    :klass-raw="klass"
  />
</div>

Осторожно:

Не используйте непримитивные значения, такие как объекты и массивы, в качестве ключей. Вместо этого используйте строковые или числовые значения.

Вот почему я преобразовал массив filteredClasses в строку. (Могут быть другие array-> методы преобразования строк)

И я также хочу сказать, что "Рекомендуется предоставлять ключевой атрибут с v-for, когда это возможно".