Який правильний спосіб передавати реквізит як вихідні дані у Vue.js 2?


97

Отже, я хочу передати реквізит компоненту Vue, але я очікую, що цей реквізит зміниться в майбутньому всередині цього компонента, наприклад, коли я оновлюю цей компонент Vue зсередини за допомогою AJAX. Тож вони призначені лише для ініціалізації компонента.

Мій cars-listелемент компонента Vue, куди я передаю реквізит із початковими властивостями single-car:

// cars-list.vue

<script>
    export default {
        data: function() {
            return {
                cars: [
                    {
                        color: 'red',
                        maxSpeed: 200,
                    },
                    {
                        color: 'blue',
                        maxSpeed: 195,
                    },
                ]
            }
        },
    }
</script>

<template>
    <div>
        <template v-for="car in cars">
            <single-car :initial-properties="car"></single-car>
        </template>
    </div>
</template>

То, як я це роблю зараз, це те, що всередині мого single-carкомпонента я призначаю this.initialPropertiesсвоєму this.data.propertiesна created()гачку ініціалізації. І це працює і реагує.

// single-car.vue

<script>
    export default {
        data: function() {
            return {
                properties: {},
            }
        },
        created: function(){
            this.data.properties = this.initialProperties;
        },
    }
</script>

<template>
    <div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>

Але моя проблема в тому, що я не знаю, чи правильно це зробити? Хіба це не спричинить мені деяких неприємностей у дорозі? Або є кращий спосіб це зробити?


13
На мою думку, це найбільш заплутане у Vue: кожен пов'язаний, але ви не можете перейти до компонентів, ви dataпередаєте two-way, але ви не можете змінити отримане та перетворити на . Тоді що? Одне, що я дізнався, - це те, що вам слід передавати і викликати події. Тобто, якщо компонент хоче змінити отриманий, він повинен викликати подію і бути «повторно відтвореним». Але тоді у вас залишається прив’язка точно так само, як React, і я не бачу використання на той час. Досить заплутано. datapropspropspropsdatapropspropsone-waydata
Андре Пена,

1
Дані в основному призначені для приватного використання компонента. Все, що розміщено на ньому в контексті компонента, реагує і може бути пов’язане з ним. Концепція реквізиту полягає в тому, щоб передавати значення в компонент, але утримувати компонент від можливості мовчки вводити зміни стану в батьківському, змінюючи передане значення. Краще зробити це явним у події, як ви вказали. Це була зміна філософії з Vue 1.0 на 2.0.
Девід К. Гесс,

Сьогодні я спробував розпочати тему тут: forum.vuejs.org/t/…
bgraves

Відповіді:


103

Завдяки цьому https://github.com/vuejs/vuejs.org/pull/567 я знаю відповідь зараз.

Спосіб 1

Передайте початковий опис безпосередньо даним. Як приклад в оновлених документах:

props: ['initialCounter'],
data: function () {
    return {
        counter: this.initialCounter
    }
}

Але майте на увазі, якщо переданий prop є об’єктом або масивом, який використовується у стані батьківського компонента, будь-яка зміна цього prop призведе до зміни цього батьківського компонента.

Попередження : цей метод не рекомендується. Це зробить ваші компоненти непередбачуваними. Якщо вам потрібно встановити батьківські дані з дочірніх компонентів, використовуйте управління станом, як Vuex, або використовуйте "v-model". https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Спосіб 2

Якщо ваш початковий опис є об'єктом або масивом, і якщо ви не хочете, щоб зміни у дочірньому стані поширювались на батьківський стан, просто використовуйте, наприклад, Vue.util.extend[1], щоб зробити копію реквізиту, замість цього вказуючи його безпосередньо на дочірні дані, наприклад:

props: ['initialCounter'],
data: function () {
    return {
        counter: Vue.util.extend({}, this.initialCounter)
    }
}

Спосіб 3

Ви також можете використовувати оператор поширення для клонування реквізиту. Детальніше у відповіді Ігоря: https://stackoverflow.com/a/51911118/3143704

Але майте на увазі, що оператори поширення не підтримуються у старих браузерах, і для кращої сумісності вам потрібно буде переписати код, наприклад, використовуючи babel.

Виноски

[1] Майте на увазі, що це внутрішня утиліта Vue, і вона може змінитися з новими версіями. Можливо, ви захочете скористатися іншими методами для копіювання цього реквізиту, див. " Як правильно клонувати об'єкт JavaScript? ".

Моя скрипка, де я її тестував: https://jsfiddle.net/sm4kx7p9/3/


1
тож це нормально, якщо зміни поширюються на батьківський компонент?
igor

1
@igor особисто я намагався би уникати цього якнайбільше. Це зробить ваші компоненти непередбачуваними. Я думаю, що кращий спосіб отримати зміни в батьківському компоненті - це використовувати метод "v-model", коли ви випромінюєте зміни з вашого дочірнього компонента на батьківський компонент. vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Домінік Серафін,

так, але як щодо глибоких предметів? чи слід глибоко копіювати? чи слід переробляти мої компоненти, щоб не використовувати глибокий об'єкт? тобто проп з: { ok: 1, notOk: { meh: 2 } }якщо встановити внутрішні дані за допомогою будь-якого з попередніх методів клонування, він все одно змінить пропnotOk.meh
Ніксо

1
Дякуємо за чудове запитання та відповідь @DominikSerafin. Це чудово ілюструє ахілесове зцілення Vue.js. Як фреймворк він настільки добре обробляє компонентизацію, але передача даних між компіляторами є головним PITA. Тільки подивіться на номенклатурний кошмар, створений. Отже, тепер мені потрібно initialдодати до мого реквізиту префікс лише для того, щоб мати можливість використовувати їх у своєму комп. Було б набагато чистіше, якби ми могли просто передати підказку, що викликається dataдо будь-якого комп, і дозволити йому автоматично розповсюджувати локалізовані дані всередині без усього цього initialPropNameбожевілля.
AJB

1
@DominikSerafin Я чую, що ти кажеш, і ти не помиляєшся. Але напишіть додаток для створення форм за допомогою Vue.js, і ви швидко зрозумієте, що я маю на увазі. Я фактично переробляю свою техніку обміну даними з prop --> comp.dataвикористанням Vue-Stash для всіх даних (або Vuex теж буде працювати). Але зараз я просто відбиваю свої дані від windowоб’єкта, як це робив раніше, перш ніж «сучасні» фреймворки JS нав'язали мені свою думку. Тож виникає питання: у чому сенс властивості comp.data взагалі?
AJB

25

У супутнику відповіді @ dominik-serafin:

Якщо ви передаєте об'єкт, ви можете легко його клонувати за допомогою оператора поширення (синтаксис ES6):

 props: {
   record: {
     type: Object,
     required: true
   }
 },

data () { // opt. 1
  return {
    recordLocal: {...this.record}
  }
},

computed: { // opt. 2
  recordLocal () {
    return {...this.record}
  }
},

Але найголовніше - пам’ятати про використання opt. 2, якщо ви передаєте обчислене значення або більше, ніж асинхронне значення. В іншому випадку місцеве значення не оновиться.

Демо:

Vue.component('card', { 
  template: '#app2',
  props: {
    test1: null,
    test2: null
  },
  data () { // opt. 1
    return {
      test1AsData: {...this.test1}
    }
  },
  computed: { // opt. 2
    test2AsComputed () {
      return {...this.test2}
    }
  }
})

new Vue({
  el: "#app1",
  data () {
    return {
      test1: {1: 'will not update'},
      test2: {2: 'will update after 1 second'}
    }
  },
  mounted () {
    setTimeout(() => {
      this.test1 = {1: 'updated!'}
      this.test2 = {2: 'updated!'}
    }, 1000)
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app1">
  <card :test1="test1" :test2="test2"></card>
</div>

<template id="app2">
  <div>
    test1 as data: {{test1AsData}}
    <hr />
    test2 as computed: {{test2AsComputed}}
  </div>
</template>

https://jsfiddle.net/nomikos3/eywraw8t/281070/


Привіт @ igor-parra, здається, ти надіслав дубльовані відповіді. А також може бути непогано згадати, що оператор поширення підтримується не у всіх браузерах, і для кращої сумісності може знадобитися крок транпіляції. В іншому випадку це виглядає чудово - дякую за додавання цього альтернативного методу!
Домінік Серафін

@ dominik-serafin Так, ви праві, я додав посилання на ES6. У додатках, заснованих на webpack, так легко мати попередньо налаштований babel, щоб повною мірою використовувати сучасний javascript.
Ігор Парра

recordLocal: [...this.record](у моєму випадку запис - це масив) у мене не спрацював - після завантаження та перевірки компонента vue, recordмістить елементи та recordLocalбув порожнім масивом.
Крістіан

Коли я використовую його, хоча властивість computed
COMPUTED, він справляється

Будьте обережними. Оператор розповсюдження лише неглибокі клони, тому для об’єктів, що містять об’єкти або масиви, ви все одно скопіюєте вказівники замість отримання нової копії. Я використовую cloneDeep з lodash.
Сінді Конвей

13

Я вважаю, що ви робите це правильно, оскільки саме це зазначено в документах.

Визначте локальну властивість даних, яка використовує початкове значення пропа як початкове значення

https://vuejs.org/guide/components.html#One-Way-Data-Flow


4
Для реквізиту, що передає значення, це нормально. У цьому випадку це об’єкт, тому його зміна вплине на батьківського. З цієї сторінки: "Зверніть увагу, що об'єкти та масиви в JavaScript передаються за посиланням, тому, якщо властивість є масивом або об'єктом, мутація самого об'єкта або масиву всередині дочірнього компонента вплине на батьківський стан."
Jake

Це працює для мене і чітко задокументовано. цей рядок у відповіді вище конфліктує з документацією vue, наскільки я можу сказати "Попередження: цей метод не рекомендується. Він зробить ваші компоненти непередбачуваними. Якщо вам потрібно встановити батьківські дані з дочірніх компонентів, або використовуйте управління станом, як Vuex, або використовуйте "v-model". "
Натаніель

Отже, через 4 роки та сотні годин навчання, я повернувся туди, де я почав, просто відбиваючи вар від windowоб’єкта.
AJB
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.