Vue 2 - 变异道具vue-warn

时间:2016-10-05 08:29:03

标签: javascript vue.js vuejs2

我开始了https://laracasts.com/series/learning-vue-step-by-step系列。我在 Vue,Laravel和AJAX 课程上停止了这个错误:

vue.js:2574 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "list" (found in component <task>)

我在 main.js

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

当我覆盖列表道具时,我知道问题出在 created(),但我是Vue的新手,所以我完全不知道如何解决它。任何人都知道如何(并请解释原因)来解决它?

24 个答案:

答案 0 :(得分:188)

这与 mutating a prop locally is considered an anti-pattern in Vue 2

这一事实有关

如果您想在本地改变道具,您现在应该做的是在props中声明一个使用Vue.component('task', { template: '#task-template', props: ['list'], data: function () { return { mutableList: JSON.parse(this.list); } } }); 值作为其初始值的字段值,然后改变副本:

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

您可以在Vue.js official guide

上详细了解相关信息

注1:请注意 you should not use the same name for your prop and data ,即:

props

注2:关于NULL反应性 I feel there is some confusion以来,我建议您查看this主题< / p>

答案 1 :(得分:29)

Vue只是警告你:你改变组件中的prop,但是当父组件重新渲染时,“list”将被覆盖,你将丢失所有的更改。所以这样做很危险。

使用计算属性,如下所示:

PropertyChanged

答案 2 :(得分:17)

Props down,事件发生了。这是Vue的模式。关键是,如果你试图改变从父母传递的道具。它不会工作,只是被父组件重复覆盖。子组件只能发出一个事件来通知父组件做某事。如果您不喜欢这些限制,您可以使用VUEX(实际上这种模式会吸引复杂的组件结构,您应该使用VUEX!)

答案 3 :(得分:17)

如果你正在使用Lodash,你可以在返回之前克隆道具。如果您在父项和子项上修改该道具,则此模式很有用。

假设我们在组件 grid 上有 list

在父组件中

<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)
    }
}

答案 4 :(得分:12)

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

从Vue 2.2.0开始,您可以使用v-model(与computed properties一起使用)。我发现这种组合在组件之间创建了一个简单,干净且一致的接口:

  • 传递给组件的任何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)
            }
        }
    }
})  

model属性定义了哪个propv-model相关联,以及哪个事件将在更改时发出。然后,您可以从父级调用此组件,如下所示:

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

listLocal计算属性在组件内提供了一个简单的getter和setter接口(将其视为私有变量)。在#task-template中,您可以渲染listLocal,并且它将保持响应状态(即,如果parentList发生更改,它将更新Task组件)。您还可以通过调用设置器(例如listLocal)来对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来调用其他子组件(例如表单元素)。

答案 5 :(得分:11)

您不应更改子组件中道具的值。 如果您确实需要更改它,可以使用.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'
})

答案 6 :(得分:6)

根据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渲染您的模板,因此您可以在列表中保持列表支持安全。

答案 7 :(得分:5)

不要直接在components中更改道具。如果你需要更改,它会设置一个新属性,如下所示:

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

并更改listClone的值。

答案 8 :(得分:4)

我也遇到过这个问题。使用$on$emit后警告消失了。 这类似于使用$on$emit建议将数据从子组件发送到父组件。

答案 9 :(得分:3)

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

<强> component.vue

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

答案 10 :(得分:3)

单向数据流, 根据{{​​3}},该组件遵循单向 数据流, 所有道具形成了子属性和父属性之间的单向向下绑定,当父属性更新时,它将向下流向子而不是相反,这可以防止子组件意外地改变父级&#39 ; s,这可以使您的应用程序的数据流更难理解。

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

通常有两种情况可能会使支柱变异: prop用于传递初始值;子组件之后想要将它用作本地数据属性。 prop是作为需要转换的原始值传递的。 这些用例的正确答案是: 定义使用prop的初始值作为其初始值的本地数据属性:

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

定义根据prop的值计算的计算属性:

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

答案 11 :(得分:3)

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

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

成分:

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

答案 12 :(得分: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'
})

也许这会满足您的需求。

答案 13 :(得分:2)

添加最佳答案

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

通过数组设置道具是为了进行开发/原型制作,在生产环境中,请确保设置道具类型(https://vuejs.org/v2/guide/components-props.html)并设置默认值(以防在父项未填充道具的情况下)。

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

此方式,你ATLEAST得到在一个空对象mutableList如果未定义,而不是JSON.parse错误。

答案 14 :(得分:2)

Vue.js道具不会被改变,因为这被视为Vue中的反模式。

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

 //get user preferences
Spinner spinnerCurrency = (Spinner) findViewById(R.id.spinnerCurrency);
Spinner spinnerRefresh = (Spinner) findViewById(R.id.spinnerRefresh);

currency = spinnerCurrency.getSelectedItem().toString();
refresh = spinnerRefresh.getSelectedItem().toString();

Button saveButton = findViewById(R.id.saveChangesButton);

saveButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {



        preferences=getSharedPreferences("UserPreference", Context.MODE_PRIVATE);

        SharedPreferences.Editor editor = preferences.edit();

        editor.putString("currency",currency);

        editor.putString("refresh",refresh);

        editor.commit();


    }
});

现在,您传递给组件的列表结构将通过组件的props: ['list'], data: () { return { parsedList: JSON.parse(this.list) } } 属性进行引用和变异: - )

如果您希望做的不仅仅是解析列表属性,那么请使用Vue组件&#39; data财产。 这样你就可以对你的道具进行更深入的突变。

computed

上面的示例解析了您的列表道具,并将其过滤为仅有效列表 - tems,将其记录为用于schnitts和giggles 退货。

note props: ['list'], computed: { filteredJSONList: () => { let parsedList = JSON.parse(this.list) let filteredList = parsedList.filter(listItem => listItem.active) console.log(filteredList) return filteredList } } &amp; data属性在模板中引用相同的内容,例如

computed

可以很容易地认为需要调用<pre>{{parsedList}}</pre> <pre>{{filteredJSONList}}</pre> 属性(作为方法)...它没有

答案 15 :(得分:1)

答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,使用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)),并将其分配给输入变量,一旦接收到输入,我们就可以捕获该动作并将输入发送给父级,提示输入了数据来自表单元素。

答案 16 :(得分:1)

下面是小吃店组件,当我将 snackbar 变量直接输入v模型时,如果这样可以正常工作,但在控制台中,它将给出错误消息

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

<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>

摆脱这种突变错误的正确方法是使用 watcher

<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>

这对我有用

答案 17 :(得分:1)

Vue3有一个非常好的解决方案。花了几个小时到达那里。但这确实很好。

在父模板上

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>

子组件

app.component('user-name', {
      props: {
      firstName: String,
      lastName: String
     },
template: `
   <input 
  type="text"
  :value="firstName"
  @input="$emit('update:firstName', 
 $event.target.value)">

<input
  type="text"
  :value="lastName"
   @input="$emit('update:lastName', 
    $event.target.value)">
  `
})

这是唯一进行双向绑定的解决方案。我喜欢前两个答案都很好地解决了使用SYNC和发射更新事件以及计算属性getter设置器的问题,但这真是一件难事,而且我不喜欢这么辛苦。

答案 18 :(得分: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>

答案 19 :(得分:0)

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

道具旨在提供单向通信。

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

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

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

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

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

答案 20 :(得分:0)

我个人总是建议您是否需要对道具进行突变,首先将其传递给计算属性,然后从那里返回,此后,您就可以轻松地对道具进行突变,即使您可以追踪道具的突变,也可能是从其他组件变异而来的,或者我们也可以观看。

答案 21 :(得分:0)

因为Vue道具是数据流的一种方式,所以可以防止子组件意外改变父级的状态。

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

  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
      }
    
    
    

答案 22 :(得分:0)

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

?很简单,我们杀死了蝙蝠侠?

答案 23 :(得分:0)

当TypeScript是您的首选语言时。发展之路

<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;
}