Как правильно передавать реквизиты в качестве исходных данных в Vue.js 2?

102

Итак, я хочу передать реквизиты компоненту 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связан, но вы не можете перейти dataк компонентам, вы проходите props, но вы не можете изменить принятый propsни преобразовать propsв data. И что? Я понял одну вещь: вы должны передавать propsи запускать события. То есть, если компонент хочет изменить propsполученное, он должен вызвать событие и быть «повторно обработанным». Но тогда у вас остается one-wayпривязка точно такая же, как у React, и я не вижу в ней пользы data. Довольно запутанно.
Андре Пена
1
Данные в первую очередь предназначены для частного использования компонента. Все, что размещено на нем в контексте компонента, является реактивным и может быть привязано к нему. Концепция props заключается в том, чтобы передавать значения в компонент, но не позволять компоненту молча вносить изменения состояния в родительский элемент, изменяя переданное значение. Лучше сделать это явным в событии, как вы указали. Это было изменение философии с Vue 1.0 на 2.0.
Дэвид К. Хесс
Сегодня я попытался начать обсуждение
bgraves

Ответы:

109

Благодаря этому https://github.com/vuejs/vuejs.org/pull/567 теперь я знаю ответ.

Способ 1

Передайте начальную опору прямо в данные. Как пример в обновленных документах:

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

Но имейте в виду, что если переданная опора является объектом или массивом, который используется в состоянии родительского компонента, любая модификация этой опоры приведет к изменению состояния этого родительского компонента.

Предупреждение : этот метод не рекомендуется. Это сделает ваши компоненты непредсказуемыми. Если вам нужно установить родительские данные из дочерних компонентов, используйте управление состоянием, например Vuex, или используйте «v-модель».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
Так это нормально, когда изменения распространяются на родительский компонент?
игорь
1
@igor лично я бы старался избегать этого, насколько это возможно. Это сделает ваши компоненты непредсказуемыми. Я думаю, что лучший способ получить изменения в родительском компоненте - использовать метод «v-model», при котором вы $ испускаете изменения от дочернего компонента к родительскому компоненту. vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Доминик Серафин
да, а как насчет глубоких объектов? я должен копировать? я должен перепроектировать мои компоненты, чтобы не использовать глубокий объект? то есть опора с: { ok: 1, notOk: { meh: 2 } }если установлено на внутренние данные с помощью любого из предыдущих методов клонирования, все равно изменит опоруnotOk.meh
Nikso
2
Спасибо за отличный вопрос и ответ @DominikSerafin. Это прекрасно иллюстрирует ахиллесово исцеление Vue.js. В качестве фреймворка он так хорошо обрабатывает компонентность, но передача данных между компиляциями - это основная PITA. Вы только посмотрите на созданный кошмар номенклатуры. Итак, теперь мне нужно поставить перед всеми моими реквизитами префикс, initialчтобы иметь возможность использовать их в моем компе. Было бы намного чище, если бы мы могли просто передать свойство, вызываемое dataлюбой компом, и заставить его автоматически распространять локализованные данные внутри без всего этого initialPropNameбезумия.
AJB
1
@DominikSerafin Я слышу, что вы говорите, и вы не ошиблись. Но напишите приложение для создания форм с использованием Vue.js, и вы быстро поймете, что я имею в виду. На самом деле я меняю свою технику обмена данными с prop --> comp.dataиспользования Vue-Stash для всех данных (или Vuex тоже подойдет). Но теперь я просто перенаправляю свои данные с windowобъекта, как я делал это до того, как «современные» JS-фреймворки навязали мне свое мнение. Возникает вопрос: в чем вообще смысл свойства comp.data?
AJB
29

В дополнение к ответу @ 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. В приложениях на основе веб-пакетов очень легко предварительно настроить babel, чтобы в полной мере использовать современный javascript.
Игорь Парра
recordLocal: [...this.record](в моем случае запись представляет собой массив) у меня не сработало - после загрузки и проверки компонента vue, он recordсодержит элементы и recordLocalбыл пустым массивом.
Кристиан,
Когда я использую его, хотя свойство COMPUTED, все идет хорошо, у меня, когда я пытаюсь установить данные внутри, он не работает = (... Но я получил его, используяcomputed
felipe muner
Будь осторожен. Оператор распространения только мелкие клоны, поэтому для объектов, содержащих объекты или массивы, вы все равно будете копировать указатели вместо получения новой копии. Я использую cloneDeep из lodash.
Синди Конвей,
14

Я считаю, что вы делаете это правильно, потому что это то, что указано в документации.

Определите свойство локальных данных, которое использует начальное значение свойства в качестве начального значения

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

Джонан Пинеда
источник
4
Для пропсов с передачей по значению это нормально. В данном случае это объект, поэтому его изменение повлияет на родителя. На этой странице: «Обратите внимание, что объекты и массивы в JavaScript передаются по ссылке, поэтому, если опора является массивом или объектом, изменение самого объекта или массива внутри дочернего компонента повлияет на родительское состояние».
Джейк
У меня это работает и четко задокументировано. эта строка в ответе выше конфликтует с документацией vue, насколько я могу судить "Предупреждение: этот метод не рекомендуется. Это сделает ваши компоненты непредсказуемыми. Если вам нужно установить родительские данные из дочерних компонентов, используйте управление состоянием, например Vuex, или используйте "v-model". "
Натаниэль Ринк
2
Итак, после 4 лет и сотен часов обучения я вернулся к тому месту, где начал просто отталкивать вары от windowобъекта.
AJB