Vue 2-变异道具vue-warn


177

我开始https://laracasts.com/series/learning-vue-step-by-step系列。我停在课程Vue,Laravel和AJAX遇到此错误:

vue.js:2574 [Vue警告]:避免直接更改道具,因为每当父组件重新渲染时,该值就会被覆盖。而是使用基于属性值的数据或计算属性。变异的道具:“列表”(位于component中)

我在main.js中有此代码

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.list = JSON.parse(this.list);
    }
});
new Vue({
    el: '.container'
})

我知道当我覆盖列表属性时问题出在created()中,但是我是Vue的新手,所以我完全不知道如何解决它。任何人都知道如何(并请解释为什么)修复它?


2
猜猜,这只是警告消息,而不是错误。
David R

Answers:


262

这与以下事实有关:在Vue 2中将局部更改prop视为反模式

如果要在本地更改prop,现在应该做的是在您的容器中声明一个data使用该props值作为其初始值的字段,然后对副本进行更改:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

您可以在Vue.js官方指南中了解更多有关此内容的信息


注意1:请注意,不能prop和使用相同的名称data,即:

data: function () { return { list: JSON.parse(this.list) } // WRONG!!

注2:由于我觉得有一些混乱关于props反应,我建议你有一看这个线程


3
这是一个很好的答案,但我也认为应该对其进行更新以包括事件绑定。子级触发的事件可以更新父级,这会将更新后的道具传递给子级。这样,真理的来源仍然在所有组成部分中得到维护。值得一提的是Vuex用于管理在多个组件之间共享的状态。
Wes Harper

@WesHarper,也可以使用.sync修饰符作为速记来自动获取父级的update事件。
danb4r

44

Vue模式props向下和events向上。听起来很简单,但是在编写自定义组件时很容易忘记。

从Vue 2.2.0开始,您可以使用v模型(具有计算属性)。我发现这种组合在组件之间创建了一个简单,干净且一致的接口:

  • props传递给您组件的任何内容都将保持反应性(即,它不会被克隆,也不需要watch检测到更改时需要更新本地副本的函数)。
  • 更改会自动发送给父级。
  • 可以与多个级别的组件一起使用。

计算属性允许setter和getter分别定义。这样Task就可以按以下方式重写组件:

Vue.component('Task', {
    template: '#task-template',
    props: ['list'],
    model: {
        prop: 'list',
        event: 'listchange'
    },
    computed: {
        listLocal: {
            get: function() {
                return this.list
            },
            set: function(value) {
                this.$emit('listchange', value)
            }
        }
    }
})  

模型,其属性定义prop相关联v-model,并且该事件将在改变发射。然后可以从父级调用此组件,如下所示:

<Task v-model="parentList"></Task>

所述listLocal计算出的属性在组件内提供了一个简单的getter和setter接口(认为它像被一个私有变量)。在其中#task-template可以进行渲染listLocal,并且它将保持反应性(即,如果进行parentList更改,它将更新Task组件)。您也可以listLocal通过调用setter(例如this.listLocal = newList)进行更改,它将更改发送给父级。

这种模式的优点在于,您可以将传递listLocal给的子组件Task(使用v-model),并且对子组件的更改将传播到顶级组件。

例如,假设我们有一个单独的EditTask组件,用于对任务数据进行某种类型的修改。通过使用相同的v-model和计算的属性模式,我们可以传递listLocal给组件(使用v-model):

<script type="text/x-template" id="task-template">
    <div>
        <EditTask v-model="listLocal"></EditTask>
    </div>
</script>

如果EditTask发出改变它会适当地调用set()listLocal,并由此传播事件到顶层。同样,该EditTask组件也可以使用调用其他子组件(例如表单元素)v-model


1
我正在做类似的事情,除了同步。问题是,在发出变更事件后,我需要运行另一种方法,但是当该方法运行并命中吸气剂时,我得到了旧值,因为变更事件尚未被侦听器拾取/传播回了孩子。在这种情况下,我想使prop发生变异,以便在父更新向下传播之前,我的本地数据是正确的。在这方面有什么想法吗?
koga73 '18

我怀疑从设置方法中调用吸气剂不是一个好习惯。您可能会考虑在计算的属性上放一个监视并在其中添加逻辑。
克里斯

支撑下来,事件增加,这是我所点击的。VueJs的文档在哪里解释?
nebulousGirl '18


我认为这是将更改发送给父组件的更轻松的方法。
穆罕默德

36

Vue只是警告您:您更改了组件中的prop,但是当父组件重新渲染时,“列表”将被覆盖,并且您将丢失所有更改。因此这样做很危险。

改为使用计算属性:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
        listJson: function(){
            return JSON.parse(this.list);
        }
    }
});

