Vue.js Renderless Components It’s like advanced internet Legos! ToThePoint STS / Lars van Herk / 3 june, 2019
1. Build a standard, feature-rich base component 2. Separate the view from the logic 3. Slap it into a publishable library WHAT’CHA GONNA DO? INTRODUCTION We’re gonna do some Vue.
WHY SO COMPLICATED? INTRODUCTION Every app works differently - “Our date pickers don’t look like that!” - Tweaking a prebuilt component’s layout is hard or impossible - Either accept it, or rebuild it
WHAT DO WE NEED FOR THIS? INTRODUCTION Slots. Scoped Slots. https://vuejs.org/v2/guide/components-slots.html
<template> <section class="my-alert"> <slot name="title"> <h1>This will be the default title!</h1> </slot> <slot v-bind:person="person"> Hello, {{ person.name }}! </slot> </section> </template> <template> <div id="app"> <my-alert :person="outerPerson"> <template v-slot:title> <h1>I'm going to replace the default!</h1> </template> </my-alert> </div> </template> <template> <section class="my-alert"> <h1>I'm going to replace the default!</h1> Hello, Bobby! </section> </template> MyAlert.vue App.vue Rendered Output INTRODUCTION
RENDERING ALL THE THINGS! INTRODUCTION - Template part of SFC isn’t required! - Can be replaced with a render function - Also known as the ‘h(…)’ function - ‘render()’: available in ‘script’ section
THE STARTING BLOCKS BUILDING THE COMPONENT 1. Build the basic component 2. Split logic from rendering 3. Use our old view as a default BRACE YOURSELVES, CODE AHOY!
BUILDING THE COMPONENT <script> export default { data: () => ({ user: { firstName: '', lastName: '' } }), watch: { user: { handler (val) { this.$emit('input:user', val); }, deep: true } }, methods: { insertBob () { this.user = { firstName: 'Bob', lastName: 'Marley' }; } } }; </script> <template> <section class="my-user"> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" v-model="user.firstName"> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" v-model="user.lastName"> </div> <button @click="insertBob()">Insert Bob</button> </section> </template>
BUILDING THE COMPONENT <script> export default { data: () => ({ scopeData: { user: { firstName: '', lastName: '' } } }), watch: { 'scopeData.user': { … } }, methods: { updateUser (data) { … }, insertBob () { … } }, render () { return this.$scopedSlots.default({ // TODO }); } }; </script> <script> export default { data: () => ({ user: { firstName: '', lastName: '' } }), watch: { user: { handler (val) { this.$emit('input:user', val); }, deep: true } }, methods: { insertBob () { this.user = { firstName: 'Bob', lastName: 'Marley' }; } } }; </script>
BUILDING THE COMPONENT <script> export default { data: () => ({ scopeData: { user: { firstName: '', lastName: '' } } }), … render () { return this.$scopedSlots.default({ // DATA ...this.scopeData, // ACTIONS updateUser: this.updateUser, insertBob: this.insertBob }); } }; </script>
WE’RE ALMOST THERE 🎉 BUILDING THE COMPONENT 1. Build the basic component 2. Split logic from rendering 3. Use our old view as a default
BUILDING THE COMPONENT <template> <section class="my-user"> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" v-model="user.firstName"> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" v-model="user.lastName"> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> <template> <section class="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template>
BUILDING THE COMPONENT <template> <section class="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> … </section> </template> </my-user-rl>
BUILDING THE COMPONENT <template> <section class="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" />
RENDERLESS RECAP BUILDING THE COMPONENT - We’ve got a logic-only component - Reusable for other ‘views’ - Added a default sample layout - Based on the same logic layer 📦 Wrap it up and ship it! 📦
WEBPACK TIME, YAY (?) SHIPPING COMPONENTS
SHIPPING MADE EASY SHIPPING COMPONENTS 1. SETUP PLUGIN 2. BUILD THE LIBRARY 🎉 ALL DONE 🎉
HEY, WHERE’S THE LINKS?! ROUNDING UP GitHub Project: github.com/larsvanherk/vue-renderless-demo Adam Wathan’s original post: adamwathan.me/renderless-components-in-vuejs/ Get in touch: - @LarsVHerk & @ToThePointIT
🎉 Thank you! 🎉 ToThePoint STS / Lars van Herk / 3 june, 2019

Using Renderless Components in Vue.js during your software development.

  • 1.
    Vue.js Renderless Components It’slike advanced internet Legos! ToThePoint STS / Lars van Herk / 3 june, 2019
  • 2.
    1. Build astandard, feature-rich base component 2. Separate the view from the logic 3. Slap it into a publishable library WHAT’CHA GONNA DO? INTRODUCTION We’re gonna do some Vue.
  • 3.
    WHY SO COMPLICATED? INTRODUCTION Everyapp works differently - “Our date pickers don’t look like that!” - Tweaking a prebuilt component’s layout is hard or impossible - Either accept it, or rebuild it
  • 4.
    WHAT DO WENEED FOR THIS? INTRODUCTION Slots. Scoped Slots. https://vuejs.org/v2/guide/components-slots.html
  • 5.
    <template> <section class="my-alert"> <slot name="title"> <h1>Thiswill be the default title!</h1> </slot> <slot v-bind:person="person"> Hello, {{ person.name }}! </slot> </section> </template> <template> <div id="app"> <my-alert :person="outerPerson"> <template v-slot:title> <h1>I'm going to replace the default!</h1> </template> </my-alert> </div> </template> <template> <section class="my-alert"> <h1>I'm going to replace the default!</h1> Hello, Bobby! </section> </template> MyAlert.vue App.vue Rendered Output INTRODUCTION
  • 6.
    RENDERING ALL THETHINGS! INTRODUCTION - Template part of SFC isn’t required! - Can be replaced with a render function - Also known as the ‘h(…)’ function - ‘render()’: available in ‘script’ section
  • 7.
    THE STARTING BLOCKS BUILDINGTHE COMPONENT 1. Build the basic component 2. Split logic from rendering 3. Use our old view as a default BRACE YOURSELVES, CODE AHOY!
  • 8.
    BUILDING THE COMPONENT <script> exportdefault { data: () => ({ user: { firstName: '', lastName: '' } }), watch: { user: { handler (val) { this.$emit('input:user', val); }, deep: true } }, methods: { insertBob () { this.user = { firstName: 'Bob', lastName: 'Marley' }; } } }; </script> <template> <section class="my-user"> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" v-model="user.firstName"> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" v-model="user.lastName"> </div> <button @click="insertBob()">Insert Bob</button> </section> </template>
  • 9.
    BUILDING THE COMPONENT<script> export default { data: () => ({ scopeData: { user: { firstName: '', lastName: '' } } }), watch: { 'scopeData.user': { … } }, methods: { updateUser (data) { … }, insertBob () { … } }, render () { return this.$scopedSlots.default({ // TODO }); } }; </script> <script> export default { data: () => ({ user: { firstName: '', lastName: '' } }), watch: { user: { handler (val) { this.$emit('input:user', val); }, deep: true } }, methods: { insertBob () { this.user = { firstName: 'Bob', lastName: 'Marley' }; } } }; </script>
  • 10.
    BUILDING THE COMPONENT <script> exportdefault { data: () => ({ scopeData: { user: { firstName: '', lastName: '' } } }), … render () { return this.$scopedSlots.default({ // DATA ...this.scopeData, // ACTIONS updateUser: this.updateUser, insertBob: this.insertBob }); } }; </script>
  • 11.
    WE’RE ALMOST THERE🎉 BUILDING THE COMPONENT 1. Build the basic component 2. Split logic from rendering 3. Use our old view as a default
  • 12.
    BUILDING THE COMPONENT <template> <sectionclass="my-user"> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" v-model="user.firstName"> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" v-model="user.lastName"> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> <template> <section class="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template>
  • 13.
    BUILDING THE COMPONENT <template> <sectionclass="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> … </section> </template> </my-user-rl>
  • 14.
    BUILDING THE COMPONENT <template> <sectionclass="my-user"> <my-user-rl @input:user="$emit('input:user', $event)"> <template v-slot:default="{ user, updateUser, insertBob }"> <section> <h1>My User</h1> <div class="my-user__input"> <label for="firstname">First Name</label> <input type="text" id="firstname" :value="user.firstName" @input="updateUser({ firstName: $event.target.value })" /> </div> <div class="my-user__input"> <label for="lastname">Last Name</label> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" /> </div> <button @click="insertBob()">Insert Bob</button> </section> </template> </my-user-rl> </section> </template> <input type="text" id="lastname" :value="user.lastName" @input="updateUser({ lastName: $event.target.value })" />
  • 15.
    RENDERLESS RECAP BUILDING THECOMPONENT - We’ve got a logic-only component - Reusable for other ‘views’ - Added a default sample layout - Based on the same logic layer 📦 Wrap it up and ship it! 📦
  • 16.
    WEBPACK TIME, YAY(?) SHIPPING COMPONENTS
  • 17.
    SHIPPING MADE EASY SHIPPINGCOMPONENTS 1. SETUP PLUGIN 2. BUILD THE LIBRARY 🎉 ALL DONE 🎉
  • 18.
    HEY, WHERE’S THELINKS?! ROUNDING UP GitHub Project: github.com/larsvanherk/vue-renderless-demo Adam Wathan’s original post: adamwathan.me/renderless-components-in-vuejs/ Get in touch: - @LarsVHerk & @ToThePointIT
  • 19.
    🎉 Thank you!🎉 ToThePoint STS / Lars van Herk / 3 june, 2019