Методы связи Vue2.0, вы поймете после прочтения

Vue.js

предисловие

vue это数据驱动视图, поэтому обмен данными между компонентами очень важен для Vue. Коммуникация компонентов Vue также является часто задаваемым вопросом на собеседованиях. Для нас очень важно хорошо освоить взаимодействие компонентов vue. Так как же компоненты взаимодействуют друг с другом? Эта статья относительно длинная и будет всесторонне описывать различные способы общения.Это должна быть самая полная статья о vue-общении.Если есть какие-то упущения, пожалуйста, укажите. Далее будет вращаться вокруг父子通信组件а также非父子组件通信Разверните, оно будет сопровождаться объяснением кода и диаграммой примера результата.

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

Во-первых, мы должны понять, чтоvue组件通信в основном делятся:

  • Связь между родительским и дочерним компонентами (конкретно разделенная на родительские компоненты, передающие значения дочерним компонентам, и дочерние компоненты, передающие значения родительским компонентам):props,$emit,.sync,v-model,ref,$parentа также$children
  • Связь между родительским компонентом и дочерним компонентом:$attrа также$listeners,provideа такжеinject
  • Связь между неродительскими и дочерними компонентами:eventbus,vuex

1. Связь между родительским и дочерним компонентами

1. реквизит (отец - "ребенок")

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

Ниже приведен пример, иллюстрирующий, как родительский компонент передает данные дочернему компоненту: в дочернем компонентеchild.vueПолучить родительский компонент вparent.vueданные вtitle: 周末安排так же какtodoList: ['吃饭', '睡觉', '打豆豆']

// 父组件 parent.vue
<template>
   <child :title="title" :list="todoList"></child>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            title: "周末安排",
            todoList: ['吃饭', '睡觉', '打豆豆']
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
    }
}
</script>
<style lang="scss" scoped>
    
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p class="title">{{title}}</p>
        <ul class="list">
            <li v-for="item in list" :key="item">{{item}}</li>
        </ul>
   </div>
</template>
<script>
export default {
    props: ['title','list'],
    data() {
        return {
        }
    },
    components:{
    },
    mounted() {
    },
    methods: {
    }
}
</script>
<style lang="scss">
    .todos{
        margin: 20px;
        .title{
            font-size: 20px;
            margin-bottom: 10px;
        }
        .list{
            list-style: square inside;
            font-size: 18px;
        }
        li{
            padding: 5px 5px;
        }
    }
</style>

WechatIMG299.png

реквизиты также могут быть установлены默认值,Следующим образом:

//child.vue
props: {
        title: {
            type: String,
            default: '默认-工作日'
        },
        list: {
            type: Array, 
            default: function () {
                // 对象或数组Array默认值必须从一个工厂函数获取
                return ['上班搬砖','喝快乐肥宅水','继续码']
            }
        }
    },

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

WechatIMG300.png

Значение типа реквизита:String,Number,Boolean,Array,Object,Date,Function,Symbol

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

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

вкратце:prop是单向下行绑定的.

2. $emit (ребенок - "родитель")

$emitСубка может быть достигнута по значению на родительский компонент. Победу$emitЗарегистрируйте событие, передайте данные в качестве параметра и передайте их в родительский компонент.$eventперенимать.

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

// 父组件 parent.vue
<template>
    <div class="todos-wrap">
        <child @clickTodo="clickTodo"></child>
        <p v-if="clickValue">点击了:{{clickValue}}</p>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            title: "周末安排",
            todoList: ['吃饭', '睡觉', '打豆豆'],
            clickValue: '',
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
        clickTodo($event){
            this.clickValue = $event
        }
    }
}
</script>
<style lang="scss" scoped>
    .todos-wrap{
        margin: 20px;
        font-size: 18px;
    }
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p class="title">{{title}}</p>
        <ul class="list">
            <li v-for="item in list" :key="item" @click="$emit('clickTodo', item)">{{item}}</li>
        </ul>
   </div>
</template>
<script>
export default {
    props: {
        title: {
            type: String,
            default: '默认-工作日'
        },
        list: {
            type: Array, 
            default: function () {
                // 对象或数组Array默认值必须从一个工厂函数获取
                return ['上班搬砖','喝快乐肥宅水','继续码']
            }
        }
    },
    // props: ['title','list'],
    data() {
        return {
        }
    },
    components:{
    },
    mounted() {
    },
    methods: {
    }
}
</script>
<style lang="scss">
    .todos{
        .title{
            font-size: 20px;
        }
        .list{
            list-style: square inside;
            margin: 10px 0;
        }
        li{
            padding: 5px 5px;
            cursor: pointer;
        }
    }
</style>

WechatIMG301.png

3. .sync (двусторонняя связь родитель-потомок)

В некоторых случаях нам может понадобиться «двусторонняя привязка» реквизита. В этот момент.syncна поле..syncМодификаторы существуют в качестве синтаксического сахара с компиляцией. Он будет продлен до свойства родительского компонента Auto-Updatev-onслушатель

<child :isShow.sync="showValue"></child> // 是语法糖,最后会被解析成
<child :isShow="showValue" @update:isShow="val => showValue = val"></child>

Затем дочерний компонент может передать значение родительскому компоненту следующим образом:

this.$emit('update:isShow', newValue)

Если вы все еще не понимаете старые правила, давайте попробуем написать код. Если мы хотим реализовать реализацию щелчка переключателя详情文字显示/隐藏, то вы можете сделать:

// 父组件 parent.vue
<template>
    <div class="todos-wrap">
        <p>这是一行标题</p>
        <child :isShow.sync="showValue"></child>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            showValue: true
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
    }
}
</script>
<style lang="scss" scoped>
    .todos-wrap{
        margin: 20px;
        font-size: 18px;
    }
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p v-if="isShow">这是一段详情。这是一段详情。这是一段详情。</p>
        <el-button @click="toggleShow">显示/隐藏详情</el-button>
   </div>
</template>
<script>
export default {
    props: ['isShow'],
    data() {
        return {
        }
    },
    components:{
    },
    mounted() {
    },
    methods: {
        toggleShow(){
            this.$emit('update:isShow', !this.isShow)
        }
    }
}
</script>
<style lang="scss">
    .todos{
        .title{
            font-size: 20px;
        }
        .list{
            list-style: square inside;
            margin: 10px 0;
        }
        li{
            padding: 5px 5px;
            cursor: pointer;
        }
    }
</style>

добиться эффекта

1.gif

Пользовательские компоненты Vue используют v-модель для достижения той же функции. Давайте перепишем этот функционал с помощью v-model, посмотрим.

4. V-модель (отец и ребенок двусторонняя связь)

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

<input v-model="something">// 是语法糖,最后会被解析成

<input :value="something" @input="something = $event.target.value">

Теперь, когда мы поняли теоретические знания, давайте запишем то, что мы только что использовали..syncреализовать функцию.

// 父组件 parent.vue
<template>
    <div class="todos-wrap">
        <p>这是一行标题</p>
        <child v-model="showValue"></child>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            showValue: true
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
    }
}
</script>
<style lang="scss" scoped>
    .todos-wrap{
        margin: 20px;
        font-size: 18px;
    }
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p v-if="value">这是一段详情。这是一段详情。这是一段详情。</p>
        <el-button @click="toggleShow">显示/隐藏详情</el-button>
   </div>
</template>
<script>
export default {
    props: ['value'], //接收一个 value prop,注意,这里用的是value
    data() {
        return {
        }
    },
    components:{
    },
    mounted() {
    },
    methods: {
        toggleShow(){
            this.$emit('input', !this.value); //触发 input 事件,并传入新值
        }
    }
}
</script>
<style lang="scss">
    .todos{
        .title{
            font-size: 20px;
        }
        .list{
            list-style: square inside;
            margin: 10px 0;
        }
        li{
            padding: 5px 5px;
            cursor: pointer;
        }
    }
</style>

1.gif

5. ref (родитель вызывает дочерний метод или обращается к дочерним данным)

ref: при использовании с обычным элементом DOM ссылка указывает наDOM 元素; при ссылке на дочерний компонент указывает на组件实例, мы можем напрямую вызвать метод компонента или получить доступ к данным через экземпляр.Вот пример, как получить значение дочернего компонента через родительский компонент и контролировать значение дочернего компонента +1

// 父组件 parent.vue
<template>
    <div class="todos-wrap">
        <child ref="child"></child>
        <el-button @click="changeChildValue">plus</el-button>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
        changeChildValue(){
            const child = this.$refs.child;
            console.log(child.number);  // 原始数据
            child.changeValue();  // 数据发生了改变
        }
    }
}
</script>
<style lang="scss" scoped>
    .todos-wrap{
        margin: 20px;
        font-size: 18px;
    }
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p>{{number}}</p>
   </div>
</template>
<script>
export default {
    data() {
        return {
            number: 1
        }
    },
    components:{
    },
    mounted() {
    },
    methods: {
        changeValue(){
            this.number ++
        }
    }
}
</script>
<style lang="scss">
    .todos{
        .title{
            font-size: 20px;
        }
        .list{
            list-style: square inside;
            margin: 10px 0;
        }
        li{
            padding: 5px 5px;
            cursor: pointer;
        }
    }
</style>

2.gif

6.$children / $parent

Продолжаем преобразовывать приведенный выше пример. Если вы используете$children / $parentдобиться общения. Перейдите непосредственно к коду:

// 父组件 parent.vue
<template>
    <div class="todos-wrap">
        <child></child>
        <el-button @click="changeChildValue">plus</el-button>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data() {
        return {
            msg: '父组件的值'
        }
    },
    components:{
       child
    },
    mounted() {
    },
    methods: {
        changeChildValue(){
            console.log(this.$children[0].number);  // 注意:this.$children是个数组
            this.$children[0].changeValue();  // 数据发生了改变
        }
    }
}
</script>
<style lang="scss" scoped>
    .todos-wrap{
        margin: 20px;
        font-size: 18px;
    }
</style>

// 子组件 child.vue
<template>
    <div class="todos">
        <p>{{parentVal}}</p>
        <p>{{number}}</p>
   </div>
</template>
<script>
export default {
    data() {
        return {
            number: 1
        }
    },
    components:{
    },
    mounted() {
    },
    computed:{
        parentVal(){
            return this.$parent.msg;
        }
    },
    methods: {
        changeValue(){
            this.number ++
        }
    }
}
</script>
<style lang="scss">
    .todos{
        .title{
            font-size: 20px;
        }
        .list{
            list-style: square inside;
            margin: 10px 0;
        }
        li{
            padding: 5px 5px;
            cursor: pointer;
        }
    }
</style>

Полученный эффект выглядит следующим образом:

3.gif

Уведомление:

  1. this.$parentа такжеthis.$childrenВозвращаемое значение не то же самое,this.$childrenЗначение представляет собой массив, аthis.$parentявляется объектом
  2. в #приложенииthis.$parentВы получаете экземпляр new Vue(), и в этом экземпляреthis.$parentРезультат не определен, а подкомпонент внизуthis.$childrenэто пустой массив

Во-вторых, связь родитель-потомок-Sun между компонентами

1. $attrа также$listeners

Когда мы пишем высокоуровневые компоненты, если есть Npropsи н$emitИнициированные события, вы можете использовать$attrа также$listenersЛегко решить, в противном случае каждый реквизит, передаваемый от родительского компонента к дочернему компоненту, мы должны явно объявить его в реквизитах дочернего компонента, прежде чем его можно будет использовать.

В результате наши подтепенные компоненты каждый раз необходимость утверждать много реквизитов. У него есть многоуровневые компоненты вложенные чехол, код будет выглядеть очень избыточным, с$attrа также$listenersНет необходимости передавать реквизит по одному слою за раз.

$attrs、$listenersВсе они являются компонентами родитель-потомок, которые могут быть междоменными и могут передаваться между компонентами родитель-потомок-внук. Вот пример использования$attrs、$listenersРеализовать родительский компонентparent.vueс внучатыми компонентамиgrandson.vueКоммуникация.

// parent.vue 父组件
<template>
  <div class="parent">
    <Child
      class="origin"
      style="color:red"
      :message="message"
      :number="this.number"
      @upNumber="upNumber"
      @input="(event) => { message = event }"
    />
  </div>
</template>

<script>
import Child from "./child";
export default {
  components: {
    Child
  },
  data() {
    return {
      message: "parent",
      number: 0
    };
  },
  methods: {
    upNumber (event) {
      this.number = event;
    }
  },
};
</script>
<style lang="scss">
.parent{
    padding: 20px;
}
</style>

// child.vue 子组件
<template>
   <Grandson
      v-bind="$attrs"
      v-on="$listeners"
    />
</template>

<script>
import Grandson from "./grandson";
export default {
  inheritAttrs:false,
  components: {
    Grandson
  },
};
</script>

// grandson.vue 孙子组件
<template>
  <div class="children" style="font-size:18px">
    {{$attrs.message}} 
    <p @click="$listeners.upNumber($attrs.number + 1)">点击数字实现递增{{$attrs.number}}</p>
  </div>
</template>

<script>
export default {
  inheritAttrs:false,
  mounted() {
    console.log(this.$attrs); // 不包含class 和 style
    console.log(this.$listeners);
    setTimeout(() => {
      // 用$emit跟$listeners都行
      // this.$emit("input", "children");
      // this.$emit('upNumber', this.$attrs.number + 1)
      this.$listeners.input("children")
      this.$listeners.upNumber(this.$attrs.number + 1)
    }, 1500);
  }
};
</script>

Эффект следующий:

4.gif

