【问题标题】:computed property set not called in VueVue 中未调用计算属性集
【发布时间】:2020-10-30 08:19:39
【问题描述】:

根据文档,只要我定义 get/set 方法,我应该能够在 Vue 中使用计算属性作为 v-model,但在我的情况下它不起作用:

export default{

    template: `
      <form class="add-upload" @submit.prevent="return false">
        <label><input type="checkbox" v-model="options.test" /> test </label>
      </form>
    `,

    computed: {

      options: {

        get(){
          console.log('get');
          return {test: false};
        },

        set(value){
          console.log('set');
        },

      },

    }

}

当我选中/取消选中输入时,显然不会调用 set。 但是在显示组件的时候会调用get...

【问题讨论】:

  • 这里 option 是被动的,而不是 options.test
  • 这是否意味着我需要使用 get/set 为每个键定义计算属性?那将迫使我编写大量重复的代码。我这里只有test,但我计划添加更多输入字段..
  • 使用表单提交。因为该复选框将单独更新表单值
  • 我不明白?
  • 那么为什么要使用 v-model 的计算属性呢?定义数据有什么问题?这里的问题是v-model 尝试在options.test 上设置值,但您的计算属性正在options 上设置新值。不确定这是否会自动工作。

标签: javascript vue.js computed-properties


【解决方案1】:

代替computed getter/setter,使用本地数据道具,初始化为目标localStorage项目;和一个deep watcher(检测任何子属性的更改)在更改时设置localStorage。这使您仍然可以将v-model 与本地数据属性一起使用,同时观察对象子属性的变化。

步骤:

  1. 声明一个本地数据属性(名为options),初始化为localStorage的当前值:
export default {
  data() {
    return {
      options: {}
    }
  },
  mounted() {
    const myData = localStorage.getItem('my-data')
    this.options = myData ? JSON.parse(myData) : {}
  },
}
  1. 在 data 属性 (options) 上声明一个监视,将 deep=truehandler 设置为将 localStorage 设置为新值的函数:
export default {
  watch: {
    options: {
      deep: true,
      handler(options) {
        localStorage.setItem('my-data', JSON.stringify(options))
      }
    }
  },
}

demo

