簡體   English   中英

Vue:從父母那里收到道具后,子組件不會改變

[英]Vue: child component won't change after receiving props from parents

所以我對vue的父子組件通信有問題。 問題是,在我導航到一個組件后,它應該調用 ajax 從服務器獲取數據。 父組件收到數據后,應該通過 props 將數據發送給所有子組件,但是 props 數據沒有顯示出來。 只有在我在編輯器上更改代碼后,子組件才開始顯示道具數據。 所以,這是我的父組件的代碼

<template>
  <div id="single-product-container">
    <product-header :name="singleProductName" :details="singleProductDetail" />
    <product-spec :spec="singleProductSpec" />
  </div>
</template>

<script>
import SingleProductHeader from '@/pages/SingleProductPage/single-product-header'
import SingleProductSpec from '@/pages/SingleProductPage/single-product-spec'
import singleProductApi from '@/api/product.api'

export default {
  data () {
    return {
      singleProductData: null,
      singleProductDetail: [],
      singleProductName: '',
      singleProductSpec: null
    }
  },
  methods: {
    getAllSingleProductDetail () {
      const productName = this.$route.params.product
      const location = this.location || 'jakarta'
      let vehicleType = null
      const path = this.$route.fullPath
      let self = this
      if (path.includes('motorcycle')) {
        vehicleType = 'motorcycle'
      } else if (path.includes('car')) {
        vehicleType = 'car'
      }
      singleProductApi.getSingleProductRequest(location, productName, vehicleType)
        .then(singleProductResponse => {
          console.log(singleProductResponse)
          let specObj = singleProductResponse.specification
          self.singleProductDetail = singleProductResponse.detail
          self.singleProductName = singleProductResponse.product_name
          self.singleProductSpec = specObj
          self.singleProductData = singleProductResponse
        })
        .catch(error => {
          throw error
        })
    }
  },
  mounted () {
    document.title = this.$route.params.product
  },
  created () {
     this.getAllSingleProductDetail()
  },
  components: {
    'product-header': SingleProductHeader,
    'product-spec': SingleProductSpec
  }
}
</script>

這是我的單一產品規格組件,它不會加載道具數據:

<template>
  <div id="product-spec">
    <div class="product-spec-title">
      Spesifikasi
    </div>
    <div class="produk-laris-wrapper">
      <div class="tab-navigation-wrapper tab-navigation-default">
        <div class="tab-navigation tab-default" v-bind:class="{ 'active-default': mesinActive}" v-on:click="openSpaceTab(event, 'mesin')">
          <p class="tab-text tab-text-default">Mesin</p>
        </div>
        <div class="tab-navigation tab-default" v-bind:class="{ 'active-default': rangkaActive}" v-on:click="openSpaceTab(event, 'rangka')">
          <p class="tab-text tab-text-default">Rangka & Kaki</p>
        </div>
        <div class="tab-navigation tab-default" v-bind:class="{ 'active-default': dimensiActive}" v-on:click="openSpaceTab(event, 'dimensi')">
          <p class="tab-text tab-text-default">Dimensi & Berat</p>
        </div>
        <div class="tab-navigation tab-default" v-bind:class="{ 'active-default': kapasitasActive}" v-on:click="openSpaceTab(event, 'kapasitas')">
          <p class="tab-text tab-text-default">Kapasitas</p>
        </div>
        <div class="tab-navigation tab-default" v-bind:class="{ 'active-default': kelistrikanActive}" v-on:click="openSpaceTab(event, 'kelistrikan')">
          <p class="tab-text tab-text-default">Kelistrikan</p>
        </div>
      </div>
      <div id="tab-1" class="spec-tab-panel" v-bind:style="{ display: mesinTab }">
        <table class="spec-table">
          <tbody>
            <tr class="spec-row" v-for="(value, name) in mesinData" :key="name">
              <td> {{ name }} </td>
              <td> {{ value }} </td>
            </tr>
          </tbody>
        </table>
      </div>
      <div id="tab-2" class="spec-tab-panel" v-bind:style="{ display: rangkaTab }">
        <table class="spec-table">
          <tbody>
            <tr class="spec-row" v-for="(value, name) in rangkaData" :key="name">
              <td> {{ name }} </td>
              <td> {{ value }} </td>
            </tr>
          </tbody>
        </table>
      </div>
      <div id="tab-3" class="spec-tab-panel" v-bind:style="{ display: dimensiTab }">
        <table class="spec-table">
          <tbody>
            <tr class="spec-row" v-for="(value, name) in dimensiData" :key="name">
              <td> {{ name }} </td>
              <td> {{ value }} </td>
            </tr>
          </tbody>
        </table>
      </div>
      <div id="tab-4" class="spec-tab-panel" v-bind:style="{ display: kapasitasTab }">
        <table class="spec-table">
          <tbody>
            <tr class="spec-row" v-for="(value, name) in kapasitasData" :key="name">
              <td> {{ name }} </td>
              <td> {{ value }} </td>
            </tr>
          </tbody>
        </table>
      </div>
      <div id="tab-5" class="spec-tab-panel" v-bind:style="{ display: kelistrikanTab }">
        <table class="spec-table">
          <tbody>
            <tr class="spec-row" v-for="(value, name) in kelistrikanData" :key="name">
              <td> {{ name }} </td>
              <td> {{ value }} </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    location: String,
    spec: Object
  },
  data () {
    return {
      mesinActive: true,
      rangkaActive: false,
      dimensiActive: false,
      kapasitasActive: false,
      kelistrikanActive: false,
      mesinTab: 'block',
      rangkaTab: 'none',
      dimensiTab: 'none',
      kapasitasTab: 'none',
      kelistrikanTab: 'none',
      mesinData: {},
      rangkaData: {},
      dimensiData: {},
      kapasitasData: {},
      kelistrikanData: {}
    }
  },
  methods: {
    openSpaceTab (evt, tab) {
      if (tab === 'mesin') {
        this.mesinActive = true
        this.rangkaActive = false
        this.dimensiActive = false
        this.kapasitasActive = false
        this.kelistrikanActive = false
        this.mesinTab = 'block'
        this.rangkaTab = 'none'
        this.dimensiTab = 'none'
        this.kapasitasTab = 'none'
        this.kelistrikanTab = 'none'
      } else if (tab === 'rangka') {
        this.mesinActive = false
        this.rangkaActive = true
        this.dimensiActive = false
        this.kapasitasActive = false
        this.kelistrikanActive = false
        this.mesinTab = 'none'
        this.rangkaTab = 'block'
        this.dimensiTab = 'none'
        this.kapasitasTab = 'none'
        this.kelistrikanTab = 'none'
      } else if (tab === 'dimensi') {
        this.mesinActive = false
        this.rangkaActive = false
        this.dimensiActive = true
        this.kapasitasActive = false
        this.kelistrikanActive = false
        this.mesinTab = 'none'
        this.rangkaTab = 'none'
        this.dimensiTab = 'block'
        this.kapasitasTab = 'none'
        this.kelistrikanTab = 'none'
      } else if (tab === 'kapasitas') {
        this.mesinActive = false
        this.rangkaActive = false
        this.dimensiActive = false
        this.kapasitasActive = true
        this.kelistrikanActive = false
        this.mesinTab = 'none'
        this.rangkaTab = 'none'
        this.dimensiTab = 'none'
        this.kapasitasTab = 'block'
        this.kelistrikanTab = 'none'
      } else if (tab === 'kelistrikan') {
        this.mesinActive = false
        this.rangkaActive = false
        this.dimensiActive = false
        this.kapasitasActive = false
        this.kelistrikanActive = true
        this.mesinTab = 'none'
        this.rangkaTab = 'none'
        this.dimensiTab = 'none'
        this.kapasitasTab = 'none'
        this.kelistrikanTab = 'block'
      }
    }
  },
  created () {
    this.mesinData = this.spec.mesin
    this.rangkaData = this.spec.rangka
    this.dimensiData = this.spec.dimensi
    this.kapasitasData = this.spec.kapasitas
    this.kelistrikanData = this.spec.kelistrikan
  }
}
</script>

正如我所說,我的單一產品規格組件的唯一問題不是它不會加載道具數據。 問題是,當我在文本編輯器中更改代碼時,它只加載道具數據(這很奇怪,我知道)。 當我開始調試時,我開始意識到這一點,當我在 single-product-spec 組件中更改我的代碼時,props 數據開始開始加載。 如果我不更改我的單一產品規格組件代碼,那么無論我等待多長時間,道具數據都不會加載。

好的,讓我們按順序逐步介紹發生的事情:

  1. 創建父組件,觸發created鈎子並從服務器啟動數據加載。
  2. 父組件渲染,創建子組件。 spec的 prop 值將是null因為數據尚未加載,而singleProductSpec仍然是null
  3. single-product-spec運行created的掛鈎。 由於this.specnull我想這會引發錯誤,盡管問題中沒有提到錯誤。
  4. 在未來的某個時間點,數據加載完成,更新singleProductSpec的值。 它是父組件的渲染依賴,因此該組件將被添加到渲染隊列中。
  5. 父組件將重新渲染。 singleProductSpec的新值將作為spec屬性傳遞給single-product-spec 不會創建single-product-spec的新實例,它只會重新使用它第一次渲染時創建的實例。

到那時,其他任何事情都不會發生。 single-product-speccreated鈎子不會重新運行,因為它不是剛剛創建的。

當您編輯子組件的源代碼時,它將觸發該組件的熱重載。 這種更改的確切效果會有所不同,但通常會導致重新創建該子項,而無需重新創建父項。 由於父級已經從服務器加載了數據,因此新創建的子級將傳遞完全填充的spec值。 這允許在created的鈎子中讀取它。

有很多方法可以解決這個問題。

首先,在數據准備好之前,我們可以避免創建single-product-spec

<product-spec v-if="singleProductSpec" :spec="singleProductSpec" />

這將簡單地避免在初始渲染期間創建組件,以便在運行孩子的created鈎子時,它可以訪問您想要的數據。 這可能是您應該使用的方法。

第二種方法是使用key 鍵用於在重新渲染時配對組件,以便 Vue 知道哪個舊組件與哪個新組件匹配。 如果key發生變化,那么 Vue 將丟棄舊的子組件並創建一個新的子組件。 創建新組件時,它將運行created的鈎子。 這可能不是您的方案的最佳方法,因為尚不清楚通過null spec時子組件應該做什么。

第三種方法是在子組件中使用watch 這將觀察spec的值何時更改並將相關值復制到組件的本地數據屬性。 雖然在某些情況下使用這樣的watch是合適的,但它通常表明組件設計存在潛在的弱點。

但是,您的代碼中還有其他問題...

  1. 目前尚不清楚為什么您首先要將道具中的值復制到本地數據中。 你可以直接使用道具。 如果您這樣做只是為了給它們提供更短的名稱,那么只需使用計算屬性即可。 像這樣復制它們的唯一合法原因是,如果屬性值可以在子級中更改,並且 prop 僅用於傳遞初始值。 即使在那種情況下,您也不會使用created的鈎子,您只需在data function 中執行它。 請參閱https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
  2. 您為 5 個選項卡復制了 5 次所有內容。 這應該使用對象數組來實現,每個 object 都包含選項卡的所有相關詳細信息。
  3. 屬性mesinActivemesinTab都表示相同的基礎數據。 你不應該同時擁有data 至少一個應該是計算屬性,盡管我個人可能會完全擺脫mesinTab 而是使用 CSS 類來應用相關樣式,並只使用mesinActive來決定應用哪些類(就像你在其他地方一樣)。 顯然,這同樣適用於其他xActive / xTab屬性。
  4. 您的選項卡是一種單選形式。 使用 5 個 boolean 值來表示一個選擇不是合適的數據結構。 執行此操作的正確方法是擁有一個標識當前選項卡的屬性。 細節可能會有所不同,它可能包含選項卡索引,或代表選項卡數據的 object,或代表選項卡的 id。
  5. 您不需要將let self = this與箭頭函數一起使用。 this值是從周圍的 scope 中保留下來的。

正確實現single-product-spec的代碼應該崩潰到幾乎沒有。 您應該能夠擺脫大約 80% 的代碼。 如果您只使用適當的數據結構來保存所有數據,我希望openSpaceTab方法是單行的。

更新:

根據要求,考慮到我回答的“其他問題”部分的第 1-4 點,這是對您的組件的重寫。

 const ProductSpecTitle = { template: ` <div> <div class="product-spec-title"> Spesifikasi </div> <div class="produk-laris-wrapper"> <div class="tab-navigation-wrapper tab-navigation-default"> <div v-for="tab of tabs":key="tab.id" class="tab-navigation tab-default":class="{ 'active-default': tab.active }" @click="openSpaceTab(tab.id)" > <p class="tab-text tab-text-default">{{ tab.text }}</p> </div> </div> <div v-for="tab in tabs" class="spec-tab-panel":class="{ 'spec-tab-panel-active': tab.active }" > <table class="spec-table"> <tbody> <tr v-for="(value, name) in tab.data":key="name" class="spec-row" > <td> {{ name }} </td> <td> {{ value }} </td> </tr> </tbody> </table> </div> </div> </div> `, props: { spec: Object }, data () { return { selectedTab: 'mesin' } }, computed: { tabs () { const tabs = [ { id: 'mesin', text: 'Mesin' }, { id: 'rangka', text: 'Rangka & Kaki' }, { id: 'dimensi', text: 'Dimensi & Berat' }, { id: 'kapasitas', text: 'Kapasitas' }, { id: 'kelistrikan', text: 'Kelistrikan' } ] for (const tab of tabs) { tab.active = tab.id === this.selectedTab tab.data = this.spec[tab.id] } return tabs } }, methods: { openSpaceTab (tab) { this.selectedTab = tab } } } new Vue({ el: '#app', components: { ProductSpecTitle }, data () { return { spec: { mesin: { a: 1, b: 2 }, rangka: { c: 3, d: 4 }, dimensi: { e: 5, f: 6 }, kapasitas: { g: 7, h: 8 }, kelistrikan: { i: 9, j: 10 } } } } })
 .tab-navigation-wrapper { display: flex; margin-top: 10px; }.tab-navigation { border: 1px solid #000; cursor: pointer; }.tab-text { margin: 10px; }.active-default { background: #ccf; }.spec-tab-panel { display: none; }.spec-tab-panel-active { display: block; margin-top: 10px; }.spec-table { border-collapse: collapse; }.spec-table td { border: 1px solid #000; padding: 5px; }
 <script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script> <div id="app"> <product-spec-title:spec="spec"></product-spec-title> </div>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM