Статьи

Шаблоны проектирования для связи между компонентами Vue.js

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

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

Для разработчиков Vue.js важно знать все наиболее распространенные шаблоны, чтобы мы могли выбрать правильный для данного проекта. Это приведет к правильной и эффективной связи компонентов.

Когда мы создаем приложение с компонентной средой , такой как Vue.js , мы стремимся сделать компоненты нашего приложения максимально изолированными. Это делает их многоразовыми, ремонтопригодными и проверяемыми. Чтобы сделать компонент многократно используемым, нам нужно придать ему более абстрактную и отделенную (или слабо связанную) форму, и, таким образом, мы можем добавить его в наше приложение или удалить его, не нарушая его функциональность.

Однако мы не можем добиться полной изоляции и независимости компонентов нашего приложения. В какой-то момент им необходимо общаться друг с другом: обмениваться некоторыми данными, изменять состояние приложения и т. Д. Поэтому нам важно научиться правильно осуществлять эту связь, сохраняя при этом работоспособность, гибкость и масштабируемость приложения.

В Vue.js есть два основных типа связи между компонентами:

  1. Прямое общение между родителями , основанное на строгих отношениях между родителями и детьми и родителями.
  2. Межкомпонентная связь , при которой один компонент может «общаться» с любым другим независимо от их взаимосвязи.

В следующих разделах мы рассмотрим оба типа вместе с соответствующими примерами.

Стандартная модель взаимодействия компонентов, которую Vue.js поддерживает «из коробки», представляет собой модель типа «родитель-потомок», реализованную с помощью реквизита и пользовательских событий. На диаграмме ниже вы можете увидеть, как эта модель выглядит в действии.

Диаграмма связи между родителем и ребенком

Как видите, родитель может общаться только со своими непосредственными детьми, а дети могут общаться только напрямую со своими родителями. В этой модели невозможен родственный или межкомпонентный обмен данными.

В следующих разделах мы возьмем компоненты из диаграммы выше и реализуем их в серии практических примеров.

Давайте предположим, что компоненты, которые мы имеем, являются частью игры. Большинство игр отображают счет игры где-то в их интерфейсе. Представьте, что у нас есть переменная score объявленная в родительском компоненте A , и мы хотим отобразить ее в дочернем компоненте A. Итак, как мы можем это сделать?

Для отправки данных от родителя его дочерним элементам Vue.js использует реквизиты . Для передачи свойства необходимо выполнить три шага:

  1. Регистрация собственности у ребенка, вот так: props: ["score"]
  2. Используя зарегистрированное свойство в дочернем шаблоне, например: <span>Score: {{ score }}</span>
  3. Привязка свойства к переменной Score (в шаблоне родителя), например: <child-a :score="score"/>

Давайте рассмотрим полный пример, чтобы лучше понять, что на самом деле происходит:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// HTML part
 
<div id=»app»>
  <grand-parent/>
</div>
 
// JavaScript part
 
Vue.component(‘ChildB’,{
  template:`
    <div id=»child-b»>
      <h2>Child B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
    </div>`,
})
 
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ score }}
    </div>`,
  props: [«score»] // 1.Registering
})
 
Vue.component(‘ParentB’,{
  template:`
    <div id=»parent-b»>
      <h2>Parent B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
    </div>`,
})
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <child-a :score=»score»/> // 3.Binding
      <child-b/>
    </div>`,
  data() {
    return {
      score: 100
    }
  }
})
 
Vue.component(‘GrandParent’,{
  template:`
    <div id=»grandparent»>
      <h2>Grand Parent</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <parent-a/>
      <parent-b/>
    </div>`,
})
 
new Vue ({
  el: ‘#app’
})

Пример CodePen

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

01
02
03
04
05
06
07
08
09
10
props: {
   // Simple type validation
   score: Number,
   // or Complex type validation
   score: {
     type: Number,
     default: 100,
     required: true
   }
 }

При использовании реквизита, пожалуйста, убедитесь, что вы понимаете разницу между их буквальным и динамическим вариантами. Реквизит является динамическим, когда мы связываем его с переменной (например, v-bind:score="score" или его сокращением :score="score" ), и, таким образом, значение реквизита будет изменяться в зависимости от значения переменной. Если мы просто введем значение без привязки, то это значение будет интерпретировано буквально, и результат будет статическим. В нашем случае, если мы напишем score="score" , он будет отображать счет вместо 100 . Это буквальная опора. Вы должны быть осторожны с этой тонкой разницей.

До сих пор мы успешно отображали счет игры, но в какой-то момент нам нужно его обновить. Давайте попробуем это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  props: [«score»],
  methods: {
    changeScore() {
      this.score = 200;
    }
  }
})

Мы создали метод changeScore() , который должен обновлять счет после нажатия кнопки « Изменить счет» . Когда мы делаем это, кажется, что счет обновляется правильно, но мы получаем следующее предупреждение Vue в консоли:

[Vue warn]: избегайте прямого изменения свойства, так как значение будет перезаписываться при каждом повторном рендеринге родительского компонента. Вместо этого используйте данные или вычисляемое свойство на основе значения проп. Опора изменена: «оценка»

Как вы можете видеть, Vue говорит нам, что реквизит будет перезаписан, если родитель перерисовывает. Давайте проверим это, симулируя такое поведение с помощью встроенного метода $forceUpdate() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <button @click=»reRender»>Rerender Parent</button>
      <hr/>
      <child-a :score=»score»/>
      <child-b/>
    </div>`,
  data() {
    return {
      score: 100
    }
  },
  methods: {
    reRender() {
      this.$forceUpdate();
    }
  }
})

Пример CodePen

Теперь, когда мы изменим счет и затем нажмем кнопку Rerender Parent , мы увидим, что счет возвращается к исходному значению от родителя. Так что Vue говорит правду!

Имейте в виду, однако, что массивы и объекты будут влиять на их родителей, потому что они не копируются, а передаются по ссылке.

Таким образом, когда нам необходимо изменить опору у ребенка, есть два способа обойти этот побочный эффект повторного рендеринга.

Первый метод заключается в том, чтобы превратить реквизит score в локальное свойство данных ( localScore ), которое мы можем использовать в changeScore() и в шаблоне:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ localScore }}
    </div>`,
  props: [«score»],
  data() {
    return {
      localScore: this.score
    }
  },
  methods: {
    changeScore() {
      this.localScore = 200;
    }
  }
})

Пример CodePen

Теперь, если мы снова нажмем кнопку Rerender Parent , после того, как мы изменили счет, мы увидим, что на этот раз счет остается прежним.

Второй метод состоит в том, чтобы использовать свойство prop в вычисляемом свойстве, где оно будет преобразовано в новое значение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ doubleScore }}
    </div>`,
  props: [«score»],
  computed: {
    doubleScore() {
      return this.score * 2
    }
  }
})

Пример CodePen

Здесь мы создали вычисленную doubleScore() , которая умножает score родителя на два, а затем результат отображается в шаблоне. Очевидно, что нажатие кнопки Rerender Parent не будет иметь никакого побочного эффекта.

Теперь давайте посмотрим, как компоненты могут общаться противоположным образом.

Мы только что видели, как мутировать реквизит в дочернем элементе, но что, если нам нужно использовать этот реквизит в нескольких дочерних компонентах? В этом случае нам нужно будет поменять реквизит из его источника в родительском, так что все компоненты, которые используют реквизит, будут обновлены правильно. Чтобы удовлетворить это требование, Vue вводит пользовательские события .

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

  1. В дочернем процессе мы генерируем событие, описывающее изменение, которое мы хотим выполнить, например: this.$emit('updatingScore', 200)
  2. В родительском @updatingScore="updateScore" мы регистрируем прослушиватель событий для @updatingScore="updateScore" события, например: @updatingScore="updateScore"
  3. Когда событие this.score = newValue назначенный метод обновит реквизит, например: this.score = newValue

Давайте рассмотрим полный пример, чтобы лучше понять, как это происходит:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  props: [«score»],
  methods: {
    changeScore() {
      this.$emit(‘updatingScore’, 200) // 1. Emitting
    }
  }
})
 
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <button @click=»reRender»>Rerender Parent</button>
      <hr/>
      <child-a :score=»score» @updatingScore=»updateScore»/> // 2.Registering
      <child-b/>
    </div>`,
  data() {
    return {
      score: 100
    }
  },
  methods: {
    reRender() {
      this.$forceUpdate()
    },
    updateScore(newValue) {
      this.score = newValue // 3.Updating
    }
  }
})

Пример CodePen

Мы используем встроенный метод $emit() для генерации события. Метод принимает два аргумента. Первый аргумент — это событие, которое мы хотим выдать, а второй — новое значение.

Vue предлагает модификатор .sync , который работает аналогичным образом, и мы можем использовать его как ярлык в некоторых случаях. В таком случае мы используем метод $emit() немного другим способом. В качестве аргумента события мы ставим update:score следующим образом: this.$emit('update:score', 200) . Затем, когда мы связываем реквизит .sync , мы добавляем модификатор .sync следующим образом: <child-a :score.sync="score"/> . В родительском компоненте A мы удаляем метод updateScore() и регистрацию событий ( @updatingScore="updateScore" ), поскольку они больше не нужны.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  props: [«score»],
  methods: {
    changeScore() {
      this.$emit(‘update:score’, 200)
    }
  }
})
 
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <button @click=»reRender»>Rerender Parent</button>
      <hr/>
      <child-a :score.sync=»score»/>
      <child-b/>
    </div>`,
  data() {
    return {
      score: 100
    }
  },
  methods: {
    reRender() {
      this.$forceUpdate()
    }
  }
})

Пример CodePen

Vue предлагает два метода API, которые дают нам прямой доступ к родительским и дочерним компонентам: this.$parent и this.$children . Поначалу может быть заманчиво использовать их как более быструю и простую альтернативу реквизитам и событиям, но мы не должны этого делать. Это считается плохой практикой или анти-паттерном, потому что он формирует тесную связь между родительскими и дочерними компонентами. Последнее приводит к негибким и легко ломаемым компонентам, которые трудно отлаживать и обдумывать. Эти методы API используются редко, и, как правило, мы должны избегать их или использовать их с осторожностью.

Реквизит и события являются однонаправленными. Реквизит идет вниз, события идут вверх. Но, используя реквизиты и события вместе, мы можем эффективно взаимодействовать вверх и вниз по дереву компонентов, что приводит к двустороннему связыванию данных. Это на самом деле то, что директива v-model делает внутри.

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

Давайте посмотрим на диаграмму ниже:

Схема межкомпонентной связи

Как вы можете видеть, в этом типе связи «любой-к-любому» каждый компонент может отправлять и / или получать данные от любого другого компонента без необходимости промежуточных этапов и промежуточных компонентов.

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

Глобальная шина событий — это экземпляр Vue, который мы используем для генерации и прослушивания событий. Давайте посмотрим на практике.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const eventBus = new Vue () // 1.Declaring
 
 
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  props: [«score»],
  methods: {
    changeScore() {
      eventBus.$emit(‘updatingScore’, 200) // 2.Emitting
    }
  }
})
 
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <button @click=»reRender»>Rerender Parent</button>
      <hr/>
      <child-a :score=»score»/>
      <child-b/>
    </div>`,
  data() {
    return {
      score: 100
    }
  },
  created () {
    eventBus.$on(‘updatingScore’, this.updateScore) // 3.Listening
  },
  methods: {
    reRender() {
      this.$forceUpdate()
    },
    updateScore(newValue) {
      this.score = newValue
    }
  }
})

Пример CodePen

Вот шаги для создания и использования шины событий:

  1. Объявление нашей шины событий как нового экземпляра Vue, например: const eventBus = new Vue ()
  2. Излучение события из исходного компонента, например: eventBus.$emit('updatingScore', 200)
  3. Прослушивание eventBus.$on('updatingScore', this.updateScore) события в целевом компоненте, например: eventBus.$on('updatingScore', this.updateScore)

В приведенном выше примере кода мы удаляем @updatingScore="updateScore" из дочернего @updatingScore="updateScore" и вместо этого используем ловушку жизненного цикла @updatingScore="updateScore" created() для прослушивания события @updatingScore="updateScore" . Когда событие будет updateScore() будет выполнен метод updateScore() . Мы также можем передать метод обновления как анонимную функцию:

1
2
3
created () {
  eventBus.$on(‘updatingScore’, newValue => {this.score = newValue})
}

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

Для более сложных приложений, где вещи могут быстро выйти из-под контроля, мы должны рассмотреть специальный шаблон управления состоянием, такой как Vuex , который даст нам более детальный контроль, лучшую структуру и организацию кода, а также полезные функции отслеживания и отладки изменений ,

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

Здесь я дам вам краткое объяснение того, что такое Vuex, а также контекстуальный пример. Если вы хотите глубже погрузиться в Vuex, я предлагаю вам взглянуть на мое специальное руководство по созданию сложных приложений с помощью Vuex .

Давайте теперь рассмотрим следующую диаграмму:

Диаграмма связи компонентов Vuex

Как видите, приложение Vuex состоит из четырех отдельных частей:

  • Состояние , где мы храним данные нашего приложения.
  • Методы получения — это методы доступа к состоянию хранилища и его визуализации для компонентов.
  • Мутации являются актуальными и единственными методами, позволяющими изменять состояние.
  • Действия — это методы для выполнения асинхронного кода и запуска мутаций.

Давайте создадим простой магазин и посмотрим, как все это работает в действии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
const store = new Vuex.Store({
  state: {
    score: 100
  },
  mutations: {
    incrementScore (state, payload) {
      state.score += payload
    }
  },
  getters: {
    score (state){
      return state.score
    }
  },
  actions: {
    incrementScoreAsync: ({commit}, payload) => {
      setTimeout(() => {
        commit(‘incrementScore’, 100)
      }, payload)
    }
  }
})
 
