说明

想简单封装一下组件,结果报错。

错误的实现

子组件

<template>

  <el-drawer direction="rtl" 
    :visible.sync="visibile" size="50%">
    详情
    xxx
  </el-drawer>

</template>
<script>
import axios from "axios";

import { outerFrontMonitorLogDetail } from "@/api/getData.js";

export default {
  name: 'frontMonitorLogDetail',
  props: {
    visibile: { type: Boolean, default: false },
    detailData: { type: Object },
  },
  data() {
    return {
    }
  },
  computed: {
  },
  mounted() {
  },
  created() {
  },
  methods: {
  },
}
</script>
<style lang="css" scoped>
</style>

通过 visible 传入过来,希望可以控制页面的显示与否。

调用方

<template>
  <el-container direction="vertical">
    <el-main>
      <frontMonitorLogDetail :visibile.sync="drawFlag"></frontMonitorLogDetail>  
      <el-button @click="showDraw" >点击</el-button>
    </el-main>
  </el-container>
</template>

<script>
import axios from "axios";
import frontMonitorLogDetail from '@/components/frontMonitorLogDetail';

export default {
  components: { frontMonitorLogDetail },

  name: 'test',
  data() {
    return {
      drawFlag: false
    }
  },
  mounted() {
  },
  methods: {
    showDraw() {
      this.drawFlag = true;
    }
  }
}

</script>
<style lang="css" scoped>
</style>

点击按钮,展示页面。

但是关闭会报错,因为关闭改变了 drawFlag 的属性,而且是在子控件中改变的。

错误原因

报错场景

A 组件中引用 B 组件,使用 v-model 给 B 传递参数,B 使用 props: { value } 接收内容,在 B 中根据逻辑直接修改并赋值 value, 事件触发后在浏览器 console 里看到报错,内容如下:

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

分析原因

从报错内容上来看,我们改动了子组件中引用的父组件的变量,也就是 props 中的数据,是不能这么操作的;

从提示的信息上看,使用 mutated 是否可行?

在 Vue2 中组件 props 中的数据只能单向流动,即只能从父组件通过组件的 DOM 属性 attribute 传递 props 给子组件,子组件只能被动接收父组件传递过来的数据,并且在子组件中,不能修改由父组件传来的 props 数据。

组件内不能修改props的值,同时修改的值也不会同步到组件外层,即调用组件方不知道组件内部当前的状态是什么。

2.1 这是什么原因造成?

在 vue1.x 版本中利用 props 的 twoWay 和 .sync 绑定修饰符就可以实现 props 的双向数据绑定。

在 vue2.0 中移除了组件的 props 的双向数据绑定功能,如果需要双向绑定需要自己来实现。

在 vue2.0 中组件的 props 的数据流动改为了只能单向流动,即只能由(父组件)通过组件的 v-bind:attributes 传递给(子组件),子组件只能被动接收父组件传递过来的数据,并在子组件内不能修改由父组件传递过来的 props 数据。

官方文档解释:

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
虽然废弃了props的双向绑定对于整个项目整体而言是有利且正确的,但是在某些时候我们确实需要从组件内部修改props的需求

如何解决

在 Vue2.0 中,实现组件属性的双向绑定方式(算不上是绑定了,算是异步修改), 可使用如下方法

  1. v-model 指令或 .sync 修饰符

  2. 将修改属性的方法通过 v-bind 传给子组件调用,子组件直接按方法使用即可

  3. 将修改属性的方法通过 v-on 传递给子组件调用,使用 $emit() 实现回调修改、或者使用 this.$parent 去修改

解决例子

子组件

<template>

  <el-drawer direction="rtl" 
    :visible.sync="drawerFlag" size="50%">
    详情
    xxx
  </el-drawer>

</template>
<script>
import axios from "axios";

import { outerFrontMonitorLogDetail } from "@/api/getData.js";

export default {
  name: 'frontMonitorLogDetail',
  props: {
    visibile: { type: Boolean, default: false },
    detailData: { type: Object },
  },
  data() {
    return {
      detailVisible: this.visibile
    }
  },
  computed: {
      drawerFlag:{
          get(){
              return this.visibile
          },
          set(v){
            // 值发生变化
              this.$emit("changeDrawer",v)
         }
      }
  },
  mounted() {
  },
  created() {
  },
  methods: {
  },
}
</script>
<style lang="css" scoped>
</style>

开关标识变化的时候,暴露一个 changeDrawer 方法,给父类调用。

调用

<template>

  <el-container direction="vertical">
    <el-main>
      <frontMonitorLogDetail :visibile.sync="drawFlag" @changeDrawer="changeDrawer"></frontMonitorLogDetail>  
      <el-button @click="showDraw" >点击</el-button>
    </el-main>
  </el-container>

</template>

<script>
import axios from "axios";
import frontMonitorLogDetail from '@/components/frontMonitorLogDetail';

export default {
  components: { frontMonitorLogDetail },

  name: 'test',
  data() {
    return {
      drawFlag: false
    }
  },
  mounted() {
  },
  methods: {
    showDraw() {
      this.drawFlag = true;
    },
    changeDrawer(v) {
      this.drawFlag = v;
    }
  }
}

</script>
<style lang="css" scoped>

</style>

问题解决。

参考资料

https://blog.csdn.net/u013948858/article/details/118342541