小萝卜干的技术分享

15 object(s)
 

Vue中使用自定义组件的避坑指南其一

最近在整Vue中的自定义组件的时候,遇到了这么个错误:

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: "xxxx"

相信很多初学者在做自定义组件的时候会遇到这样的错误,尤其是自定义组件中还引用了其他的控件,想要做联动,比如input。

来举一个简单的例子:

假设我们要实现一个,把vue中的treeselect封装到我们的一个自定义组件mycomponent中的需求。页面上这样使用我们的自定义组件:

<mycomponent
      v-model="SomeValue"
      :options="SomeOptions"
    />

在撸自定义组件的时候,可能会写出类似下面的代码:

<template>
  <div>
    <treeselect
      v-model="value"
      :options="options"
      :disable-branch-nodes="true"
      :placeholder="placeholder"
    />
  </div>
</template>

<script>
import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";

export default {
  components: { treeselect },
  props: {
    // ViewModel
    value: String,
    options: {
      type: Array,
      default: () => [],
    },
    placeholder: {
      type: String,
      default: "Hello World",
    },
  },
  model: {
    prop: "value",
    event: "change",
  },
  watch: {
    value: {
      handler(newValue) {
        this.$emit("change", newValue);
      },
    },
  },
};
</script>

咋一看似乎没啥毛病,但是实际上当我们选择treeselect中的内容后,控制台就会报:

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: "xxxx"

如果将这个控件放到form中,那么我们在页面上做form的校验,重置等操作结果会异常。

分析原因:如果在自定义组件中修改了props的话,就会导致出现这样的错误,因为vue中并不能在自身控件中修改props的值。

既然在内部修改不能,那么就在控件之外进行修改不就行了嘛!所以我们的目标是,如何让内部的treeselect的改动通知到自定义控件之外,由外部将值修改到SomeValue中,那这样以来我们控件中的value就能被正常修改了

这种做法,网上其实已经有一大把教程示例了,但是讲的可能不是那么详细,也许你大脑理解了,双手还没真正理解。所以下面贴一段正确的写法,加强记忆:

<template>
  <div>
    <treeselect
      :value="temp"
      :options="options"
      @input="onValueChange"
      :disable-branch-nodes="true"
      :placeholder="placeholder"
    />
  </div>
</template>

<script>
import treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";

export default {
  components: { treeselect },
  data() {
    return {
      temp: this.value
    };
  },
  props: {
    // ViewModel
    value: String,
    options: {
      type: Array,
      default: () => [],
    },
    placeholder: {
      type: String,
      default: "Hello World",
    },
  },
  model: {
    prop: "value",
    event: "change",
  },
  watch: {
    value: {
      handler(newValue) {
        this.temp = newValue
      },
    },
  },
  methods: {
    onValueChange(val,instanceId){
      this.$emit("change", val);
    }
  },
};
</script>

仔细观察上面两段代码,可以发现有这么些区别:

梳理一下流程:

  1. 当我们操作treeselect控件的时候,触发input事件,同时改变temp的内容,然后将变更后的值通过model中定义的change事件进行分发;页面上绑定我们自定义控件的v-model的值就会发生变化(SomeValue变量值发生改变),页面上可以看到选中的内容出现在treeselect的框框中,代码中再去获取SomeValue变量的时候就是我们要的temp的值
  2. 当我们利用别的手段改变SomeValue的值后,通过watch,temp字段能够感知value发生了变更,然后更新自己的值。这样,treeselect也就能自动变更内容

好了,关于自定义组件的其中一个问题就说到这里