Vue каратэ: основные приемчики

Привет, поговорим о vue-приемчиках… Под приемами я подразумеваю способы передачи данных между компонентами. Сразу скажу, что это не все приемы, а лишь те которые я использую и о которых помню на момент написания статьи. Погнали!

Events

Наверное самая простая вещь в фреймворке которая полностью идентична js-концепции событий. К примеру событие клика по кнопке

<!-- HTML -->
<button onclick="func"></button>
<!-- Vue -->
<button v-on:click="func"></button>
<!-- работает идеентично -->
<button @click="func"></button>

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

<!-- child-component -->
<button @click="$emit('custom-event',{})"></button>
<!-- parent-compponent -->
<child-component @custom-event="func"/>

Однако это работает только от ребенка к родителю

Управление ребенком

Здесь ивенты уже как-бы “не алё”, на помощь приходят либо ссылки либо пропсы

Начнем из менее лаконичного способа – ссылок. Выглядит это так(родитель)

<template>
  <div>
    <button @click="toggle">Click me!</button>
    <child-component ref="child"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  methods:{
    toggle(){
      this.$refs.child.collapse = !this.$refs.child.collapse
    }
  }
}
</script>

ребёнок

<template>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  data:function () {
    return {collapse:true}
  }
}
</script>

<style scoped>

</style>

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

Более правильно(как по мне) будет использование пропсов:

<template>
  <div>
    <button @click="toggle">Click me!</button>
    <child-component :collapse="collapse"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  data:function () {
    return {collapse:true}
  },
  methods:{
    toggle(){
      this.collapse = !this.collapse
    }
  }
}
</script>
<template>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  props:{
    collapse:Boolean
  }
}
</script>

<style scoped>

</style>

тут свойство collapse попадает в дату родителя, а в дочернем копроненте переносится в пропс.

Но у меня был третий вариант: тогл происходил внутри дочерних, а в родителе была кнопка “свернуть все”. Вот мое решение

<template>
  <div>
    <button @click="toggle">Close!</button>
    <child-component :close="close" @open="open"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  data:function () {
    return {close:false}
  },
  methods:{
    toggle(){
      this.close = true
    },
    open(){
      this.close = false
    }
  }
}
</script>
<template>
  <div>
    <button @click="toggle">Toggle</button>
  </div>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  data:function () {
    return {collapse:true}
  },
  props:{
    close:Boolean
  },
  methods:{
    toggle(){
      this.collapse = !this.collapse
      this.$emit('open')
    }
  },
  watch:{
    close:function (old) {
      if (old)
        this.collapse = true
    }
  }
}
</script>

Суть его в том, чтоб создать пропс и поставить его под наблюдение в watch. В таком варианте, как я навел, проще было б не лезть в дитя, а просто управлять по ссылке; но дело было сложнее и с ссылками не вариант. И еще – при тогле в вложенном компоненте , я емичу событие для обнуления в родитель.

Интересный случай с таблицей. Обработка форм

Задача была построить таблицу с чекбоксами разных настроек. Моя первая итерация данного компонента

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" @change="change(i)">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',
  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ],
      i:0
    }
  },
  methods:{
    change(i){
      this.settings[i].enabled = !this.settings[i].enabled
    }
  }
}
</script>

и это работает, но у меня возникла мысль – а как получить содержимое если это textarea? Кароч, правильный путь – забыть о change и тп, использовать директиву v-model

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" v-model="setting.enabled">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',
  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ]
    }
  },
}
</script>

но все же

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" @change="setting.enabled = $event.target.checked">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',

  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ]
    }
  },
}
</script>

для тех, кому v-model не угодил(в комментах пишите причину – может я чего-то не знаю)…