[英]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 數據開始開始加載。 如果我不更改我的單一產品規格組件代碼,那么無論我等待多長時間,道具數據都不會加載。
好的,讓我們按順序逐步介紹發生的事情:
created
鈎子並從服務器啟動數據加載。spec
的 prop 值將是null
因為數據尚未加載,而singleProductSpec
仍然是null
。single-product-spec
運行created
的掛鈎。 由於this.spec
是null
我想這會引發錯誤,盡管問題中沒有提到錯誤。singleProductSpec
的值。 它是父組件的渲染依賴,因此該組件將被添加到渲染隊列中。singleProductSpec
的新值將作為spec
屬性傳遞給single-product-spec
。 不會創建single-product-spec
的新實例,它只會重新使用它第一次渲染時創建的實例。 到那時,其他任何事情都不會發生。 single-product-spec
的created
鈎子不會重新運行,因為它不是剛剛創建的。
當您編輯子組件的源代碼時,它將觸發該組件的熱重載。 這種更改的確切效果會有所不同,但通常會導致重新創建該子項,而無需重新創建父項。 由於父級已經從服務器加載了數據,因此新創建的子級將傳遞完全填充的spec
值。 這允許在created
的鈎子中讀取它。
有很多方法可以解決這個問題。
首先,在數據准備好之前,我們可以避免創建single-product-spec
:
<product-spec v-if="singleProductSpec" :spec="singleProductSpec" />
這將簡單地避免在初始渲染期間創建組件,以便在運行孩子的created
鈎子時,它可以訪問您想要的數據。 這可能是您應該使用的方法。
第二種方法是使用key
。 鍵用於在重新渲染時配對組件,以便 Vue 知道哪個舊組件與哪個新組件匹配。 如果key
發生變化,那么 Vue 將丟棄舊的子組件並創建一個新的子組件。 創建新組件時,它將運行created
的鈎子。 這可能不是您的方案的最佳方法,因為尚不清楚通過null
spec
時子組件應該做什么。
第三種方法是在子組件中使用watch
。 這將觀察spec
的值何時更改並將相關值復制到組件的本地數據屬性。 雖然在某些情況下使用這樣的watch
是合適的,但它通常表明組件設計存在潛在的弱點。
但是,您的代碼中還有其他問題...
created
的鈎子,您只需在data
function 中執行它。 請參閱https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow 。mesinActive
和mesinTab
都表示相同的基礎數據。 你不應該同時擁有data
。 至少一個應該是計算屬性,盡管我個人可能會完全擺脫mesinTab
。 而是使用 CSS 類來應用相關樣式,並只使用mesinActive
來決定應用哪些類(就像你在其他地方一樣)。 顯然,這同樣適用於其他xActive
/ xTab
屬性。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.