Привет, поговорим о 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 не угодил(в комментах пишите причину – может я чего-то не знаю)…