Vue父子组件通过prop异步传输数据踩坑

21,093 阅读3分钟

今天碰到vue开发父子组件通信的一个小坑,情况是这样的:子组件使用echart展示图表,所需options由父组件通过prop传入,父组件中的options初始值为空,在mounted钩子函数中发起http请求获取数据然后更新options,结果子组件无法正确显示图表,经过一番查找解决了问题,通过本文做个记录,也希望能帮到以后遇到相同问题的小伙伴,示例采用demo演示

父组件

<template>
  <div id="app">
    <child :message="message"></child>
  </div>
</template>

<script>
import child from "./components/child";
export default {
  name: "app",
  components: {child },
  data() {
    return {
      message:{}
    };
  },
  mounted(){
    // 模拟异步请求
    setTimeout( () => this.message.age = 18,2000)
  }
};
</script>

子组件

<template>
    <div>{{age}}</div>
</template>

<script type='text/ecmascript-6'>
export default {
  props: ["message"],
  mounted(){
  // 模拟echart的初始化操作
    this.age = this.message.age
  },
  data() {
    return {
      age: null
    };
  }
};
</script>

实际效果如图

其实这里子组件是拿到了更新后的值,如果template中的是{{message.age}}是可以显示出18的,但是项目中是传的options,子组件有个echart的setOptions操作,并不是直接将prop的数据展示,所以demo写成这样。这里是可以分析出问题所在的,子组件mounted时父组件传来的值message为空对象,this.message.age就是underfined,所以页面显示为空,2秒后父组件的值更新,子组件可以拿到新的值,但mounted钩子函数不再触发,所以age仍为underfined,在网上查了下,通常的解决办法是使用watch来监听prop。改进后代码如下

子组件

<template>
    <div>{{age}}</div>
</template>

<script type='text/ecmascript-6'>
export default {
  props: ["message"],
  mounted() {
    this.age = this.message.age;
  },
  watch: {
      message(nv,ov){
          this.age = nv.age
      }
  },
  data() {
    return {
      age: null
    };
  }
};
</script>

看到网上的教程基本到这就结束了,子组件都可以正确渲染,but...我这页面还是老样子,依旧一片空白,查看下调试工具,还是这样:

我擦,这是什么鬼,debug一下发现watch里的代码根本不会执行,可message明明变了啊,于是又在网络中遨游了一番,然后看到了这么一段话。

Vue在实例化的时候会给data中的每个属性加上getter setter以实现响应式更新,但是新增的属性无法实现响应式更新,这里我们的父组件中message初始值为{},2秒中后设置他的age为18,因为age是新增的属性,实例初始化的时候并没有给age加上getter和setter所以watch失败。这里盗一张官网的图

修改代码,在父组件中message的初始值中添加age

<template>
  <div id="app">
    <child :message="message"></child>
  </div>
</template>

<script>
import child from "./components/child";
export default {
  name: "app",
  components: {child },
 
  data() {
    return {
      message:{age:null}
    };
  },
  mounted(){
    // 模拟异步请求
    setTimeout( () => this.message.age = 18,2000)
  }
};
</script>

嗯,应该没问题了吧,打开页面,额。。。还是熟悉的页面

有点小崩溃,冷静,再分析一波。子组件watch了message,但是我们在异步代码中执行了this.message.age = 18而不是类似this.message = xxx这种操作,虽然message这个对象确实发生了改变,但是却无法触发setter,watch也就不起作用,查了下官网,发现了这个配置:

修改代码如下:

<template>
    <div>{{age}}</div>
</template>

<script type='text/ecmascript-6'>
export default {
  props: ["message"],
  mounted() {
    this.age = this.message.age;
  },
  watch: {
    message: {
      deep: true,
      handler(nv, ov) {
        this.age = nv.age;
      }
    }
  },
  data() {
    return {
      age: null
    };
  }
};
</script>

再次打开页面

ok,完结撒花!!!