Який правильний спосіб передавати реквізит як вихідні дані у 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.