ОК, так что давайте рассмотрим, что происходит по порядку:
- Создается родительский компонент, запускающий хук
created
и инициирующий загрузку данных с сервера. - Родительский компонент выполняет рендеринг, создавая дочерние компоненты. Значение опоры для
spec
будет null
, поскольку данные еще не загружены, а singleProductSpec
по-прежнему null
. - Хук
created
для single-product-spec
работает. Поскольку this.spec
равно null
Я бы предположил, что это приводит к ошибке, хотя в вопросе об ошибке не упоминалось. - В какой-то момент в будущем загрузка данных завершается, обновляя значение
singleProductSpec
,Это зависимость рендеринга родительского компонента, поэтому этот компонент будет добавлен в очередь рендеринга. - Родительский компонент выполнит повторный рендеринг. Новое значение
singleProductSpec
будет передано как spec
prop в single-product-spec
. Новый экземпляр single-product-spec
будет не создан, он будет просто повторно использовать тот, который он создал, он сначала отобразил.
В этот момент больше ничего не произойдет. Хук created
single-product-spec
не будет перезапущен, так как он только что был создан.
Когда вы редактируете исходный код дочернего компонента, он вызывает горячую перезагрузку этого компонента. ,Точный эффект такого изменения будет отличаться, но часто это приведет к воссозданию этого потомка без воссоздания родителя. Поскольку родительский объект уже загрузил данные с сервера, вновь созданному дочернему элементу будет передано полностью заполненное значение spec
. Это позволяет читать его в хуке created
.
Есть несколько способов решить эту проблему.
Во-первых, мы можем избежать создания single-product-spec
до тех пор, пока данные не будут готовы:
<product-spec v-if="singleProductSpec" :spec="singleProductSpec" />
Это просто позволит избежать создания компонента во время первоначального рендеринга, так что при запуске хука дочернего элемента created
он получит доступ к нужным данным. Это, вероятно, тот подход, который вам следует использовать.
Второй способ сделать это - использовать key
. Ключи используются для сопряжения компонентов при повторном рендеринге, чтобы Vue знал, какой старый компонент соответствует какому новому компоненту. Если key
изменится, то Vue отбросит старый дочерний компонент и вместо этого создаст новый. При создании нового компонента он запускает хук created
. Это, вероятно, не лучший подход для вашего сценария, так как неясно, что должен делать дочерний компонент, когда передан spec
из null
.
Третий подход будет состоять в использовании watch
в дочернем компоненте. Это будет отслеживать, когда значение spec
изменится, и скопировать соответствующие значения в локальные свойства данных компонента. Несмотря на то, что в некоторых случаях использование watch
, как это целесообразно, обычно указывает на слабое место в дизайне компонента.
Однако в вашем коде есть другие проблемы ...
- Непонятно, зачем вы копируете значения из реквизита в локальные данные. Вы можете просто использовать опору напрямую. Если вы делаете это просто для того, чтобы дать им более короткие имена, просто используйте вместо этого вычисляемое свойство. Единственная законная причина для их копирования, как это, заключается в том, что значения свойств могут быть изменены внутри дочернего элемента, а свойство используется только для передачи начального значения. Даже в этом случае вы бы не использовали хук
created
, вы просто делали бы это внутри функции data
. См. https://vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow. - Вы дублируете все 5 раз для 5 вкладок. Это должно быть реализовано с использованием массива объектов, каждый из которых содержит все необходимые данные для вкладки.
- Свойства
mesinActive
и mesinTab
оба представляют одни и те же базовые данные. Вы не должны иметь оба в data
. По крайней мере, один должен быть вычисляемым свойством, хотя лично я, вероятно, просто избавлюсь от mesinTab
в целом. Вместо этого используйте CSS-классы, чтобы применить соответствующий стиль, и просто используйте mesinActive
, чтобы решить, какие классы применять (как у вас в другом месте). Очевидно, то же самое относится и к другим xActive
/ xTab
свойствам. - Ваши вкладки представляют собой одну форму выбора. Использование 5 логических значений для представления одного выбора не является подходящей структурой данных. Правильный способ сделать это - иметь одно свойство, которое идентифицирует текущую вкладку. Специфика может варьироваться, это может содержать индекс вкладки, или объект, представляющий данные вкладки, или идентификатор, представляющий вкладку.
- Вам не нужно использовать
let self = this
с функциями стрелок. Значение this
сохраняется из окружающей области.
Правильно реализованный код для 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>