Створення «Renderless» компонентів у Vue

10 хв. читання

Досить часто Vue називають «дитиною» React та Angular. У такій аналогії справді є сенс. Vue користується популярністю, що не дивно, зважаючи на його малу криву вивчення. До того ж, Vue дає розробникам усю можливу владу над компонентами та їх реалізацією. У статті переконаємось у цьому.

Терміном renderless компонент позначають компоненти, що нічого не відображають. Дізнаємось як використовувати функцію render() для створення таких компонентів, а також як Vue обробляє рендеринг компонентів.

Як Vue рендерить компонент. Розвінчуємо міфи

Існує декілька способів визначення розмітки компонента у Vue :

  • Однофайлові компоненти: визначаємо компонент та його розмітку як у звичайному HTML-файлі;
  • Властивість компонента template, з якою можемо використовувати Template Literals у JavaScript;
  • Властивість компонента el, що вказує Vue отримати розмітку з DOM і використати її як шаблон;

Врешті-решт, Vue і всі його компоненти — просто JavaScript.

Ви можете не погодитись, і вас можна зрозуміти, зважаючи на кількість написаного HTML та CSS. Наочний приклад: однофайлові компоненти.

З однофайловими компонентами визначити компонент Vue можна так:

<template>
  <div class="mood">
    {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
  </div>
</template>

<script>
  export default {
    data: () => ({ todayIsSunny: true })
  }
</script>

<style>
  .mood:after {
    content: '🎉🎉';
  }
</style>

Після такого коду складно сказати: «Vue — просто JavaScript». Але це так. Нашим представленням дійсно легше керувати стилями та іншими ресурсами з Vue, але це не прямий обов'язок фреймворка. Для цього на етапі збірки використовується Webpack.

Коли Webpack зустрічає .vue файл, він пропускає його через процес перетворення. У цей час CSS вилучається з компонента і розміщується в окремому файлі, а усе інше перетворюється на JavaScript. Отримаємо щось на зразок:

export default {
  template: `
    <div class="mood">
      {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
    </div>`,
  data: () => ({ todayIsSunny: true })
}

Тепер помітно різницю. Щоб зрозуміти що буде далі, нам треба поговорити про компілятор шаблонів.

Компілятор шаблонів та render()

Наступний етап створення компонентів необхідний для компіляції та реалізації усіх оптимізацій Vue.

Коли компілятор шаблонів зустрічає такий код:

{
  template: `<div class="mood">...</div>`,
  data: () => ({ todayIsSunny: true })
}

... він компілює вміст властивості template у JavaScript. Далі функція render() додається до об'єкта компонента. Вона повертатиме перетворений на JavaScript вміст властивості template.

Так виглядатиме функція render():

render(h) {
  return h(
    'div',
    { class: 'mood' },
    this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me'
  )
}

Тепер, коли об'єкт компонента передано у Vue, render() проходить певну оптимізацію і перетворюється на VNode (віртуальний вузол). З VNode працює snabbdom (внутрішня бібліотека Vue для управління Virtual DOM).

VNode — це рендеринг компонентів у Vue. До речі, render() також дозволяє використовувати JSX у Vue.

Нам не потрібно чекати поки Vue додасть render() за нас, ми можемо визначити цю функцію, і вона матиме пріоритет над властивостями el або template. Більше про render() та її властивості тут.

Примітка: У процесі збірки компонентів Vue з Vue CLI або деяким іншим способом, вам не потрібно імпортувати компілятор шаблонів, який роздуватиме розмір build-файлу. Ваші компоненти — легкі JavaScript-файли, що також попередньо оптимізовані для більшої продуктивності.

Renderless компоненти у Vue

Як вже згадувалось, renderless компоненти нічого не відображають.

Навіщо нам компоненти, які нічого не рендерять?

Можна пояснити все створенням абстракції загального функціонала компонента та його розширенням для створення кращих та надійніших компонентів.

Згадаймо принципи S.O.L.I.D. Згідно з принципом єдиної відповідальності:

У класу повинно бути одне призначення.

Можна використати таку концепцію і для Vue. Тоді кожен компонент повинен мати одну відповідальність.

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

Так ми порушуємо принцип відкритості/закритості у S.O.L.I.D. Його суть:

Клас (у нашому випадку компонент) повинен бути відкритим для розширень, але закритим для модифікацій.

Тобто замість того, щоб редагувати сирцевий код компонента, вам слід його розширити. Оскільки Vue приймає принципи S.O.L.I.D, компоненти мають props, події, слоти, слоти з обмеженою областю видимості, що дозволяють на одному диханні взаємодіяти з компонентами та розширювати їх. Тепер можна переходити до збірки компонентів з усіма їх фічами, але без будь-яких стилів чи розмітки. Так набагато краще для ефективності та повторного використання.

Створюємо renderless компонент Toggle

Тут усе просто: нам не треба налаштовувати проект Vue CLI.

Компонент Toggle дозволить вам перемикати стани «on» та «off» за допомогою хелперів. Матеріал буде корисним для створення будь-яких компонентів зі станами on/off.

Створимо заглушку для нашого компонента. Перейдіть до секції з JavaScript на CodePen та розмістіть там код:

// toggle.js
const toggle = {
  props: {
    on: { type: Boolean, default: false }
  },
  render() {
    return []
  },
  data() {
    return { currentState: this.on }
  },
  methods: {
    setOn() {
      this.currentState = true
    },
    setOff() {
      this.currentState = false
    },
    toggle() {
      this.currentState = !this.currentState
    }
  }
}

Тут все ще не завершено. Нам потрібен шаблон, та оскільки ми не хочемо, щоб цей компонент щось рендерив, треба переконатися, що він працює з будь-яким іншим звичайним компонентом.

Натяк на слоти!

Слоти у renderless компонентах

Слоти дозволяють розміщувати контент між тегами компонента Vue. Наприклад:

<toggle>
  This entire area is a slot.
</toggle>

Щоб визначити слот, пишемо в однофайлових компонентах Vue наступне:

<template>
  <div>
    <slot/>
  </div>
</template>

Щоб зробити те ж саме з render():

// toggle.js
render() {
  return this.$slots.default
}

Тепер ми можемо автоматично розмістити контент всередині нашого компонента toggle. І ніякої розмітки.

Передача даних деревом за допомогою слотів з обмеженою областю видимості

У файлі toggle.js є стан on та деякі хелпери в об'єкті methods. Було б непогано дати розробнику доступ до них. Зараз ми використовуємо слоти, а вони не дозволяють отримувати будь-що з дочірніх компонентів.

Scoped slots — те, що нам потрібно. Слоти з обмеженою областю видимості працюють як звичайні слоти, але мають перевагу: компоненти з таким слотом можуть розкривати дані без запуску подій.

Можемо зробити так:

<toggle>
  <div slot-scope="{ on }">
    {{ on ? 'On' : 'Off' }}
  </div>
</toggle>

У div ми бачимо атрибут slot-scope, що деструктурує об'єкт, отриманий з компонента Toggle.

Повернемося до render() і вносимо зміни:

render() {
  return this.$scopedSlots.default({})
}

На цей раз ми викликаємо default для $scopedSlots як метод (scoped slots — методи, що приймають аргументи). У нашому прикладі метод називається default, оскільки ми не дали назву нашому слоту і він єдиний у проекті. Ми передамо у scoped slot аргумент, який отримаємо з компонента. Для прикладу, отримаємо поточний стан on та хелпер, щоб працювати зі станом.

render() {
  return this.$scopedSlots.default({
    on: this.currentState,
    setOn: this.setOn,
    setOff: this.setOff,
    toggle: this.toggle,
  })
}

Використання Toggle компонента

Подивимось на результат у CodePen:

Наша розмітка у дії:

<div id="app">
  <toggle>
    <div slot-scope="{ on, setOn, setOff }" class="container">
      <button @click="click(setOn)" class="button">Blue pill</button>
      <button @click="click(setOff)" class="button isRed">Red pill</button>
      <div v-if="buttonPressed" class="message">
        <span v-if="on">It's all a dream, go back to sleep.</span>
        <span v-else>I don't know how far the rabbit hole goes, I'm not a rabbit, neither do I measure holes.</span>
      </div>
    </div>
  </toggle>
</div>
  1. Спочатку ми деструктуруємо стан та хелпери із scoped slot.
  2. Далі, у межах scoped slot, створюємо дві кнопки для зміни поточного стану.
  3. Метод click потрібен, щоб переконатися, що кнопка була дійсно натиснута, перш ніж відобразити результат. Поглянемо на його код:
new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

Ми досі можемо передавати props та запускати події з компонента Toggle. Тобто зі scoped slot все як і раніше.

new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

Ми розглянули базовий приклад, але усю міць такої фічі можна відчути при створенні компонентів, на зразок datepicker або віджет автозаповнення. Їх можна використовувати повторно в інших проектах, і при цьому, не турбуватися про надокучливі стилі.

Наостанок, зі scoped slot ми можемо розкрити атрибути, необхідні для доступності, та не турбуватися про те, що інші компоненти також стануть доступними.

Висновки

  • Функція компонентів render() неймовірно потужна.
  • Створюйте компоненти Vue для швидшого виконання застосунків.
  • Властивості el, template або навіть однофайлові компоненти компілюються у render().
  • Намагайтесь створювати менші компоненти для повторного використання коду.
  • Ваш код не обов'язково повинен відповідати принципам S.O.L.I.D, але майте їх на увазі.
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація