Vue父子元件通過prop非同步傳輸資料踩坑

Webwwl發表於2018-07-04

今天碰到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>
複製程式碼

實際效果如圖

Vue父子元件通過prop非同步傳輸資料踩坑
其實這裡子元件是拿到了更新後的值,如果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...我這頁面還是老樣子,依舊一片空白,檢視下除錯工具,還是這樣:

Vue父子元件通過prop非同步傳輸資料踩坑

我擦,這是什麼鬼,debug一下發現watch裡的程式碼根本不會執行,可message明明變了啊,於是又在網路中遨遊了一番,然後看到了這麼一段話。

Vue父子元件通過prop非同步傳輸資料踩坑
Vue在例項化的時候會給data中的每個屬性加上getter setter以實現響應式更新,但是新增的屬性無法實現響應式更新,這裡我們的父元件中message初始值為{},2秒中後設定他的age為18,因為age是新增的屬性,例項初始化的時候並沒有給age加上getter和setter所以watch失敗。這裡盜一張官網的圖

Vue父子元件通過prop非同步傳輸資料踩坑
修改程式碼,在父元件中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>
複製程式碼

嗯,應該沒問題了吧,開啟頁面,額。。。還是熟悉的頁面

Vue父子元件通過prop非同步傳輸資料踩坑
有點小崩潰,冷靜,再分析一波。子元件watch了message,但是我們在非同步程式碼中執行了this.message.age = 18而不是類似this.message = xxx這種操作,雖然message這個物件確實發生了改變,但是卻無法觸發setter,watch也就不起作用,查了下官網,發現了這個配置:

Vue父子元件通過prop非同步傳輸資料踩坑
修改程式碼如下:

<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>
複製程式碼

再次開啟頁面

Vue父子元件通過prop非同步傳輸資料踩坑
ok,完結撒花!!!

相關文章