В Vue 3 есть два основных способа установить связь между дочерними и родительскими объектами. Использование реквизита и использование хранилища Vuex. В этом уроке мы рассмотрим оба ниже.

Общение ребенка с родителями с помощью реквизита

Первый способ, которым дочерние компоненты могут взаимодействовать со своими родительскими компонентами без использования дополнительных узловых пакетов, — это использовать функцию props платформы Vue. Возможно, вы использовали props раньше для передачи данных от родителя к дочернему, но с несколькими небольшими изменениями мы можем передать изменения данных обратно родителю.

То, что вы, возможно, уже знаете о реквизитах для передачи данных от родителя к дочернему, вероятно, выглядит примерно так:

<child :counter="counter" />

Теперь, если мы хотим, чтобы значение counter реагировало на то, что происходит в дочернем элементе, мы можем сделать это, просто добавив v-model к реквизиту следующим образом.

<child v-model:counter="counter" />

Допустим, ребенку может понадобиться что-то сделать со значением счетчика. Ваш код может выглядеть примерно так

<template>
  <div>Child Counter: {{ counter }} </div>
  <button @click="increment">Increment Counter</button>
</template>
<script>
export default {
  props: {
   counter: {
    type: Number
   }
  },  
  data: () => ({
   childCounter: 0
  }),
  created() {
   // the prop needs to be re-assigned here. Vue doesn't like prop          mutation
   this.childCounter = this.counter
  },
  methods: {
   increment() {
    this.childCounter++
   }
  }
}
</script>

Но это не обновляет значение счетчика, о котором знает родитель. Так что же нам делать?

Мы уже сказали дочернему компоненту в родительском компоненте прослушивать обновления для счетчика с помощью v-model . Чтобы подключиться к этому слушателю, мы изменим наш метод приращения с помощью this.$emit('update:myProp', newPropValue)

increment() {
  this.childCounter++
  this.$emit('update:counter', this.childCounter)
}

Полный код выглядит так:

Родительский компонент

#Parent.vue
<template>
  <div>Parent Counter: {{ counter }}</div>
  <child v-model:counter="counter" />
</template>
<script>
import Child from './Child'
export default {
  components: {
   Child,
  },
  data: () => ({
   counter: 0,
  }),
}
</script>

Дочерний компонент

<template>
  <div>Child Counter: {{ childCounter }} </div>
  <button @click="increment">Increment Counter</button>
</template>
<script>
export default {
  props: {
   counter: {
    type: Number
   }
  },  
  data: () => ({
   childCounter: 0
  }),
  created() {
   // the prop needs to be re-assigned here. Vue doesn't like prop mutation
   this.childCounter = this.counter
  },
  methods: {
   increment() {
    this.childCounter++
    this.$emit('update:counter', this.childCounter)
   }
  }
}
</script>

Взаимодействие ребенка и родителя с Vuex Store

Использование props в компонентах — это всего лишь один из способов, с помощью которого ребенок может передавать изменения данных родителям. Vue предлагает нам еще один способ достичь этой цели с помощью Vuex Store. Хранилище — это место, где данные хранятся в течение всего сеанса в нашем приложении. Чтобы начать работу с Vuex add, вам нужно добавить его в свое приложение. Нам нужно добавить vuex@next в наш проект, а не vuex, потому что vuex@next работает с Vue 3, а vuex все еще работает с Vue 2 на момент написания этой статьи.

$>npm i vuex@next

Затем нам нужно настроить наш магазин в приложении. Сначала добавим файл сценария магазина. Мы добавим к этому позже

#src/store/store.js
import { createStore } from 'vuex'
export default createStore({})

А затем зарегистрируйте магазин с вашим приложением

#src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store/store'
const app = createApp(App)
app.use(store)
app.mount('#app')

Как только это будет сделано, нам понадобится state со значением, которое содержит counter, а также способ mutate этого состояния.

import { createStore } from 'vuex'
const state = {
  counter: 0
}
const mutations = {
  increment: (state) => state.counter++
}
export default createStore({
  state,
  mutations
})

Чтобы получить это значение из хранилища, достаточно добавить в компонент вычисляемое свойство, которое возвращает значение счетчика в хранилище. Это может быть где угодно в приложении, где вам нужно значение, будь то родитель дочернего элемента, выполняющего мутацию, или совершенно другое место.

computed: {
  counter() {
    return store.state.counter
  }
}

И чтобы увеличить счетчик, нам просто нужно commit a mutation в методе вашего компонента

methods: {
  increment() {
    store.commit('increment')
  }
}

Результирующий код будет выглядеть так:

Дочерний компонент

<template>
  <div>Child Counter: {{ counter }} </div>
  <button @click="increment">Increment Counter</button>
</template>
<script>
import store from '../store/store'
export default {
  computed: {
    counter() {
      return store.state.counter
    }
  },
  methods: {
    increment() {
      store.commit('increment')
    }
  }
}
</script>

Родительский компонент

<template>
  <div>Parent Counter: {{ counter }}</div>
  <child />
</template>
<script>
import store from '../store/store'
import Child from './Child'
export default {
  components: {
    Child,
  },
  computed: {
    counter() {
      return store.state.counter
    }
  }
}
</script>

Вывод

Мы изучили два основных способа взаимодействия компонентов в Vue 3. Конечно, есть и другие решения и разные реализации магазина. Вы должны найти тот, который лучше всего подходит для вас и задачи, которую вы пытаетесь выполнить.

Мой исходный код, включая приведенные выше примеры, можно найти здесь: https://github.com/jacobtshirt/vue-child-parent-commucation

Документы Vue: https://v3.vuejs.org/

Документы Vuex: https://next.vuex.vuejs.org/