【问题标题】:How to prevent parent component from reloading when changing a parameterised child component in Vue js在Vue js中更改参数化子组件时如何防止父组件重新加载
【发布时间】:2019-10-01 05:59:03
【问题描述】:

我有一个页面,其中包含证券列表(子组件)的 ClientPortfolio(父组件)加载到 v-data-table 列表中。

我遇到的问题是,每次单击列表中的安全性时都会完全重新加载 ClientPortfolio,从而导致刷新整个列表,从而导致滚动和选定的类重置,以及不必要的性能开销。 我查看了 Vue 的文档,似乎没有任何内容指出如何仅在子组件具有参数时刷新它,看起来父组件正在刷新,因为每次选择安全性时路由都会发生变化,尽管期望Vue 会知道只有 sub(嵌套路由)在改变,因此只需要重新加载子组件

https://github.com/vuejs/vue-router/issues/230 上解释了我得到的最接近的答案,但代码中没有解释如何实现这一点。

routes.js:

routes: [
    {
      path: '/client/:clientno/portfolios/:portfolioNo',
      component: ClientPortfolios,
      children: [
        { path: 'security/:securityNo', component: Security }
      ]     
    }, 
  ]

ClientPortfolios.vue 中的路由器链接:

 <router-link tag="tr" style="cursor:pointer"
              :to="`/client/${$route.params.clientno}/portfolios/${selectedPortfolioSequenceNo}/security/${props.item.SecurityNo}-${props.item.SequenceNo}`"
              :key="props.item.SecurityNo+props.item.SequenceNo">

            </router-link>

ClientPortfolios.vue 中的路由器视图(用于安全组件):

<v-flex xs10 ml-2>
      <v-layout>
          <router-view :key="$route.fullPath"></router-view>
      </v-layout>
    </v-flex>

感谢任何有关如何防止父级重新加载的提示。

编辑:试图更接近问题,我注意到每当我更改安全性时,ClientPortfolios 中的“Key”属性都会发生变化(如上面的 Vue 调试窗口所示),这可能是原因吗?有没有办法为 ClientPortfolios 组件分配一个密钥,尽管它不是一个子组件?或者在导航到不同证券时不更新其密钥的方法?

更新:完整代码

ClientPortfolios.vue

<template>
  <v-layout row fill-height>
    <v-flex xs2>
      <v-layout column class="ma-0 pa-0 elevation-1">
        <v-flex>
          <v-select v-model="selectedPortfolioSequenceNo" :items="clientPortfolios" box label="Portfolio"
            item-text="SequenceNo" item-value="SequenceNo" v-on:change="changePortfolio">
          </v-select>
        </v-flex>
        <v-data-table disable-initial-sort :items="securities" item-key="Id" hide-headers hide-actions
          style="overflow-y: auto;display:block;height: calc(100vh - 135px);">
          <template slot="items" slot-scope="props">
            <router-link tag="tr" style="cursor:pointer"
              :to="{ name: 'Security', params: { securityNo: props.item.SecurityNo+'-'+props.item.SequenceNo } }"
              >
            </router-link>

          </template>
          <template v-slot:no-data>
            <v-flex class="text-xs-center">
              No securities found
            </v-flex>
          </template>
        </v-data-table>
      </v-layout>
    </v-flex>

    <v-flex xs10 ml-2>
      <v-layout>
        <keep-alive>
          <router-view></router-view>
        </keep-alive>
      </v-layout>
    </v-flex>
  </v-layout>

</template>
<script>
  import Security from '@/components/Security'

  export default {
    components: {

      security: Security
    },
    data () {
      return {
        portfoliosLoading: false,
        selectedPortfolioSequenceNo: this.$route.params.portfolioNo,
        selectedPortfolio: null,
        securityNo: this.$route.params.securityNo
      }
    },
    computed: {
      clientPortfolios () {
        return this.$store.state.ClientPortfolios
      },
      securities () {
        if (this.clientPortfolios == null || this.clientPortfolios.length < 1) {
          return []
        }
        let self = this
        this.selectedPortfolio = global.jQuery.grep(this.clientPortfolios, function (portfolio, i) {
          return portfolio.SequenceNo === self.selectedPortfolioSequenceNo
        })[0]

        return this.selectedPortfolio.Securities
      }
    },
    mounted () {
      this.getClientPortfolios()
    },
    activated () {
    },
    methods: {
      changePortfolio () {
        this.$router.push({
          path: '/client/' + this.$route.params.clientno + '/portfolios/' + this.selectedPortfolioSequenceNo
        })
      },
      getClientPortfolios: function () {
        this.portfoliosLoading = true
        let self = this
        this.$store.dispatch('getClientPortfolios', {
          clientNo: this.$route.params.clientno
        }).then(function (serverResponse) {
          self.portfoliosLoading = false
        })
      }
    }
  }
</script>

安全.vue

<template>
  <v-flex>
    <v-layout class="screen-header">
      <v-flex class="screen-title">Security Details </v-flex>

    </v-layout>
    <v-divider></v-divider>
    <v-layout align-center justify-space-between row class="contents-placeholder" mb-3 pa-2>
      <v-layout column>
        <v-flex class="form-group" id="security-portfolio-selector">
          <label class="screen-label">Sequence</label>
          <span class="screen-value">{{security.SequenceNo}}</span>
        </v-flex>
        <v-flex class="form-group">
          <label class="screen-label">Security</label>
          <span class="screen-value">{{security.SecurityNo}}-{{security.SequenceNo}}</span>
        </v-flex>

        <v-flex class="form-group">
          <label class="screen-label">Status</label>
          <span class="screen-value-code" v-if="security.Status !== ''">{{security.Status}}</span>
        </v-flex>
      </v-layout>

    </v-layout>

  </v-flex>

</template>
<script>
  export default {
    props: ['securityNo'],
    data () {
      return {
        clientNo: this.$route.params.clientno,
        securityDetailsLoading: false
      }
    },
    computed: {
      security () {
        return this.$store.state.SecurityDetails
      }
    },

    created () {
      if (this.securityNo.length > 1) {
        this.getSecurityDetails()
      }
    },

    methods: {
      getSecurityDetails: function () {
        let self = this
        this.securityDetailsLoading = true

        this.$store.dispatch('getSecurityDetails', {
          securityNo: this.securityNo,
          clientNo: this.clientNo

        }).then(function (serverResponse) {
          self.securityDetailsLoading = false
        })
      }
    }
  }
</script>

路由器.js

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      component: Dashboard
    },
    {
      path: '/client/:clientno/details',
      component: Client,
      props: true
    },

    {
      path: '/client/:clientno/portfolios/:portfolioNo',
      component: ClientPortfolios,
      name: 'ClientPortfolios',
      children: [
        { path: 'security/:securityNo',
          component: Security,
          name: 'Security'
        }
      ]
    }
  ]
})

更新

只是为了更新这个已经有一段时间了,我终于找到了问题所在,这就是@matpie 在其他地方指出的,我发现我的 App.vue 是有 :key 的罪魁祸首添加到应用程序的最根目录:&lt;router-view :key="$route.fullPath" /&gt; 这是我从某个地方使用的模板,但从来不必查看它“正在工作”,删除密钥后,一切正常,标记 matpie 答案已接受。

【问题讨论】:

  • 你能提供 jsfiddle 或等效的吗?
  • 使用 vuex 存储当前选择的安全性,而不是使用路由器加载子组件不是更容易吗?然后,只要 vuex 存储属性发生更改,您就可以获取子数据。
  • 这将如何解决在更改安全性时重新加载 ClientPortfolio 的问题?我可以 vuex 来存储证券等,但父组件仍然需要重新加载一次。另外,我在每个投资组合中都有成千上万的证券,我不认为 vuex 旨在存储如此数量的数据。
  • 如果能分享更多代码就好了。您是否尝试过用 包装您的组件
  • 您没有碰巧在顶级 上定义了一个 :key 吗?如果是这样,那可能就是问题

标签: vue.js vuejs2 vue-component vue-router


【解决方案1】:

这里不用路由器。在根级别为选定的证券和投资组合声明两个变量,

根据所选投资组合列出证券。

在从显示的证券中选择证券时,使用更新根变量,

this.$root.selectedSecurityId = id;

您可以在安全组件级别使用watch

在根目录中,

<security selectedid="selectedSecurityId" />

在组件security

....
watch:{
   selectedid:function(){
   //fetch info and show
   }
}
... 

组件将如下所示,

<portfolio>
  //active. list goes here
</portfolio>
........
<security selectedid="selectedSecurityId">
//info goes here
</security>

以上方法将有助于避开路由器。希望这会有所帮助。

【讨论】:

  • 但如果没有路由器,用户能否在无需浏览客户和投资组合的情况下获得特定证券的最终 url?为了安全起见,复制和粘贴 url 对于用户通过电子邮件/聊天等进行交流很重要。
  • 这是一个非常好的方法。为此,我们可以在路由器中加载单个页面,并从 url 读取传递的安全性和投资组合并相应地加载。如果没有通过证券 ID 或投资组合,则显示默认页面。在将信息作为 URL 的一部分传递时设置顶级变量。
  • 使用像&lt;context&gt;/&lt;portfolio&gt;/&lt;securityId&gt;这样的URL。这样您就可以避免使用静态键序。
  • 但是如何使用 this.$root.selectedSecurityId = id 更新安全性将更新路由?我不明白
  • @Kajal... 你可以使用要点
【解决方案2】:

我曾经遇到过类似的问题。 IMO 它是由路径字符串解析引起的。 尝试为您的路线设置名称。并用一个对象替换您的路由器链接to 参数。 并删除 router-view :key 道具。它不需要在那里。它用于在路由更改时强制组件更新。这通常是错误代码的标志。您的组件 (Security) 应该对路由参数更新做出反应。不是父组件强制它。