Уведомление:$attrСодержит привязки атрибутов, которые не распознаются (и не получены) как реквизиты в родительской области (classа такжеstyleКроме). Когда компонент не объявляет никаких реквизитов, сюда включаются все привязки родительской области (classа такжеstyleкроме), и может пройтиv-bind="$attrs"Передача внутренних компонентов — полезно при создании высокоуровневых компонентов.

inheritAttrs

inheritAttrsПо умолчанию установлено значение true, если не установлено значение false, привязки свойств, которые не считаются реквизитами в родительской области, будут «отступать» и применяться к корневому элементу дочернего компонента как обычные свойства HTML. Если мы установимinheritAttrs:falseЭто поведение по умолчанию будет удалено. Разве это не особенно непонятно, не паникуйте - см. сравнительную таблицу ниже двух, я думаю, вы поймете!

WechatIMG311.png

WechatIMG312.png

2. provideа такжеinject

provide/ injectЭто API, добавленный в vue2.2.0.Проще говоря, он передается в родительский компонент.provideдля предоставления переменных, а затем передать дочерний компонентinjectвводить переменные.

provideа такжеinjectВ основном используется при разработке высокоуровневых библиотек плагинов/компонентов. Не рекомендуется использовать в обычном коде приложения.provideа такжеinjectПозволяет компоненту-предку внедрять зависимость во все его потомки, независимо от того, насколько глубока иерархия компонентов, и это всегда будет действовать, пока установлены его восходящие и нисходящие отношения. Если вы знакомы с React, это похоже на контекстные функции React.

// parent.vue 父组件
<template>
  <div class="parent">
    <Child />
  </div>
</template>

<script>
import Child from "./child";
export default {
  components: {
    Child
  },
  provide: {
    message: 'parent'
  },
};
</script>
<style lang="scss">
.parent{
    padding: 20px;
}
</style>

// child.vue 子组件
<template>
   <Grandson
      v-bind="$attrs"
      v-on="$listeners"
    />
</template>

<script>
import Grandson from "./grandson";
export default {
  components: {
    Grandson
  },
  mounted() {
   
  }
};
</script>

// grandson.vue 孙子组件
<template>
  <div class="children" style="font-size:18px">
    {{message}}
  </div>
</template>

<script>
export default {
  inject: ['message'],
};
</script>

3. Связь между неродительскими и дочерними компонентами:

1.eventbus

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

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

Глобальный eventBus просто понимается как создание нового экземпляра vue в файле, а затем его предоставление.При его использовании вы можете импортировать этот модуль.

мы здесь, чтобы достичьcomp2.vueКcomp1.vueпередать данные. Сделайте простой аккумулятор.

// parent.vue 
<template>
  <div>
    <comp1></comp1>
    <comp2></comp2>
  </div>
</template>

<script>
import comp1 from './comp1.vue'
import comp2 from './comp2.vue'
export default {
  components: { comp1, comp2 }
}
</script>
// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

// parent.vue 
<template>
  <div>
    <comp1></comp1>
    <comp2></comp2>
  </div>
</template>

<script>
import comp1 from './comp1.vue'
import comp2 from './comp2.vue'
export default {
  components: { comp1, comp2 }
}
</script>
// comp1.vue 中接收事件
<template>
  <div>计算和: {{count}}</div>
</template>

<script>
import { EventBus } from './event-bus.js'
export default {
  data() {
    return {
      count: 0
    }
  },

  mounted() {
    EventBus.$on('add', param => {
      this.count = param.num;
    })
  }
}
</script>

// comp2.vue 中发送事件
<template>
  <div>
    <el-button @click="additionHandle">+累加</el-button>    
  </div>
</template>

<script>
import {EventBus} from './event-bus.js'
console.log(EventBus)
export default {
  data(){
    return{
      num:1
    }
  },

  methods:{
    additionHandle(){
      EventBus.$emit('add', {
        num: this.num++
      })
    }
  }
}
</script>

Недостатки: когда проект большой, eventBus сложно поддерживать.

2.vuex

Vuex не будет здесь вдаваться в подробности, рекомендуется перейти непосредственноофициальная документация

Суммировать

Далее еще одна ментальная карта, иллюстрирующая основные способы общения.

WechatIMG313.png

напиши в конце

Эта статья то и дело писалась в течение недели, главным образом потому, что я кое-что прочитал за это время.vue3.0, я думаю, что много общего. Я хочу знать себя лучшеvue2.0Сделайте резюме. после всего,vue3.0изcompositionМетод написания по-прежнему является большим изменением в коде. На рефакторинг кода нужно время, а vue2.0 по-прежнему главное.

Если вы сочтете это полезным, нажмите 👍 Ха, это величайшее поощрение для оригинального автора. Надеюсь, эта статья поможет вам.