【讨论】:

    【解决方案2】:

    编辑看了下你依赖localstorage的cmets,我只能建议你采取Vuex的方式,用一个持久化库来处理localstorage。 (https://www.npmjs.com/package/vuex-persist) 这样,您的本地存储将始终链接到您的应用程序,您不必每次都弄乱 getItem/setItem。

    看看你的方法,我认为你有理由使用计算属性而不是数据属性。

    出现问题是因为您的计算属性返回了一个在 get 处理程序中没有定义的对象。 无论您尝试什么,都无法在 set 处理程序中操作该对象。

    getset 必须链接到一个公共引用。正如许多人所建议的那样,您的应用程序中的数据属性或事实来源(Vuex 实例就是一个很好的例子)。

    这样,您的 v-model 将与您的计算属性的 set 处理程序完美配合。

    这是一个演示解释的工作小提琴:

    使用 Vuex

    const store = new Vuex.Store({
      state: {
        // your options object is predefined in the store so Vue knows about its structure already
        options: {
          isChecked: false
        }
      },
      mutations: {
        // the mutation handler assigning the new value
        setIsCheck(state, payload) {
          state.options.isChecked = payload;
        }
      }
    });
    
    new Vue({
      store: store,
      el: "#app",
      computed: {
        options: {
          get() {
            // Here we return the options object as depicted in your snippet
            return this.$store.state.options;
          },
          set(checked) {
            // Here we use the checked property returned by the input and we commit a Vuex mutation which will mutate the state
            this.$store.commit("setIsCheck", checked);
          }
        }
      }
    })
    body {
      background: #20262E;
      padding: 20px;
      font-family: Helvetica;
    }
    
    #app {
      background: #fff;
      border-radius: 4px;
      padding: 20px;
      transition: all 0.2s;
    }
    
    h2 {
      font-weight: bold;
      margin-bottom: 15px;
    }
    <div id="app">
      <h2>isChecked: {{ options.isChecked }}</h2>
      <input type="checkbox" v-model="options.isChecked" />
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <script src="https://unpkg.com/vuex@2.0.0"></script>

    带有数据属性

    new Vue({
      el: "#app",
      data: {
        options: {
          isChecked: false
        }
      },
      computed: {
        computedOptions: {
          get() {
            return this.options;
          },
          set(checked) {
            this.options.isChecked = checked;
          }
        }
      }
    })
    body {
      background: #20262E;
      padding: 20px;
      font-family: Helvetica;
    }
    
    #app {
      background: #fff;
      border-radius: 4px;
      padding: 20px;
      transition: all 0.2s;
    }
    
    h2 {
      font-weight: bold;
      margin-bottom: 15px;
    }
    <div id="app">
      <h2>isChecked: {{ computedOptions.isChecked }}</h2>
      <input type="checkbox" v-model="computedOptions.isChecked" />
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

    恕我直言,您的方法有点特别,但同样,您必须有自己的理由这样做。

    【讨论】:

      【解决方案3】:

      看来问题在于options 的存在和getter 的返回值。

      你可以试试这个:

      let options;
      
      try {
        options = JSON.parse(localStorage.getItem("options"));
      }
      catch(e) {
        // default values
        options = { test: true };
      }
      
      function saveOptions(updates) {
        localStorage.setItem("options", JSON.stringify({ ...options, ...updates }));
      }
      
      export default{
        template: `
          <form class="add-upload" @submit.prevent="return false">
            <label><input type="checkbox" v-model="test" /> test </label>
          </form>`,
        computed: {
          test: {
            get() {
              console.log('get');
              return options.test;
            },
            set(value) {
              console.log('set', value);
              saveOptions({ test: value });
            },
          },
        }
      }
      

      希望这会有所帮助。

      【讨论】:

        【解决方案4】:

        这里的代码非常简单的解释。计算属性取决于其他数据/反应变量。如果只有当反应性属性改变了它们的值,并且如果相同的属性用于计算其他一些计算属性,那么计算属性就会变成反应性的。

        这样我们必须设置值并在 setter 和 getter 方法中获取。

        new Vue({
          el: '#app',
          data: {
            message: 'Use computed property on input',
            foo:0,
            isChecked:true
          },
          computed:{
           bar:{
            get: function(){
                return this.foo;
            },
           set: function(val){
             this.foo = val;
            }
           },
           
            check:{
            get: function(){
                return this.isChecked;
            },
           set: function(val){
             this.isChecked = val;
            }
           }
          }
        })
        <script src="https://unpkg.com/vue"></script>
        
        <div id="app">
          <p>{{ message }} Text</p>
          <input type="text" v-model="bar" /> 
          {{bar}}
        
        <br/>
         <p>{{ message }} Checkbox</p>
            <input type="checkbox" v-model="check" /> 
            
            {{check}}
        </div>

        【讨论】:

          【解决方案5】:

          我不熟悉是否有可以在这里工作的计算集方法,但还有一些其他方法可以解决这个问题。

          如果您想要一个单一的 getter 来改变数据,您可以使用基于事件的方法来设置数据。这个方法是我最喜欢的:

          export default {
            template: `
                <form class="add-upload" @submit.prevent="">
                  <label for="test"> test </label>
                  {{options.test}}
                  <input id="test" type="checkbox" v-model="options.test" @input="setOptions({test: !options.test})"/>
                </form>
              `,
            data() {
              return {
                optionsData: {
                  test: false
                }
              }
            },
            computed: {
              options: {
                get() {
                  return this.optionsData;
                },
              },
            },
            methods: {
              setOptions(options) {
                this.$set(this, "optionsData", { ...this.optionsData, ...options })
              }
            }
          }
          

          如果您在 get/set 中没有真正做任何事情,您可以使用 data 选项

          export default {
            template: `
                <form class="add-upload" @submit.prevent="">
                  <label for="test"> test </label>
                  {{options.test}}
                  <input id="test" type="checkbox" v-model="options.test" />
                </form>
              `,
            data() {
              return {
                options: {
                  test: false
                }
              }
            }
          }
          

          然后还有每个属性的get/set选项

          export default {
            template: `
                <form class="add-upload" @submit.prevent="">
                  <label for="test"> test </label>
                  {{test}}
                  <input id="test" type="checkbox" v-model="test" />
                </form>
              `,
            data() {
              return {
                optionsData: {
                  test: false
                }
              }
            },
            computed: {
              test: {
                get() {
                  return this.optionsData.test;
                },
                set(value) {
                  this.optionsData.test = value
                }
              },
            },
          }
          

          【讨论】:

            【解决方案6】:

            Vue 计算属性的返回值不会自动变为响应式。因为您返回的是一个普通对象,并且因为您要分配给计算属性的属性,所以 setter 不会触发。

            您有两个问题需要解决,一个问题的解决方案是存储您计算的属性值的反应版本(请参阅Vue.observable())。下一个问题有点微妙,我需要知道为什么你想加入二传手。如果没有更多信息,我最好的猜测是您实际上正在寻找执行副作用。在这种情况下,您应该注意更改的值(请参阅vm.$watch())。

            这是我根据上述假设编写该组件的方式。

            export default {
              template: `
                  <form class="add-upload" @submit.prevent="return false">
                    <label><input type="checkbox" v-model="options.test" /> test </label>
                  </form>
                `,
              computed: {
                options(vm) {
                  return (
                    vm._internalOptions ||
                    (vm._internalOptions = Vue.observable({ test: false }))
                  )
                },
              },
              watch: {
                "options.test"(value, previousValue) {
                  console.log("set")
                },
              },
            }
            

            如果您需要根据options 上的任何变化触发副作用,您可以深入观看。但最大的警告是对象必须是反应式的(通过Vue.observable() 解决或在data 选项中定义它)。

            export default {
              watch: {
                options: {
                  handler(value, previousValue) {
                    console.log("set")
                  },
                  deep: true,
                },
              },
            }
            

            【讨论】:

              猜你喜欢
              • 2021-12-17
              • 2019-10-03
              • 2020-02-03
              • 2020-12-01
              • 2020-08-23
              • 2021-05-04
              • 2022-11-11
              • 2018-02-16
              相关资源
              最近更新 更多