所以,尝试将您的代码更改为:

routes: [
    {
      path: '/client/:clientno/portfolios/:portfolioNo',
      component: ClientPortfolios,
      name: "ClientPortfoliosName", // it can be anything you want. It`s just an alias for internal use.
      children: [
        { 
           path: 'security/:securityNo', 
           name: "PortfolioSecurities", // anyway, consider setting route names as good practice
           component: Security 
        }
      ]     
    }, 
  ]
 <router-link tag="tr" style="cursor:pointer"
              :to="{ name: 'PortfolioSecurities', params: { clientno: $route.params.clientno, portfolioNo: selectedPortfolioSequenceNo, securityNo: props.item.SecurityNo+'-'+props.item.SequenceNo } }"
              :key="props.item.SecurityNo+props.item.SequenceNo">

            </router-link>

它应该可以工作。

附:在您的router-link 中,您应指向您要导航到的路线。在这种情况下PortfolioSecurities

【讨论】:

  • 只是按照您的指示做了一切,仍然无法正常工作,同样的问题,一旦单击不同的安全性,ClientPortfolios 和安全性组件都会被激活。
  • 嗯...这很奇怪。您是否从路由器视图中删除了所有:key 绑定?正如您的屏幕截图所示,您还绑定到位于v-content 内的路由器视图。
  • 我做了,从 中删除了密钥,并且没有您在其中看到的 v-content 密钥或绑定屏幕截图自动显示,我不知道如何,但看起来唯一的来源是路由定义本身
  • 在您提供的屏幕截图中,有一个 key 道具绑定。它是必然的路线。 IMO,这会导致问题。你可以从你的 App.vue 或其他东西中发布代码吗?有路由器视图的部分?
  • 添加了完整代码,我没有看到任何可能影响 ClientPortfolios 拥有密钥的内容。
【解决方案3】:

防止组件重新加载是 Vue.js 中的默认行为。 Vue 的响应式系统会自动映射属性依赖关系,并且只执行最少量的工作来确保 DOM 是最新的。

通过在任何地方使用:key 属性,您是在告诉 Vue.js 该元素或组件应该在键匹配时匹配。如果密钥不匹配,旧的将被销毁并创建一个新的。

看起来您也在数据对象 (Security.vue) 上拉入路由参数。当路由参数更改时,这些不会更新,您应该将它们拉入计算属性,以便它们始终保持最新。

export default {
  computed: {
    clientNo: (vm) => vm.$route.params.clientno,
  }
}

这将确保clientNo 始终匹配在路由器中找到的内容,无论 Vue 是否决定重用此组件实例。当clientNo发生变化时,如果需要执行其他副作用,可以添加观察者:

vm.$watch("clientNo", (clientNo) => { /* ... */ })

【讨论】:

  • 我很欣赏这里有关数据与手表的信息,但这意味着如何解决安全更改时不触发 ClientPortfolios 组件的问题?除非我没有清楚地理解你的答案?目前我有一个portfolioNo(ClientPortfolio的参数)和securitNo(Security的参数),当securityNo同时更改ClientPortfolio和Security重新加载时,我只希望Security起作用,而不是ClientPortfolio。
  • @Maya,也许我误解了你所说的“重新加载”是什么意思。生命周期钩子是否再次执行? Vue 只重新评估过时的部分。这就是 Vue 数据驱动设计的本质。如果您看到完全重新加载的内容,可能是在您的代码库中的其他地方,而不是您迄今为止在此处共享的内容。
  • 是的,这正是我的意思,当我更改安全性时,在 ClientPortfolio 中触发了生命周期钩子(创建、安装和其余部分),这正是我要修复的问题,停止在 ClientPortfolio 上执行的生命周期挂钩,建议做什么?分享 App.vue 和 main.js 以及它的 ret 吗? (尽管据我所知,那里似乎没有做错或不寻常的事情)
  • 只是为了更新这个已经有一段时间了,我终于找出问题所在,这是@matpie在其他地方指出的,我发现我的 App.vue 是罪魁祸首有一个 :key 添加到应用程序的最根目录: 这是我从某个地方使用的模板,但从来不需要查看它,因为它正在“工作”,删除密钥后,一切正常,标记 matpie 答案已接受。谢谢!
  • 天哪,非常感谢您的回答。我已经为一个非常相似的问题苦苦挣扎了大约 2 周,有了你的回答,我终于设法让它按我想要的方式工作!如果可以的话,我会把我所有的名誉都给你!
【解决方案4】:

能否请您删除安全组件的本地注册后再次检查?因为它是由 vue 路由器自己处理的,所以不需要它。

components: { // delete this code

      security: Security
    },

【讨论】:

  • 完成,没有区别
猜你喜欢
  • 1970-01-01
  • 2018-12-01
  • 2019-12-18
  • 2017-07-10
  • 1970-01-01
  • 2019-09-30
  • 2019-05-02
  • 2018-10-18
  • 1970-01-01
相关资源
最近更新 更多