当前位置: 首页 > 知识库问答 >
问题:

Vue:收到父母道具后,子组件不会更改

陆宏壮
2023-03-14

所以我对vue的父子组件通信有问题。问题是,当我导航到一个组件后,它应该调用ajax从服务器获取数据。收到数据后,父组件应该通过道具将其发送给所有子组件,但道具数据不显示。子组件只有在我更改编辑器上的代码后才开始显示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>

正如我所说,我的单一产品规范组件的唯一问题不是它不会加载道具数据。问题是,当我在文本编辑器中更改代码时,它只加载道具数据(我知道这很奇怪)。当我开始调试时,我开始意识到这一点,当我在单个产品规范组件中更改代码时,道具数据开始加载。如果我不更改我的单个产品规格组件代码,那么无论我等待多长时间,道具数据都不会加载。

共有1个答案

艾嘉石
2023-03-14

好的,让我们按顺序来看看发生了什么:

  1. 父组件被创建,触发created钩子并从服务器启动数据加载
  2. 父组件渲染,创建子组件。spec的属性值将为null,因为数据尚未加载,singleProductSpec仍然为null
  3. 单个产品规范创建的钩子运行。如所示。specisnull我想这会抛出一个错误,尽管问题中没有提到错误
  4. 在将来的某个时候,数据加载完成,更新singleProductSpec的值。它是父组件的渲染依赖项,因此该组件将添加到渲染队列中
  5. 父组件将重新渲染。singleProductSpec的新值将作为spec道具传递给singleProductSpec。不会创建单个产品规范的新实例,它只会重新使用它首先创建的实例

在这一点上,不会有其他事情发生。单个产品规范的创建的钩子不会重新运行,因为它不是刚刚创建的。

当您编辑子组件的源代码时,它将触发该组件的热重载。这种更改的确切效果会有所不同,但通常会导致该子级被重新创建,而不会重新创建父级。由于父级已经从服务器加载了数据,新创建的子级将被传递完全填充的spec值。这允许在创建的钩子中读取它。

有很多方法可以解决这个问题。

首先,在数据准备就绪之前,我们可以避免创建单一产品规范

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

这将简单地避免在初始渲染期间创建组件,以便在运行子级的创建的钩子时,它可以访问所需的数据。这可能是您应该使用的方法。

第二种方法是使用。键用于在重新渲染时将组件配对,以便Vue知道哪个旧组件与哪个新组件匹配。如果发生更改,则Vue将丢弃旧的子组件,并创建一个新的子组件。创建新组件时,它将运行createdhook。对于您的场景,这可能不是最好的方法,因为不清楚当传递specnull时,子组件应该做什么。

第三种方法是在子组件中使用watch。这将监视spec的值何时更改,并跨相关值复制到组件的本地数据属性。虽然在某些情况下,像这样使用watch是合适的,但它通常表明组件设计中的潜在弱点。

但是,代码中还有其他问题。。。

  1. 不清楚为什么要首先将道具中的值复制到本地数据中。你可以直接使用道具。如果您这样做只是为了给它们取一个较短的名称,那么只需使用computed属性即可。这样复制它们的唯一合法原因是,属性值可以在子级中更改,并且道具仅用于传递初始值。即使在这种情况下,您也不会使用创建的钩子,您只需在数据函数中执行。看见https://vuejs.org/v2/guide/components-props.html#One-数据流的方式
  2. 你将5个标签的所有内容复制了5次。这应该使用对象数组来实现,每个对象包含一个选项卡的所有相关细节
  3. 属性mesinActivemesinTab都表示相同的基础数据。您不应该在数据中同时包含这两个属性。至少有一个应该是计算属性,尽管就个人而言,我可能会完全去掉mesinTab。相反,使用CSS类来应用相关的样式,只需使用mesinActive来决定应用哪些类(正如您在其他地方所做的那样)。显然,这同样适用于其他xActive/xTab属性
  4. 您的选项卡是单一选择的一种形式。使用5个布尔值表示一个选择不是合适的数据结构。正确的方法是使用单个属性来标识当前选项卡。细节可能会有所不同,它可能包含选项卡索引、表示选项卡数据的对象或表示选项卡的id
  5. 您不需要将let self=this与箭头函数一起使用。值从周围范围中保留

正确执行单一产品规范的代码应该会崩溃到几乎为零。您应该能够删除大约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>

 类似资料:
  • 我有以下结构: ChildComponent只是呈现值:

  • parentcomponent.js ChildComponent.js 为什么在单击后,子组件仍然显示旧值而不重新呈现?

  • 我是新来的,对一些事情有点困惑。我在网上读了许多文章,声称组件不能改变自己的道具,但是父组件可以改变其子组件的道具。然而,我没有看到任何真正展示如何做到这一点的东西。 我希望能够做到这一点: 然而,我根本不知道如何做到这一点——尽管我在React上读到的几乎所有材料都说父母可以改变孩子的道具。我能正常工作的如下: 当我只想重新渲染子视图时,父视图需要重新渲染,这对我来说似乎有点过分了。如何更改<代

  • 我正在做一个React-Native项目,我意识到React-Native似乎打破了React-flow(父到子)道具更新。 基本上,我是从“应用程序”组件调用“菜单”组件,将一个道具传递给“菜单”。然而,当我更新“应用程序”状态时,“菜单”上的道具应该更新,但这不会发生。我做错什么了吗? 这是我的密码: 一个pp.js 菜单js

  • 在父组件中: 我有一个布尔状态: 我将其发送到子组件: 根据布尔值,我希望它呈现不同的组件。

  • 我正在尝试制作一个很好的ApiWrapper组件来填充各种子组件中的数据。从我读到的所有内容来看,这应该是可行的:https://jsfidle.net/vinniejames/m1mesp6z/1/ 但由于某种原因,当父状态更改时,子组件似乎没有更新。 我是不是漏了什么?