Superfast Introduction to Vue 3

Praveen Puglia

@praveenpuglia

praveenpuglia.com

@vuehyd

hyd.vue.community

The Struggle

Reactivity

Reactivity

let internalValue = data.name;
const dep = new Dep();

Object.defineProperty(data, 'name', {
  get() {
    // name is accessed.
    dep.depend();
    return internalValue;
  },

  set(newVal) {
    // name is updated.
    internalValue = newVal;
    dep.notify();
  }
});
export default {
  data() {
    name: "Vue Hyderabad"
  },
  computed: {
    message() {
      return `Hello! ${this.name}`;
    }
  }
}

👎 Reactivity caveats

Cannot detect changes to array items or object property value changes

Cannot declare reactive properties later...sort of!

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` is now reactive

vm.b = 2
// `vm.b` is NOT reactive
// Array item update via [index] notation
vm.items[indexOfItem] = newValue

// Add or remove a property in an object
delete vm.user.firstName
vm.user.age = 28

😕 Half baked solutions

Cannot detect changes to array items or object property value changes

Cannot declare reactive properties later...sort of!

// Can't add to root level anymore
// But we can in nested object.
Vue.set(vm.someObject, 'b', 2)
Vue.set(vm.items, indexOfItem, newValue)

Go Immutable

Code organization

const vm = new Vue({
  props: {...},
  data() {...},
  methods: {...},
  computed: {...},
  filters: {...},
  watch: {...},
  created() {...},
  mounted() {...},
  ...,
  ...
})

Code 😳r...MESS

const vm = new Vue({
  props: {

  },
  data() {

  },
  methods: {

  },
  computed: {

  },
  filters: {

  },
  watch: {

  },
  created() {

  },
  mounted() {

  }
})

TypeScript

  • Large projects and teams
     
  • Make the most out of GraphQL Stack
     
  • Better tooling, IDE support
     
  • All the benefits of a Type System.

TypeScript IN VUE

  • Vue was never built using TypeScript or for it.
     
  • A lot of magic happens on `this`, the Vue instance.
     
  • Large objects aren't really that type friendly, such as the Options API
     
  • Makes it hard to work with Plugins, Third Party components / tools.

👎 Half baked solutions

import { Vue, Component, Prop } from "vue-property-decorator";

@Component
export default class AwesomeComponent extends Vue {
  @Prop({
    type: Boolean
  })
  isStupid!: boolean;

  dataProperty = 'This is reactive';
  anotherDataProperty = 'This is reactive too';

  get computedValue() {...}
  
  created() {...}
}
The Relief

It's Not GOING To BE LIKE ANGULAR

New apis are purely opt-in

REACTIVITY 

USING PROXIES

const target = {};

const targetProxy = new Proxy(target, {
  get: function(theTarget, prop) {
    return prop === 'age' ? 
      `Your age is ${theTarget[prop]} years` 
      : theTarget[prop];
  }
});

target.age = 20

target.age // 20
targetProxy.age // Your age is 20 years
  • Double the speed, immediately
     
  • Half the memory footprint
     
  • No need of Vue.set or patching Array.prototype.push / pop
     
  • You get the benefits for free

REACTIVITY 

USING PROXIES

vueschool.io

API

COMPOSITION

  • All the time people had spent learning Vue had been wasted given everything was about to change.
     
  • The Vue Core team had suddenly implemented a huge breaking change without any consultation.
     
  • Vue is turning into React!
     
  • No, Vue is turning into AngularJS/Angular!
     
  • All HTML now needs to be written as a giant string!
  • Completely Opt-in
     
  • You don't need to rewrite your entire codebase
     
  • The entire changes went through an RFC process

API

COMPOSITION

import { reactive } from 'vue'

const state = reactive({
  count: 0
})
import { reactive, watch } from 'vue'

const state = reactive({
  count: 0
})

watch(() => {
  document.body.innerHTML = `count is ${state.count}`
})
new Vue({
  data() {
    return {
      count: 0
    }
  }
})
import { reactive, watch } from 'vue'

const state = reactive({
  count: 0
})

function increment() {
  state.count++
}

const renderContext = {
  state,
  increment
}

watch(() => {
  // hypothetical internal code, NOT actual API
  renderTemplate(
    `<button @click="increment">{{ state.count }}</button>`,
    renderContext
  )
})

API

COMPOSITION

import { reactive, computed } from 'vue'

const state = reactive({
  count: 0
})

const double = computed(() => state.count * 2)

// Access computed
console.log(double.value)
<!-- Automatic Ref Unwrapping -->
{{ double }}

API

COMPOSITION

<template>
  <button @click="increment">
    Count is: {{ state.count }}, 
    double is: {{ state.double }}
  </button>
</template>
<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

API

COMPOSITION

<script>
import { reactive, computed } from 'vue';
import { useCounter } from '@/utils';

export default {
  setup() {
    const {state, increment} = useCounter();

    return {
      state,
      increment
    }
  }
}
</script>

WHY DO ALL THIS?

API

COMPOSITION

Extracting logic and much better composition & reuse.

useSearch()
useSort()
usePagination()
useIntersectionObserver()
useLocalStorage()

...

vue-use-web

API

COMPOSITION

  • Be able to use Vue's reactivity system without a Vue instance
     
  • Much wider application area
     
  • Tree-shaken internals for smaller builds
     
  • Much improved TS support because of internals being mere functions
     
  • Plugins without polluting global Vue object.

API

COMPOSITION

VDom rewrite

<div>
  <p>This is static</p>
  <p>This is static as well</p>
  <p>This is {{dynamicVariable}}</p>
  <p>This is static after dynamic</p>
</div>

Before

Re-render everything, even the static parts.

After

Only re-renders the paragraph at line 4. Static parts get hoisted and never trigger changes

VDom rewrite

Monomorphic VNode Creation

function add(a: any, b: any) {
  return a + b;
}

add(1, 2) // 3
add('a', 'b') // ab
function add(a: number, b: number) {
  return a + b;
}
function concat(a: string, b: string) {
  return a + b;
}

add(1, 2) // 3
concat('a', 'b') // ab

Multiple root nodes

<template>
  <li v-for="item of items" :key="item.id">
    <icon :name="item.icon"></icon>{{item.title}}
  </li>
</template>
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

Vue 2

Vue 3

  • v-if
  • v-for
  • multiple nodes
  • works for any component

SIMPLER custom inputs

<BaseInput
  placeholder="John Doe"
  label="First Name"
  v-model="firstName"
  @input="handleFirstName"
/>
<template>
  <label>
    {{ label }}
    <input
      v-bind="$attrs"
      v-on="$listeners"
      @input="$emit('input', $event.target.value)"
    />
  </label>
</template>
<script>
export default {
  props: ["label"],
  inheritAttrs: false
};
</script>

SIMPLER custom inputs

<BaseInput
  placeholder="John Doe"
  label="First Name"
  v-model="firstName"
  @input="handleFirstName"
/>
<template>
  <label>
    {{ label }}
    <input
      v-bind="$attrs" />
  </label>
</template>
<script>
export default {
  props: ["label"]
};
</script>

Everything is $attrs

  • Events : v-on:input => on-input
     
  • v-model : value / on-update:value
     
  • Multiple v-model : v-model:startDate, v-model:endDate
<InviteeForm
  v-model:name="inviteeName"
  v-model:email="inviteeEmail"
/>
<DatePicker
  v-model:startDate="startDate"
  v-model:endDate="endDate"
/>

And there's More

  • In Built Portals
     
  • All slots are scoped slots & compiled as functions
     
  • New custom directives API
     
  • Suspense
     
  • Improved native support
     
  • Thorough migration guide

READ ON

Praveen Puglia

@praveenpuglia

praveenpuglia.com

@vuehyd

hyd.vue.community

Made with Slides.com