Save 37% off PRO during our Black Friday Sale! »

Enjoy The Vue

Enjoy The Vue

My Presentation at ITKonekt in Belgrade 2019 as an introduction to Vue with a few deep dives and tricks, to see what can be done with it in real projects.

9b3ce79a4e2f5656234300be6c321f88?s=128

Roman Kuba

March 21, 2019
Tweet

Transcript

  1. Enjoy the Vue

  2. Why is it progressive?

  3. Mastery Necessary 0 25 50 75 100 Progress First Results

    Use Components Use Webpack Vue Ecosystem Mastery
  4. Mastery Necessary 0 25 50 75 100 Progress First Results

    Use Components Use Webpack Vue Ecosystem Mastery
  5. Roman Kuba @codebryo @codeship

  6. Why am I talking about Vue?

  7. Using Vue in production +3y started with 0.11

  8. Why did you choose Vue?

  9. 100K+ DOM Nodes Angular 1 Crashes

  10. Time constraints, limited knowledge and resources!

  11. Narrative Coherence

  12. Framework State Routing

  13. Framework State Routing axios Modelist Services Plugins Components Rxjs

  14. Where’s the code?

  15. <body> <div id="app"> <h1>{{ message }}!</h1> !</div> <script src="https:!//unpkg.com/vue">!</script> <script>

    new Vue({ el: '#app', data() { return { message: 'Hello World' } } }) !</script> !</body>
  16. Remember what made jQuery great? ♥

  17. <button data-action="greet">Say Hello!</button> <script> const actions = { greet() {

    console.log('Hello Belgrade') } } $('[data-action]').on('click', (e) !=> { e.preventDefault() const method = $(this).data('action') !// greet actions[method]() }) !</script>
  18. <button v-on:click="greet">Say Hello!</button> <script> new Vue({ methods: { greet() {

    console.log('Hello Belgrade') } } }) !</script>
  19. <button @click="greet">Say Hello!</button> <script> new Vue({ methods: { greet() {

    console.log('Hello Belgrade') } } }) !</script>
  20. <a href="/some" @click.prevent="greet">Say Hello!</a> <script> new Vue({ methods: { greet()

    { console.log('Hello Belgrade') } } }) !</script>
  21. List Rendering

  22. <ul> <li v-for="user in users">{{ user }}!</li> !</ul> <script> const

    users = ['Jane', 'Simone', 'Sarah'] new Vue({ data() { return users } }) !</script>
  23. <ul> <user v-for="user in users">{{ user }}!</user> !</ul> <script> const

    users = ['Jane', 'Simone', 'Sarah'] Vue.component('user', { template: '<li><slot !/>!</li>' }) new Vue({ data() { return users }, }) !</script>
  24. <ul> <user v-for="user in users">{{ user }}!</user> !</ul> <script> const

    users = ['Jane', 'Simone', 'Sarah'] Vue.component('user', { template: '<li><slot !/>!</li>' }) new Vue({ data() { return users }, }) !</script> Slots?
  25. Properties

  26. Way to pass values down to components.

  27. <script> Vue.component('Passenger', { props: ['name', 'id'], mounted() { console.log(this.name, this.id)

    } }) !</script>
  28. <script> Vue.component('Passenger', { props: { name: String, id: String },

    mounted() { console.log(this.name, this.id) } }) !</script>
  29. <Passenger name="Bruce Wayne" v-bind:id="randomID" !/> <script> Vue.component('Passenger', { props: {

    name: String, id: String }, mounted() { console.log(this.name, this.id) } }) !</script>
  30. <Passenger name="Bruce Wayne" v-bind:id="randomID" !/> <script> Vue.component('Passenger', { props: {

    name: String, id: String }, mounted() { console.log(this.name, this.id) } }) !</script> <script> new Vue({ data() { return { randomID: uuid() } } }) !</script>
  31. <Passenger name="Bruce Wayne" v-bind:id="randomID" !/> <script> Vue.component('Passenger', { props: {

    name: String, id: String }, mounted() { console.log(this.name, this.id) } }) !</script>
  32. <Passenger name="Bruce Wayne" :id="randomID" !/> <script> Vue.component('Passenger', { props: {

    name: String, id: String }, mounted() { console.log(this.name, this.id) } }) !</script>
  33. <script> Vue.component('Modal', { props: { id: { type: [String, Number],

    default: () !=> uuid() }, action: Function } }) !</script>
  34. Reactivity for free

  35. const state = { count: 1 } const vm =

    new Vue({ data() { return state } })
  36. const state = { count: 1 } const vm =

    new Vue({ data() { return state } }) vm.count !// 1
  37. const state = { count: 1 } const vm =

    new Vue({ data() { return state } }) vm.count !// 1 state.count!++
  38. const state = { count: 1 } const vm =

    new Vue({ data() { return state } }) vm.count !// 1 state.count!++ vm.count !// 2
  39. Object.defineProperty

  40. const state = Vue.observable({ count: 0 }) Vue 2.6

  41. Computed Properties FTW

  42. Function returning data that only reevaluates if a dependency changes.

  43. new Vue({ data() { return { numbers: [1, 2, 3,

    4] }; }, computed: { evens() { return this.numbers.filter(n !=> n % 2 !== 0); }, } });
  44. new Vue({ data() { return { numbers: [1, 2, 3,

    4] }; }, computed: { evens() { return this.numbers.filter(n !=> n % 2 !== 0); }, now() { return Date.now(); } } });
  45. new Vue({ data() { return { counter: 0 } },

    created() { setInterval(() !=> { this.counter!++ }, 1000) } })
  46. new Vue({ data() { return { counter: 0 } },

    created() { setInterval(() !=> { this.counter!++ }, 1000) } }) Lifecycle Method
  47. new Vue({ data() { return { counter: 0 } },

    computed: { secondsPassed() { const msg = `${this.counter} seconds have passed` console.log(msg) return msg } }, created() { setInterval(() !=> { this.counter!++ }, 1000) } })
  48. Mixins

  49. const userMixin = { data() { return { !// return

    User from an API user: Service.getCurrentUser() }; }, components: { UserStatus }, updated() { this.user.storeLatestUpdate(Date.now()); } }; new Vue({ mixins: [userMixin] });
  50. const userMixin = { data() { return { !// return

    User from an API user: Service.getCurrentUser() }; }, components: { UserStatus }, updated() { this.user.storeLatestUpdate(Date.now()); } }; Vue.mixin(userMixin);
  51. Plugins

  52. import userMixin from '@/mixins' const User = { install(Vue, options)

    { !// Global functions Vue.logout = function() {!!...} Vue.mixin(userMixin) Vue.$prototype.$logout = Vue.logout } }
  53. import userMixin from '@/mixins' const User = { install(Vue, options)

    { !// Global functions Vue.logout = function() {!!...} Vue.mixin(userMixin) Vue.$prototype.$logout = Vue.logout } } !// Install a Plugin Vue.use(User)
  54. Vue.use(Vuex)

  55. Let’s take a breath…

  56. None
  57. Vue SFC

  58. <template> <div> <h1>{{ message }}!</h1> !</div> !</template>

  59. <template> <div> <h1>{{ message }}!</h1> !</div> !</template> <script> export default

    { data() { return { message: "Hello Belgrade” }; } }; !</script>
  60. <template> <div> <h1>{{ message }}!</h1> !</div> !</template> <script> export default

    { data() { return { message: "Hello Belgrade” }; } }; !</script> <style scoped> h1 { color: #bada55; } !</style>
  61. <template> <div> <List :data="collection" !/> !</div> !</template> <script> import List

    from "@/components"; export default { components: { List }, computed: { collection() { return Service.getImportantData(); } } }; !</script>
  62. THAT EASY?

  63. <body> <div id="app"> <h1>{{ message }}!</h1> !</div> <script src="https:!//unpkg.com/vue">!</script> <script>

    new Vue({ el: '#app', data() { return { message: 'Hello World' } } }) !</script> !</body>
  64. <body> <header> !!!<!-- Navigation Header from Backend !!--> !</header> <div

    id="app"> !!!<!-- Content From Server !!--> <UserList :users="!#{@users.to_json}"> !!!<!-- Vue Land !!--> !</UserList> !</div> <script src="foo/app-1234.min.js">!</script> !</body>
  65. <body> <header> !!!<!-- Navigation Header from Backend !!--> !</header> <div

    id="app"> !!!<!-- Content From Server !!--> <UserList :users="!#{@users.to_json}"> !!!<!-- Vue Land !!--> !</UserList> !</div> <script src="foo/app-1234.min.js">!</script> !</body>
  66. <body> <header> !!!<!-- Navigation Header from Backend !!--> !</header> <div

    id="app"> !!!<!-- Content From Server !!--> <UserList :users="!#{@users.to_json}"> !!!<!-- Vue Land !!--> !</UserList> !</div> <script src="foo/app-1234.min.js">!</script> !</body>
  67. APP JS PACK N V PACK PACK O ther Talk

  68. RENDER FUNCTIONS

  69. render(createElement) { return createElement('span', 'some text') }

  70. render(h) { return h('span', 'h for Hyperscript') } TIL

  71. <div> <header> <h1>I'm a template!!</h1> !</header> <p v-if="message">{{ message }}!</p>

    <p v-else>No message.!</p> !</div>
  72. <div> <header> <h1>I'm a template!!</h1> !</header> <p v-if="message">{{ message }}!</p>

    <p v-else>No message.!</p> !</div> render(h) { const message = this.message return h('div', [ h('header', [ h('h1', "I'm a template!") ]), message ? h('p', message) : h('p', 'No message.') ]) }
  73. <div> <header> <h1>I'm a template!!</h1> !</header> <p v-if="message">{{ message }}!</p>

    <p v-else>No message.!</p> !</div> function anonymous() { with (this) { return _c('div', [_m(0), (message) ? _c('p', [_v(_s(message))]) : _c('p', [_v("No message.")])]) } } _m(0): function anonymous() { with (this) { return _c('header', [_c('h1', [_v("I'm a template!")])]) } }
  74. <heading>My h1 Title!</heading> <heading level="2">My h2 Title!</heading>

  75. <heading>My h1 Title!</heading> <heading level="2">My h2 Title!</heading> Vue.component('heading', { props:

    { level: { type: Number, default: 1 } }, render(h) { const tag = `h${this.level}` return h(tag, this.$slots.default) } })
  76. <heading>My h1 Title!</heading> <heading level="2">My h2 Title!</heading> Vue.component('heading', { props:

    { level: { type: Number, default: 1 } }, render(h) { const tag = `h${this.level}` return h(tag, this.$slots.default) } })
  77. Slots

  78. Slots are placeholders with super powers

  79. <template> <div class="modal"> <header> <slot name="header">!</slot> !</header> <slot>!</slot> <footer> <slot

    name="footer">!</slot> !</footer> !</div> !</template> <Modal> <template v-slot:header> <h1>Signup Form!</h1> !</template> <p>This is the modal content!!</p> <template v-slot:footer> <button>Sign me up!</button> !</template> !</Modal>
  80. this.$scopedSlots { default: function (slotProps) {}, header: function (slotProps) {}}

  81. Renderless Components

  82. <script> export default { name: "Randomizer", data() { return {

    number: Date.now() }; }, render() { return this.$scopedSlots.default({ number: this.number }); } }; !</script>
  83. <script> export default { name: "Randomizer", data() { return {

    number: Date.now() }; }, render() { return this.$scopedSlots.default({ number: this.number }); } }; !</script> <Randomizer v-slot:default="{ number }"> <span>{{ number }}!</span> !</Randomizer>
  84. <script> export default { name: "Randomizer", data() { return {

    number: Date.now() }; }, render() { return this.$scopedSlots.default({ number: this.number }); } }; !</script> <Randomizer #default=“{ number }"> <span>{{ number }}!</span> !</Randomizer>
  85. None
  86. <script> import axios from 'axios' const CODESHIP_STATUSPAGE_URL = ‘…’ export

    default { data () { return { status: 'loading', description: '' } }, render () { return this.$scopedSlots.default({ description: this.description, statusClass: this.statusClass }) }, computed: { statusClass () { return `header-item-status-${this.status}` } }, methods: { success (response) { this.status = response.data.status.indicator this.description = response.data.status.description } }, created () { axios.get(CODESHIP_STATUSPAGE_URL).then(this.success) } }
  87. Page-Status li.header-item.header-item-primary[slot-scope="{ description, statusClass }" :class="statusClass"] a.header-link.header-link-support[href="#" data-dropdown-trigger] span Support

    span.header-arrow-btn.hidden[class="md:block"] !== icon :arrow_down, prefix: :header ul.header-item-dropdown-list / Navigation links !!... !== header_dropdown_item status_url, target: '_blank', class: 'header-item-dropdown-link-status' span.header-item-dropdown-link-title Codeship Status span.header-item-dropdown-link-description {{ description }}
  88. Wrapping a Library

  89. Vue replaced soooo many libraries for me

  90. const pubsub = new Vue() pubsub.$on('alert:show', function (msg) { Alerts.push(msg)

    }) pubsub.$emit('alert:show', 'You are good to go!')
  91. None
  92. =

  93. Don’t want to load manually

  94. new SimpleBar(document.getElementById('myElement'), { autoHide: false });

  95. <template> <div ref="wrapper"> <slot!/> !</div> !</template>

  96. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { mounted() { let link = document.createElement("link"); link.href = csslink; link.type = "text/css"; link.rel = "stylesheet"; document.head.appendChild(link); let simplebarScript = document.createElement("script"); simplebarScript.async = true; simplebarScript.setAttribute("src", jslink); document.head.appendChild(simplebarScript); const waitForSimplebar = () !=> { if (window["SimpleBar"]) { this.setup(); } else { setTimeout(() !=> waitForSimplebar(), 100); } }; waitForSimplebar(); } }; !</script>
  97. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { mounted() { let link = document.createElement("link"); link.href = csslink; link.type = "text/css"; link.rel = "stylesheet"; document.head.appendChild(link); let simplebarScript = document.createElement("script"); simplebarScript.async = true; simplebarScript.setAttribute("src", jslink); document.head.appendChild(simplebarScript); const waitForSimplebar = () !=> { if (window["SimpleBar"]) { this.setup(); } else { setTimeout(() !=> waitForSimplebar(), 100); } }; waitForSimplebar(); } }; !</script>
  98. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { mounted() { let link = document.createElement("link"); link.href = csslink; link.type = "text/css"; link.rel = "stylesheet"; document.head.appendChild(link); let simplebarScript = document.createElement("script"); simplebarScript.async = true; simplebarScript.setAttribute("src", jslink); document.head.appendChild(simplebarScript); const waitForSimplebar = () !=> { if (window["SimpleBar"]) { this.setup(); } else { setTimeout(() !=> waitForSimplebar(), 100); } }; waitForSimplebar(); } }; !</script>
  99. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { mounted() { let link = document.createElement("link"); link.href = csslink; link.type = "text/css"; link.rel = "stylesheet"; document.head.appendChild(link); let simplebarScript = document.createElement("script"); simplebarScript.async = true; simplebarScript.setAttribute("src", jslink); document.head.appendChild(simplebarScript); const waitForSimplebar = () !=> { if (window["SimpleBar"]) { this.setup(); } else { setTimeout(() !=> waitForSimplebar(), 100); } }; waitForSimplebar(); } }; !</script>
  100. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { data() { return { instance: undefined, config: { autohide: false } }; }, mounted() { … } }; !</script>
  101. <script> const csslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.css"; const jslink = "https:!//cdnjs.cloudflare.com/ajax/libs/simplebar/3.1.5/simplebar.min.js"; export

    default { data() { return { instance: undefined, config: { autohide: false } }; }, methods: { setup() { this.instance = new window.SimpleBar(this.$refs.wrapper, this.config); } }, mounted() { … } }; !</script>
  102. Clean State mgmt

  103. It’s just as easy…

  104. import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export

    default Vuex.Store({ state: { count: 1 }, mutations: { add(state) { state.count!++; } }, actions: { add({ commit }) { commit("add"); } }, getters: { counter: s !=> s.count } });
  105. import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export

    default Vuex.Store({ state: { count: 1 }, mutations: { add(state) { state.count!++; } }, actions: { add({ commit }) { commit("add"); } }, getters: { counter: s !=> s.count } }); import store from "./store"; import { mapGetters, mapActions } from "vuex"; new Vue({ store, computed: { !!...mapGetters(["counter"]) }, methods: { !!...mapActions(["add"]) } });
  106. None
  107. What about testing?

  108. You need to know how to test your code!

  109. None
  110. Vue Eco System

  111. So much to praise!

  112. Vue is incredibly mature!

  113. None
  114. Vue 3

  115. faster & lighter %

  116. Reactivity Changes

  117. Typescript

  118. https://medium.com/vue-mastery/evan-you-previews-vue-js-3-0-ab063dec3547

  119. Painless Upgrades

  120. Would you choose Vue again today?

  121. !!!<!-- REACT !!--> <div> <h1>Hello!!</h1> {unreadMessages.length > 0 !&& <h2>

    You have {unreadMessages.length} unread messages. !</h2> } !</div> !!!<!-- Vue !!--> <div> <h1>Hello!!</h1> <h2 v-if="unreadMessages.length > 0"> You have {{ unreadMessages.length }} unread messages. !</h2> !</div>
  122. None
  123. None
  124. https://vueschool.io/codebryo

  125. Thank You! @codebryo