Как предотвратить перезагрузку родительского компонента при изменении параметризованного дочернего компонента в Vue js

У меня есть страница, где ClientPortfolio (родительский компонент), содержащий список ценных бумаг (дочерний компонент), загружаются в список v-data-table.

enter image description here

У меня проблема в том, что ClientPortfolio полностью перезагружается каждый раз, когда я щелкаю по безопасности в списке, в результате чего обновляется весь список, что приводит к сбросу прокрутки и выбранного класса, а также к ненужным потерям производительности. Я посмотрел документацию по Vue, и, кажется, ничто не указывает на то, как обновлять дочерний компонент только тогда, когда у него есть параметры, похоже, что родительский компонент обновляется при изменении маршрута каждый раз, когда выбирается защита, несмотря на то, что Vue будет знать, что меняется только sub (вложенный маршрут), следовательно, нужно только перезагрузить дочерний компонент

enter image description here

Самый близкий ответ, который я получил, был объяснен на 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 изменяется (как показано в окне отладки Vue выше) всякий раз, когда я меняю Security, может ли это быть причиной? Есть ли способ назначить ключ компоненту 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>

Security.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>

router.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 является виновником, где есть: ключ, добавляемый к сам корень приложения: <router-view :key="$route.fullPath"/> это был шаблон, который я использовал откуда-то, но мне никогда не приходилось смотреть, как он работал, после удаления ключа все работает Как и положено, маркировка Matpie ответа принята.

Ответ 1

Предотвращение перезагрузки компонента является поведением по умолчанию в 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) => { /* ... */ })

Ответ 2

Не могли бы вы проверить еще раз после удаления локальной регистрации компонента безопасности? Поскольку это не нужно, потому что это обрабатывается самим маршрутизатором vue.

components: { // delete this code

      security: Security
    },

Ответ 3

Вместо использования роутера здесь. Объявите две переменные на корневом уровне для выбранной безопасности и портфеля,

список ценных бумаг на основе выбранного портфеля.

при выборе ценной бумаги из отображаемых ценных бумаг обновите корневую переменную, используя

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>

Вышеуказанный подход поможет избежать роутеров. надеюсь, это поможет.

Ответ 4

У меня была похожая проблема однажды. ИМО это было вызвано разбором пути. Попробуйте установить имя для вашего маршрута. И замените вашу ссылку to маршрутизатор to param объектом. И удалите роутер-просмотр :key проп. Это не должно быть там. Он используется для принудительного обновления компонента при изменении маршрута. Обычно это признак плохого кода. Ваш компонент (Security) должен реагировать на обновление параметров маршрута. Не родительский компонент заставляет его.

Итак, попробуйте изменить свой код на:

routes: [
    {
      path: '/client/:clientno/portfolios/:portfolioNo',
      component: ClientPortfolios,
      name: "ClientPortfoliosName", // it can be anything you want. It 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>

И это должно работать.

PS В вашем router-link вы должны указать маршрут, по которому хотите перейти. В этом случае PortfolioSecurities