25
两向绑定道具怎么样?
Josh R.

7
如果要使用2种方式进行数据绑定,则应使用自定义事件,有关更多信息,请阅读:vuejs.org/v2/guide/components.html#sync-Modifier您仍然不能直接修改prop,您需要一个事件和函数来为您处理道具变更。
马可(Marco)

22

如果您使用的是Lodash,则可以在返回道具之前先对其进行克隆。如果您同时修改父母和孩子的道具,则此模式很有用。

假设我们在元件grid上有道具列表

在父组件中

<grid :list.sync="list"></grid>

在子组件中

props: ['list'],
methods:{
    doSomethingOnClick(entry){
        let modifiedList = _.clone(this.list)
        modifiedList = _.uniq(modifiedList) // Removes duplicates
        this.$emit('update:list', modifiedList)
    }
}

19

支撑下来,事件向上。这就是Vue的模式。关键是,如果您尝试突变从父代传来的道具。它将无法工作,只会被父组件重复覆盖。子组件只能发出一个事件以通知父组件执行某项操作。如果您不喜欢这些限制,则可以使用VUEX(实际上,这种模式会吸收复杂的组件结构,您应该使用VUEX!)


2
还有一种使用事件总线的选项
Steven Pribilinskiy

vue 3本身不支持事件总线。github.com
vuejs/

15

您不应在子组件中更改道具的值。如果您确实需要更改它,可以使用.sync。像这样

<your-component :list.sync="list"></your-component>

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.$emit('update:list', JSON.parse(this.list))
    }
});
new Vue({
    el: '.container'
})

7

根据VueJs 2.0,您不应在组件内部对prop进行突变。他们仅由其父母变异。因此,您应该在数据中定义具有不同名称的变量,并通过观看实际道具来使它们保持更新。如果父项更改了列表道具,则可以对其进行解析并将其分配给mutableList。这是一个完整的解决方案。

Vue.component('task', {
    template: ´<ul>
                  <li v-for="item in mutableList">
                      {{item.name}}
                  </li>
              </ul>´,
    props: ['list'],
    data: function () {
        return {
            mutableList = JSON.parse(this.list);
        }
    },
    watch:{
        list: function(){
            this.mutableList = JSON.parse(this.list);
        }
    }
});

它使用mutableList来呈现您的模板,因此您可以确保列表道具在组件中的安全。


使用手表不贵吗?
Kick Buttowski

6

不要直接在组件中更改道具,如果需要更改,请设置一个新属性,如下所示:

data () {
    return () {
        listClone: this.list
    }
}

并更改listClone的值。


6

答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,使用getter,setter或watcher计算)来破坏直接prop突变。

这是使用观察程序的简单解决方案。

<template>
  <input
    v-model="input"
    @input="updateInput" 
    @change="updateInput"
  />

</template>

<script>
  export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      input: '',
    };
  },
  watch: {
    value: {
      handler(after) {
        this.input = after;
      },
      immediate: true,
    },
  },
  methods: {
    updateInput() {
      this.$emit('input', this.input);
    },
  },
};
</script>

这是我用来创建任何数据输入组件的工具,它工作得很好。值观察者将监视从父级发送的任何新数据(v-model(ed))并将其分配给输入变量,一旦接收到输入,我们就可以捕获该动作并将输入发送给父级,提示输入了数据从表单元素。


5

我也面临这个问题。我使用$on和后警告消失了$emit。这就像用法$on$emit建议将数据从子组件发送到父组件。


4

如果您想改变道具-使用对象。

<component :model="global.price"></component>

零件:

props: ['model'],
methods: {
  changeValue: function() {
    this.model.value = "new value";
  }
}

只是要注意,在对对象进行变异时必须要小心。Javascript对象是通过引用传递的,但是有一个警告:当您将变量设置为等于值时,引用将被破坏。
ira

3

您需要像这样添加计算方法

component.vue

props: ['list'],
computed: {
    listJson: function(){
        return JSON.parse(this.list);
    }
}

3

单向数据流,根据 https://vuejs.org/v2/guide/components.html,组件遵循单向数据流,所有道具在子属性和父属性之间形成单向绑定一个是,当父级属性更新时,它将向下流到子级,但不会反过来,这防止了子级组件意外地更改父级的组件,这会使您的应用程序的数据流更难以理解。

另外,每次父组件更新时,子组件中的所有道具都会以最新值刷新。这意味着您不应该尝试在子组件内部变异道具。如果您这样做,.vue将在控制台中警告您。

通常有两种情况会诱使prop发生变异:prop用于传递初始值;子组件随后希望将其用作本地数据属性。道具作为需要转换的原始值传入。这些用例的正确答案是:定义使用prop的初始值作为其初始值的本地数据属性:

props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}

定义从prop的值计算得出的计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

2

请勿对Vue.js道具进行突变,因为这被视为Vue中的“反模式”。

您将需要采取的方法是在组件上创建一个数据属性,该数据属性引用list的原始prop属性。

props: ['list'],
data: () {
  return {
    parsedList: JSON.parse(this.list)
  }
}

现在,通过组件的data属性来引用和更改传递给组件的列表结构:-)

如果您希望做的不仅仅是解析列表属性,请使用Vue组件的computed属性。这使您可以对道具进行更多的深度突变。

props: ['list'],
computed: {
  filteredJSONList: () => {
    let parsedList = JSON.parse(this.list)
    let filteredList = parsedList.filter(listItem => listItem.active)
    console.log(filteredList)
    return filteredList
  }
}

上面的示例解析您的列表道具,并将其过滤为仅活动的列表项,将其注销,以示小偷和傻笑并返回。

注意:模板中同时引用了datacomputed属性(例如,

<pre>{{parsedList}}</pre>

<pre>{{filteredJSONList}}</pre>

容易想到computed需要调用一个属性(作为一种方法)...


2
Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
      middleData() {
        return this.list
      }
    },
    watch: {
      list(newVal, oldVal) {
        console.log(newVal)
        this.newList = newVal
      }
    },
    data() {
      return {
        newList: {}
      }
    }
});
new Vue({
    el: '.container'
})

也许这会满足您的需求。


2

加上最佳答案,

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

通过数组设置props是为dev / prototyping设计的,在生产中,请确保设置prop类型(https://vuejs.org/v2/guide/components-props.html)并设置默认值(如果prop没有)是由父母填充的。

Vue.component('task', {
    template: '#task-template',
    props: {
      list: {
        type: String,
        default() {
          return '{}'
        }
      }
    },
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

这样,您至少可以得到一个空对象,mutableList而不是JSON.parse错误(如果未定义)。


0

Vue.js认为这是一种反模式。例如,声明和设置一些道具,例如

this.propsVal = 'new Props Value'

因此,要解决此问题,您必须从props到Vue实例的数据或计算属性中获取一个值,如下所示:

props: ['propsVal'],
data: function() {
   return {
       propVal: this.propsVal
   };
},
methods: {
...
}

这肯定会工作。


0

除上述以外,还有其他问题:

“如果props值不是必需的,因此不总是返回,则传递的数据将返回undefined(而不是空的)”。这可能会破坏<select>默认值,我通过检查是否beforeMount()按如下所示设置了该值(如果未设置该值)来解决它:

JS:

export default {
        name: 'user_register',
        data: () => ({
            oldDobMonthMutated: this.oldDobMonth,
        }),
        props: [
            'oldDobMonth',
            'dobMonths', //Used for the select loop
        ],
        beforeMount() {
           if (!this.oldDobMonth) {
              this.oldDobMonthMutated = '';
           } else {
              this.oldDobMonthMutated = this.oldDobMonth
           }
        }
}

HTML:

<select v-model="oldDobMonthMutated" id="dob_months" name="dob_month">

 <option selected="selected" disabled="disabled" hidden="hidden" value="">
 Select Month
 </option>

 <option v-for="dobMonth in dobMonths"
  :key="dobMonth.dob_month_slug"
  :value="dobMonth.dob_month_slug">
  {{ dobMonth.dob_month_name }}
 </option>

</select>

0

我想给出这个答案,这有助于避免使用大量代码,观察程序和计算属性。在某些情况下,这可能是一个很好的解决方案:

道具旨在提供单向通信。

当您有一个show/hide带有道具的模式按钮时,对我来说最好的解决方案是发出一个事件:

<button @click="$emit('close')">Close Modal</button>

然后将侦听器添加到模式元素:

<modal :show="show" @close="show = false"></modal>

(在这种情况下,该道具show可能是不必要的,因为您可以v-if="show"直接在基本模式上使用easy )


0

我个人总是建议您是否需要对道具进行突变,首先将其传递给计算属性,然后从那里返回,然后一个人就可以轻松地对道具进行突变,即使您可以追踪道具突变(如果这些道具正在从另一个突变)组件还是我们也可以观看。


0

由于Vue道具是数据流的一种方式,因此可以防止子组件意外更改父组件的状态。

从Vue官方文档中,我们将找到解决此问题的2种方法

  1. 如果子组件希望使用props作为本地数据,则最好定义一个本地数据属性。

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. 道具作为需要转换的原始值传入。在这种情况下,最好使用prop的值定义一个计算属性:

      props: ['list'],
      computed: {
        localList: function() {
           return JSON.parse(this.list);
        },
        //eg: if you want to filter this list
        validList: function() {
           return this.list.filter(product => product.isValid === true)
        }
        //...whatever to transform the list
      }
    
    

0

是的!vue2中的变异属性是反模式。但是...只要使用其他规则来打破规则,然后继续吧!您需要在paret范围内的组件属性中添加.sync修饰符。 <your-awesome-components :custom-attribute-as-prob.sync="value" />

we很简单,我们杀死蝙蝠侠😁


0

当TypeScript是您的首选lang时。发展的

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop({default: 0}) fees: any;

// computed are declared with get before a function
get feesInLocale() {
    return this.fees;
}

并不是

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop() fees: any = 0;
get feesInLocale() {
    return this.fees;
}

0

下面是零食栏组件,当我给它直接变量为这样的V模型是否会起作用,但在控制台,它会给一个错误

避免直接更改道具,因为每当父组件重新渲染时,该值都会被覆盖。而是使用基于属性值的数据或计算属性。

<template>
        <v-snackbar v-model="snackbar">
        {{ text }}
      </v-snackbar>
</template>

<script>
    export default {
        name: "loader",

        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },

    }
</script>

摆脱此变异错误的正确方法是使用观察程序

<template>
        <v-snackbar v-model="snackbarData">
        {{ text }}
      </v-snackbar>
</template>

<script>
/* eslint-disable */ 
    export default {
        name: "loader",
         data: () => ({
          snackbarData:false,
        }),
        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },
        watch: { 
        snackbar: function(newVal, oldVal) { 
          this.snackbarData=!this.snackbarDatanewVal;
        }
      }
    }
</script>

因此,在将要加载此小吃店的主要组件中,您只需执行以下代码

 <loader :snackbar="snackbarFlag" :text="snackText"></loader>

这对我有用

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.