Vue.component(‘ChildB’,{
  template:`
    <div id=»child-b»>
      <h2>Child B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
    </div>`,
})
 
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  computed: {
    score () {
      return store.getters.score;
    }
  },
  methods: {
    changeScore (){
      store.commit(‘incrementScore’, 100)
    }
  }
})
 
Vue.component(‘ParentB’,{
  template:`
    <div id=»parent-b»>
      <h2>Parent B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <button @click=»changeScore»>Change Score</button>
      <span>Score: {{ score }}
    </div>`,
  computed: {
    score () {
      return store.getters.score;
    }
  },
  methods: {
    changeScore (){
      store.dispatch(‘incrementScoreAsync’, 3000);
    }
  }
})
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <child-a/>
      <child-b/>
    </div>`,
})
 
Vue.component(‘GrandParent’,{
  template:`
    <div id=»grandparent»>
      <h2>Grand Parent</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <parent-a/>
      <parent-b/>
    </div>`,
})
 
new Vue ({
  el: ‘#app’,
})

Пример CodePen

В магазине у нас есть следующее:

  • Переменная score установленная в объекте состояния.
  • Мутация incrementScore() , которая будет увеличивать счет с заданным значением.
  • Получатель score() , который будет обращаться к переменной Score из состояния и отображать ее в компонентах.
  • Действие incrementScoreAsync() , которое будет использовать мутацию incrementScore() для увеличения значения по истечении заданного периода времени.

В экземпляре Vue вместо реквизитов мы используем вычисленные свойства, чтобы получить значение счетчика через геттеры. Затем, чтобы изменить счет, в store.commit('incrementScore', 100) компоненте A мы используем мутацию store.commit('incrementScore', 100) . В родительском компоненте B мы используем действие store.dispatch('incrementScoreAsync', 3000) .

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

Внедрение зависимостей позволяет нам определять сервис через свойство provide , которое должно быть объектом или функцией, которая возвращает объект, и сделать его доступным для всех потомков компонента, а не только для его прямых потомков. Затем мы можем использовать этот сервис через свойство inject .

Давайте посмотрим на это в действии:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Vue.component(‘ChildB’,{
  template:`
    <div id=»child-b»>
      <h2>Child B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ score }}
    </div>`,
  inject: [‘score’]
})
 
Vue.component(‘ChildA’,{
  template:`
    <div id=»child-a»>
      <h2>Child A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ score }}
    </div>`,
  inject: [‘score’],
})
 
Vue.component(‘ParentB’,{
  template:`
    <div id=»parent-b»>
      <h2>Parent B</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ score }}
    </div>`,
  inject: [‘score’]
})
 
Vue.component(‘ParentA’,{
  template:`
    <div id=»parent-a»>
      <h2>Parent A</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <span>Score: {{ score }}
      <child-a/>
      <child-b/>
    </div>`,
  inject: [‘score’],
  methods: {
    reRender() {
      this.$forceUpdate()
    }
  }
})
 
Vue.component(‘GrandParent’,{
  template:`
    <div id=»grandparent»>
      <h2>Grand Parent</h2>
      <pre>data {{ this.$data }}</pre>
      <hr/>
      <parent-a/>
      <parent-b/>
    </div>`,
  provide: function () {
    return {
      score: 100
    }
  }
})
 
new Vue ({
  el: ‘#app’,
})

Пример CodePen

Используя опцию Provide в компоненте Grand Parent , мы сделали переменную Score доступной для всех ее потомков. Каждый из них может получить к нему доступ, объявив свойство inject: ['score'] . И, как видите, оценка отображается во всех компонентах.

Примечание: привязки, которые создает внедрение зависимости, не являются реактивными. Итак, если мы хотим, чтобы изменения, внесенные в компоненте провайдера, были отражены в его потомках, мы должны присвоить объект свойству данных и использовать этот объект в предоставленном сервисе.

Причины, по которым мы не должны использовать this.$root , аналогичны тем, которые были описаны для this.$parent и this.$children — это создает слишком много зависимостей. Следует избегать использования любого из этих методов для связи между компонентами.

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

Выбор правильного шаблона зависит от проекта, в котором вы участвуете, или приложения, которое вы хотите построить. Это зависит от сложности и типа вашего приложения. Давайте рассмотрим наиболее распространенные сценарии:

  • В простых приложениях реквизит и события будут все, что вам нужно.
  • Для приложений среднего уровня потребуются более гибкие способы связи, такие как шина событий и внедрение зависимостей.
  • Для сложных крупномасштабных приложений вам определенно понадобится мощь Vuex как полнофункциональной системы управления состоянием.

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

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