Computed 属性 setter 未被调用

Computed property setter is not being invoked

所以我有一个计算的 属性,它是我在 Vuex 商店中定义的一种类型的对象。我正在尝试在我的组件上使用这个 属性 和 v-model。我已经为这个 属性 定义了 getter 和 setters 并且我很确定我已经正确配置了所有内容,除非我忽略了什么。我尝试在 setter 中进行控制台登录以查看它是否曾经启动,但控制台中没有任何记录,因此根本没有被调用。任何帮助将不胜感激

商店代码:

import { Module, VuexModule, getModule, Mutation, Action } from 'vuex-module-decorators'
import { websocket } from 'src/boot/socket.io-client'
import store from 'src/store'
import { DataTablePagination } from '../types'
import { Course } from './types'
import { MessageModule } from 'src/store/message/index'
import { Notify } from 'quasar'
import { validate } from "class-validator"

export { Course } from './types'
export { DataTablePagination } from '../types'
export interface CourseState {
  pagination: DataTablePagination
  courses: Course []
  filter: string,
  disabled: boolean,
  selected: Course [],
  active: boolean,
  editCourseOpened: boolean,
  addCourseOpened: boolean,
  addCourseData: Course,
  editCourseData: Course,
  addCourseDisabled: boolean
}

@Module({
  name: 'course',
  namespaced: true,
  dynamic: true,
  store
})

class CourseClass extends VuexModule implements CourseState {
  public pagination: DataTablePagination = {
    descending: false,
    rowsNumber: 0,
    rowsPerPage: 10,
    page: 1,
    sortBy: 'name'
  }
  public courses: Course [] = []
  public filter = ''
  public disabled = true
  public selected: Course [] = []
  public active = true
  public editCourseOpened = false
  public addCourseOpened = false
  public addCourseData = new Course()
  public editCourseData = new Course()
  public addCourseDisabled = true

  @Mutation
  SET_ADDCOURSEDISABLED(disabled: boolean) {
    this.addCourseDisabled=disabled
  }

  @Mutation
  SET_EDITCOURSEDATA(data: Course) {
    this.editCourseData.copy(data)
  }

  // public SET_ADDCOURSEDATA(payload: { key: string, value: number | string }) {
  //   const { key, value } = payload
  //   if (Object.prototype.hasOwnProperty.call(this, key)) {
  //     // eslint-disable-next-line @typescript-eslint/no-explicit-any
  //     (this as any)[key] = value
  //   }
  // }
  @Mutation
  SET_ADDCOURSEDATA(data: Course) {
    this.addCourseData.copy(data)
  }

  @Mutation
  SET_EDITCOURSEOPENED(opened: boolean) {
    this.editCourseOpened=opened
  } 

  @Mutation
  SET_ADDCOURSEOPENED(opened: boolean) {
    this.addCourseOpened=opened
  }

  @Mutation
  SET_ACTIVE(active: boolean) {
    this.active=active
  }

  @Mutation
  SET_PAGINATION(pagination: DataTablePagination) {
    this.pagination=pagination
  }

  @Mutation
  SET_SELECTED(selected: Course []) {
    this.selected=selected
  }

  @Mutation
  SET_FILTER(filter: string) {
    this.filter=filter
  }

  @Mutation
  SET_COURSES(courses: Course []) {
    this.courses=courses
  }

  @Mutation
  SET_DISABLED(disabled: boolean) {
    this.disabled=disabled
  }

  @Action
  public ValidateAddCourseData() {
    validate(this.addCourseDisabled).then(errors => {
      console.log(errors)
      if(errors.length) {
        this.SET_ADDCOURSEDISABLED(true)
      } else {
        this.SET_ADDCOURSEDISABLED(false)
      }
    })
  }

  @Action 
  public async addCourse(input: Course) {
    websocket.emit('query', `mutation {
      createCourse (
        course: {
          code: "${input.code}"
          name: "${input.name}"
          creditHours: ${input.creditHours}
          numberOfLabs: ${input.numberOfLabs}
          contactHours: ${input.contactHours}
          chargeableCredits: 0
        }
      ) {
        ok
        message
      }
  }`, (response: { 
    errors: any
    data: { 
      createAcademicProgram: { 
        ok: boolean
        message: String 
      } 
    } 
  }) => {
      if(response.data) {
        this.fetchCourses()
        Notify.create({
          timeout: 3000,
          position: 'center',
          color: 'primary',
          message: 'Course Added Successfully'
        })
        this.SET_ADDCOURSEOPENED(!this.addCourseOpened)
      }
      else {
        MessageModule.newMessage({title: 'Error: Addition Failed', icon: 'error', message: 'Addition failed. Ensure that the course code you have entered is unique or contact your system administrator'})
      }
    })
  }

  @Action 
  public async deleteCourse(input: Course) {
    websocket.emit('query', `mutation {
      deleteCourse (
        courseId: "${input.id}"
      )  {
        id
        ok
        message
      }
    }`, (response: { 
      errors: any; 
      data: { 
        deleteCourse: { 
          id: any; 
          ok: boolean; 
          message: String 
        } 
      } 
    }) => {
      if(response.data) {
        this.fetchCourses()
        Notify.create({
          timeout: 2000,
          position: 'center',
          color: 'primary',
          message: 'Course Deleted Successfully'
        })
      }
      else {
        MessageModule.newMessage({ title: 'Deletion Failed', icon: 'error', message: 'Deletion Failed. Ensure that no course instances are still tied to this course or contact your system administrator' })
      }
    })
  }

  @Action
  public async editCourse(input: Course) {
    websocket.emit('query', `mutation {
      updateCourse (
        course: {
          id: "${input.id}"
          code: "${input.code}"
          name: "${input.name}"
          creditHours: ${input.creditHours}
          numberOfLabs: ${input.numberOfLabs}
          contactHours: ${input.contactHours}
          chargeableCredits: 0
        }
      ) {
        id
        ok
        message
      }
    }`, (response: {
      errors: any
      data: {
        updateCourse: {
          id: string
          ok: boolean
          message: string
        }
      }
    }) => {
      if(response.data) {
        this.fetchCourses()
        this.SET_SELECTED([input])
        this.SET_EDITCOURSEOPENED(!this.editCourseOpened)
        Notify.create({
          timeout: 3000,
          color: 'primary',
          message: 'Course Updated Successfully',
          position: 'center'
        })
      }
      else {
        MessageModule.newMessage({ title: 'Update Failed', icon: 'error', message: 'Update Failed. Ensure that all course codes are unique or contact your system administrator'})
      }
    })
  }

  @Action 
  public async fetchCourses() {
    const order = this.pagination.sortBy !== null ? `order: {
      by: ${this.pagination.sortBy}
      dir: ${this.pagination.descending ? 'DESC' : 'ASC'}
    }` : ''
    websocket.emit('query', `{
      courses(
        page: {
          skip: ${(this.pagination.page - 1) * this.pagination.rowsPerPage},
          first: ${this.pagination.rowsPerPage}
        }
        filter: {
          ilike: {
            name: "${this.filter}"
          }
        }
        ${order}
      ) {
        pagination {
          total
          listTotal
        }
        list {
          id
          code
          name
          creditHours
          numberOfLabs
          contactHours
        }
      }
    }`, (response: {
      errors: any
      data: {
        courses: {
          list: Course[]
          pagination: {
            total: number
            listTotal: number
          }
        }
      }
    }) => {
      this.SET_COURSES(response.data.courses.list)
      this.pagination.rowsNumber = response.data.courses.pagination.total
    })
  }
}

export const CourseModule = getModule(CourseClass)

组件代码:

<template>
  <q-dialog v-model="isOpened" :bordered="true">
    <q-card style="width: 50vw;">
      <q-toolbar class="bg-grey-5 text-center">
        <q-toolbar-title>Add Course</q-toolbar-title>
        <q-btn flat round dense icon="close" v-close-popup />
      </q-toolbar>
      <q-card-section class="col">
        <q-input v-model.trim="course.name" label="Name" type="object"
        :rules="[
          val => val.length > 0 || 'Required field'
        ]"/>
        <q-input v-model="course.code" label="Code"
        :rules="[
          val => val.length > 0 || 'Required field'
        ]"/>
        <q-input v-model.number="course.creditHours" label="Credit Hours" type="number"
        :rules="[
          val => val >= 0 || 'No negatives allowed'
        ]"/>
        <q-input v-model.number="course.numberOfLabs" label="Number of Labs" type="number"
        :rules="[
          val => val >= 0 || 'No negatives allowed'
        ]"/>
        <q-input v-model.number="course.contactHours" label="Contact Hours" type="number"
        :rules="[
          val => val >= 0 || 'No negatives allowed'
        ]"/>
        <q-card-actions align="right">
          <q-btn color="primary" :disable="disabled" @click="submit();">
            <q-icon name="save" />
            <q-tooltip
              anchor="top middle"
              self="bottom middle"
              :offset="[10, 10]"
            >
              <strong>Add Course</strong>
            </q-tooltip>
          </q-btn>
          <q-btn color="red" @click="toggle()" icon="cancel">
            <q-tooltip
            anchor="top middle"
            self="bottom middle"
            :offset="[10, 10]"
            >
              <strong>Cancel</strong>
            </q-tooltip>
          </q-btn>
        </q-card-actions>
      </q-card-section>
    </q-card>
  </q-dialog>
</template>

<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator'
import { CourseModule, Course } from 'src/store/course/index'

@Component
export default class AddCourse extends Vue {
  //private course: Course = new Course()
  //private disabled = true

  get disabled() {
    return CourseModule.addCourseDisabled
  }

  get course() {
    return CourseModule.addCourseData
  }

  set course(args: any) {
    console.log(args)
    //CourseModule.SET_ADDCOURSEDATA(newValue)
  }

  get isOpened() {
    return CourseModule.addCourseOpened
  }

  set isOpened(newValue: boolean) {
    CourseModule.SET_ADDCOURSEOPENED(newValue)
  }

  toggle() {
    CourseModule.SET_ADDCOURSEDATA(this.course)
    this.isOpened = !this.isOpened
    // if(this.isOpened) {
    //   this.getdisabled()
    // }
    // else {
    //   this.course.clear()
    // }
  }

  submit() {
    CourseModule.addCourse(this.course)
    //this.toggle()
  }

  // getdisabled() {
  //   validate(this.course).then(errors => {
  //     if(errors.length) {
  //       this.disabled=true
  //     } else {
  //       this.disabled=false
  //     }
  //   })
  //   return true
  // }
}
</script>

正如您已经注意到的,这里的标准解决方案是为每个表单域计算一个 属性,每个表单域都有 getset商店。

一个可行的替代方法是使用 Proxy 拦截对象属性的设置。我不知道如何用 TypeScript 写这个,但这不应该影响核心思想:

const store = new Vuex.Store({
  state: {
    course: {
      name: 'Algebra',
      teacher: 'Smith'
    }
  },
  
  mutations: {
    course (state, course) {
      state.course = course
    }
  }
})

new Vue({
  el: '#app',
  store,
  
  computed: {
    course () {
      const store = this.$store
      const course = store.state.course
      
      return new Proxy(course, {
        set (obj, key, value) {
          store.commit('course', { ...obj, [key]: value })          
          return true
        }
      })
    }
  }
})
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>

<div id="app">
  <input v-model="course.name">
  <br>
  <input v-model="course.teacher">
  <br>
  <p>{{ course.name }}</p>
  <p>{{ course.teacher }}</p>
</div>

如果您需要支持 IE,那么您将无法使用 Proxy,但应该可以使用标准 JavaScript 属性实现类似的功能。这个想法与正确的 Proxy 非常相似,但您必须在 'proxy' 对象上显式创建 getset 处理程序。