Во vue.js существуют очень интересное понятие - так называемые headless компоненты. Ещё их называют renderless-компоненты. Цель этих компонентов - максимальная гибкость посредством выделения логики рендеринга. Это особенно полезно, когда компонент содержит большую бизнес-логику.
Когда мы работаем с компонентами во Vue.js, то чаще всего мы совершаем различные ajax-запросы с использованием axios. И зачастую axios-запросы - это копипаст кусков кода из компонента в компонент. Использовать миксины в этом случае часто плохая затея, т.к. иногда нам нужно менять параметры запроса, адрес и прочее. Поэтому здесь нам на помочь призодят headless-компоненты.
Давайте вспомним, как происходит отправка запросов с использованием axios:
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then(repsponse => {
console.log(response.data);
})
.catch(error => {
console.error(error);
})
Для данного публичного API ответ будет следующим:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Чтобы получить заголовок, нам потребуется обратиться к объекту по следующему пути: response.data.title.
Вооружившись этими знаниями, мы можем написать специальный компонент request, задача которого будет состоять в отправке запроса, обработке данных. Использовать компонент мы можем следующим образом:
<request url="https://jsonplaceholder.typicode.com/posts/1">
<template v-slot:default="{ loading, data }">
<div v-if="loading">Loading...</div>
<div v-else>
<h1>
{{data.title}}
</h1>
<p>
{{data.body}}
</p>
</div>
</template>
</request>
В нашем примере компонент request использует входной параметр url, по которому компонент и осуществляет отправку запроса.
В ответ от данного компонента мы получаем на только обработанные данные в data, но и параметр loading, по которому мы определяем, показывать ли нам индикатор загрузки или нет. Только после того, как закончена загрузка, мы показываем данные ответа.
Vue.component("Request", {
render() {
return this.$scopedSlots.default({
loading: this.loading,
data: this.response && this.response.data
})[0];
},
props: {
url: String
},
data() {
return {
loading: true,
response: null
}
},
created() {
axios.get(this.url)
.then(response => {
this.response = response;
this.loading = false;
})
}
});
В этом максимально простом примере в качестве входного параметра мы принимаем только url и управляем данными ответа, состоянием загрузки. Вся процедура отправки ajax-запроса происходит в хуке created. В его задачу входит не только отправка запроса, но и его обработка, сохранение ответа, смена флага loading. После обработки данных, вся информация отправляется через scoped slots родительскому компоненту.
Есть одна очень важная деталь - у нашего компонента нет внутреннего состояния. Как и в обычном компоненте мы можем инициировать события, а в родительском компоненте подписываться на них.
И согласитесь, очень удобно, что мы теперь этот большой компонент со всей бизнес-логикой обработки ajax-запросов можем использовать неограниченное количество раз с разными типами шаблонов. Ведь заметьте, что в компоненте вообще отсутствует отрисовка шаблона. Она осуществляется в родительском компоненте, а здесь мы только передаём наверх данные