【问题标题】:Vue.js Changing propsVue.js 更改道具
【发布时间】:2017-04-08 10:08:51
【问题描述】:

我对如何更改组件内部的属性有点困惑,假设我有以下组件:

{
    props: {
        visible: {
            type: Boolean,
            default: true
        }
    },
    methods: {
         hide() {
              this.visible = false;
         }
    }
} 

虽然它有效,但它会给出以下警告:

避免直接修改 prop,因为每当父组件重新渲染时,该值都会被覆盖。相反,使用基于道具值的数据或计算属性。正在变异的道具:“可见” (在组件中找到)

现在我想知道最好的处理方法是什么,显然visible 属性是在 DOM 中创建组件时传入的:<Foo :visible="false"></Foo>

【问题讨论】:

  • 你为什么要编辑道具?它应该(代码方面)从一个地方控制,或者从父组件的组件 og 中控制? (您可以通过将更新方法作为道具传递来进行组合,在您的情况下,hide() 可以在父级上,然后将引用作为道具发送。
  • @ArneHugo 正如你所看到的,组件本身有一个基本上“显示/隐藏”元素的方法。父级也可以更新此属性。把它想象成一个警告信息,如果它是可见的,父级可以控制它,组件本身可以删除自己。
  • 是的,我明白了。我的建议是使用道具visible(布尔值)和hide(函数)。然后在父级上定义hide,它也拥有visible 的状态。这样你就不会编辑 props,而是编辑父状态,这是允许的。
  • 也许你可以创建一个fiddle 来显示你到底在做什么,我可以改变它来告诉你我的意思。
  • @ArneHugo 我很快就会整理一个例子

标签: javascript vue.js vue-component


【解决方案1】:

引用your fiddle中的代码

不知何故,你应该决定一个州居住的地方,而不是两个。我不知道将它放在 Alert 中还是放在它的父级中是否更适合您的用例,但您应该选择一个。

如何决定状态在哪里

父组件或任何兄弟组件是否依赖于状态?

  • 是的:那么它应该在父级中(或在某些外部状态管理中)
  • 否:那么让它处于组件本身的状态会更容易
  • 两者兼而有之:见下文

在极少数情况下,您可能需要组合。也许你想让父母和孩子都能够隐藏孩子。那么你应该在父母和孩子中都有状态(所以你不必在孩子内部编辑孩子的道具)。

例如,孩子可以在以下情况下可见:visible && state_visible,其中visible 来自道具并反映父状态中的值,state_visible 来自孩子状态。

我不确定这是否是您想要的行为,但这是一个 sn-p。我有点假设您实际上只想在单击子组件时调用父组件的toggleAlert

var Alert = Vue.component('alert', {
  template: `
        <div class="alert" v-if="visible && state_visible">
        Alert<br> 
        <span v-on:click="close">Close me</span>
      </div>`,
  props: {
    visible: {
      required: true,
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      state_visible: true
    };
  },
  methods: {
    close() {
      console.log('Clock this');
      this.state_visible = false;
    }
  }
});

var demo = new Vue({
  el: '#demo',
  components: {
    'alert': Alert
  },
  data: {
    hasAlerts: false
  },
  methods: {
    toggleAlert() {
      this.hasAlerts = !this.hasAlerts
    }
  }
})
.alert {
  background-color: #ff0000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" v-cloak>
  <alert :visible="hasAlerts"></alert>

  <span v-on:click="toggleAlert">Toggle alerts</span>
</div>

【讨论】:

  • 我完全明白你的意思,但我觉得这是很常见的行为,不是吗?在我的父组件中拥有所有逻辑意味着必须将该逻辑复制/粘贴到所有使用Alert 的许多不同组件上。你的小提琴是有道理的,现在看来它是最好的选择。
  • 作为表单的子组件呢?也许您想将数据从父级传递给子级,能够对其进行编辑,然后如果发布成功,将更新后的数据传回父级?
  • 即使您希望父级拥有一个不会更改的最终副本,除非子级成功更新?不过,您的观点是有道理的,因为如果存在复杂的数据结构并且您只想更新其中的一部分,那么如果您必须传递数据,就很难获得替换正确部分的逻辑。
  • @AllenWang 如果您需要一份编辑前的数据副本,我会在组件之外的商店中进行。这样,保存副本的逻辑就存在于商店中,并且组件更容易理解。将原件视为事实的来源,将副本视为草稿。然后您可以在编辑模式下显示草稿,在演示模式下显示真相。
  • @AllenWang 但是如果您不想使用存储,请将原始数据和副本都保留在父组件中,这样很容易理解数据的位置。
【解决方案2】:

如果 prop 只对这个子组件有用,给子组件一个 propinitialVisible,和一个 datamutableVisible,并在 created 钩子中(在组装组件的数据结构时调用),只需this.mutableVisible = this.initialVisible

如果 prop 被父组件的其他子组件共享,您需要将其设为父组件的 data 以使其可用于所有子组件。然后在孩子中,this.$emit('visibleChanged', currentVisible) 通知父母更改visible。在父母的模板中,使用&lt;ThatChild ... :visibleChanged="setVisible" ...&gt;。看攻略:http://vuejs.org/v2/guide/components.html

【讨论】:

    【解决方案3】:

    the Vue.js component doc

    当父属性更新时,它会向下流向子属性,但不会反过来。那么,当事情发生时,我们如何与父母沟通呢?这就是 Vue 的自定义事件系统的用武之地。

    使用子级的$emit('my-event) 向父级发送事件。使用v-on:my-event(或@my-event)在父内部的子声明中接收事件。

    工作示例:

    // child
    
    Vue.component('child', {
      template: '<div><p>Child</p> <button @click="hide">Hide</button></div>',
      methods: {
        hide () {
          this.$emit('child-hide-event')
        }
      },
    })
    
    // parent
    
    new Vue({
      el: '#app',
      data: {
        childVisible: true
      },
      methods: {
        childHide () {
          this.childVisible = false
        },
        childShow () {
          this.childVisible = true
        }
      }
    })
    .box {
      border: solid 1px grey;
      padding: 16px;
    }
    <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
    <div id="app" class="box">
      <p>Parent | childVisible: {{ childVisible }}</p>
      <button @click="childHide">Hide</button>
      <button @click="childShow">Show</button>
      <p> </p>
      <child @child-hide-event="childHide" v-if="childVisible" class="box"></child>
    </div>

    【讨论】:

      【解决方案4】:

      阅读您最新的 cmets 后,您似乎担心是否有逻辑显示/隐藏父级警报。因此,我建议如下:

      父母

      # template
      <alert :alert-visible="alertVisible"></alert>
      
      # script
      data () {
        alertVisible: false,
        ...
      },
      ...
      

      然后在子警报上,您将 $watch 道具的值并将所有逻辑移动到警报中:

      孩子(警报)

      # script
      data: {
        visible: false,
        ...
      },
      methods: {
        hide () {
          this.visible = false
        },
        show () {
          this.visible = true
        },
        ...
      },
      props: [
        'alertVisible',
      ],
      watch: {
        alertVisible () {
          if (this.alertVisible && !this.visible) this.show()
          else if (!this.alertVisible && this.visible) this.hide()
        },
        ...
      },
      ...
      

      【讨论】:

      • 已更新,允许您在父级上设置参数以控制任何子级警报。
      • 是的,要记住的一件事是您只会使用父级上的参数 (alertVisible) 来设置状态,您将无法可靠地读取从中。如果你想这样做,那么你开始需要事件或传递警报调用的其他方法道具(显示/隐藏等) - 但出于警报的目的,父级不太可能真的需要知道它的状态,因此它应该在大多数情况下运行良好,无需额外的逻辑。还允许您轻松控制来自父母的多个警报,这是一个奖励。
      • 我希望手表只能在第一次使用。因此,如果多次显示警报(通常情况下),此解决方案将不起作用。否则父母仍然需要一种方法来重置为原始值(并知道何时)
      【解决方案5】:

      为了帮助任何人,我遇到了同样的问题。我刚刚将 v-model="" 中的 var 从 props 数组更改为数据。记住 props 和 data 之间的区别,我的情况是改变它不是问题,你应该权衡你的决定。

      例如:

      <v-dialog v-model="dialog" fullscreen hide-overlay transition="dialog-bottom-transition">
      

      之前:

      export default {
          data: function () {
              return {
                  any-vars: false
              }
          },
          props: {
                  dialog: false,
                  notifications: false,
                  sound: false,
                  widgets: false
              },
          methods: {
              open: function () {
                  var vm = this;
      
                  vm.dialog = true;
              }
          }
      }
      

      之后:

      export default {
          data: function () {
              return {
                  dialog: false
              }
          },
          props: {
                  notifications: false,
                  sound: false,
                  widgets: false
              },
          methods: {
              open: function () {
                  var vm = this;
      
                  vm.dialog = true;
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        也许它看起来像 hack 并且违反了单一数据源的概念,但它的工作) 该解决方案是创建本地代理变量并从道具继承数据。接下来使用代理变量。

        Vue.component("vote", {
            data: function() {
                return {
                    like_: this.like,
                    dislike_: this.dislike,
                }
            },
        
            props: {
                like: {
                    type: [String, Number],
                    default: 0
                },
                dislike: {
                    type: [String, Number],
                    default: 0
                },
                item: {
                    type: Object
                }
            },
        
            template: '<div class="tm-voteing"><span class="tm-vote tm-vote-like" @click="onVote(item, \'like\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{like_}}</span></span><span class="tm-vote tm-vote-dislike" @click="onVote(item, \'dislike\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{dislike_}}</span></span></div>',
        
            methods: {
                onVote: function(data, action) {
                    var $this = this;
                    // instead of jquery ajax can be axios or vue-resource
                    $.ajax({
                        method: "POST",
                        url: "/api/vote/vote",
                        data: {id: data.id, action: action},
                        success: function(response) {
                            if(response.status === "insert") {
                                $this[action + "_"] = Number($this[action + "_"]) + 1;
                            } else {
                                $this[action + "_"] = Number($this[action + "_"]) - 1;
                            }
                        },
                        error: function(response) {
                            console.error(response);
                        }
                    });
                }
            }
        });
        

        使用组件并传递道具

        <vote :like="item.vote_like" :dislike="item.vote_dislike" :item="item"></vote>
        

        【讨论】:

          【解决方案7】:

          不知道为什么警告有提示时会被别人漏掉

          避免直接改变 prop,因为每当父组件重新渲染时,该值将被覆盖。相反,请根据道具的值使用数据或计算属性。正在变异的道具:“可见”(在组件中找到)

          尝试从子组件中收到的 prop 创建一个计算属性

          computed: {
            isVisible => this.visible
          }
          

          并在您的子组件中使用此计算值并将更改发送给您的父组件。

          【讨论】:

            猜你喜欢
            • 2021-03-19
            • 2020-09-22
            • 2018-08-16
            • 2020-06-09
            • 1970-01-01
            • 2018-03-21
            • 2019-06-13
            • 2018-07-02
            • 2018-12-15
            相关资源
            最近更新 更多