Introduction to Vue

Edd Yerburgh

iPlayer Web

Agenda

  • My role
  • What Vue is
  • How Vue works
  • Why it's popular

My Role

Benefits

  • Create popular software
  • Work with great developers from all over the world
  • Recognition (increased opportunities)

Work

  • Maintain @vue/test-utils
  • Maintain vue-jest
  • Review PRs in Vue org projects
  • Discuss future of Vue

How I got started

  • Created a Vue test library in January 2017
  • People used the project (my first successful open source)
  • Contacted the Vue team in June 2017
  • Joined the team in 2017 to create official Vue Test Utils

What?

  • Front-end JavaScript framework
  • Used for writing dynamic web apps
  • Declarative
  • Official ecosystem

Similarities

  • View layer ✅
  • Component-based architecture ✅
  • Virtual DOM ✅

Differences

  • Reactivity
  • GitHub stars 😉

Declarative??

<h1></h1>

<script>
let count = 0

document.querySelector('h1').textContent = count

function incrementCount() {
  count++;
  document.querySelector('h1').textContent = count
}
</script>
<h1>{{count}}</h1>

<script>
// Vue code goes here
</script>

Example

<script src="https://unpkg.com/vue@2.5.9/dist/vue.js"></script>
  
<div id="app">
  <h1>{{count}}</h1>
</div>

<script>
  window.vm = new Vue({
    el: '#app',
    data: () => ({
      count: 0
    })
  })
</script>

Templates

<script src="https://unpkg.com/vue@2.5.9/dist/vue.js"></script>
  
<div id="app" />

<script>
  window.vm = new Vue({
    el: '#app',
    template: '<h1>{{count}}</h1>',
    data: () => ({
      count: 0
    })
  })
</script>

Click handlers

<script>
  window.vm = new Vue({
    el: '#app',
    template: `
        <div>
            <h1>
                {{count}}
            </h1>
            <button @click="count++">Increment</button>
        </div>`,
    data: () => ({
      count: 0
    })
  })
</script>

Components

<script>
  const CounterComponent = {
    template: '<h1>{{count}}</h1>',
    props: ['count']
  }

  new Vue({
    el: '#app',
    template: '<counter-component :count="count" />',
    data: () => ({
      count: 0
    }),
    components: { CounterComponent }
  })
</script>
<script>
  const CounterComponent = {
    template: '<h1>{{count}}</h1>',
    data: () => ({
      count: 0
    })
  }

  new Vue({
    el: '#app',
    template: '<counter-component />',
    components: { CounterComponent }
  })
</script>

Single File Components

<template>
  <h1>{{count}}</h1>
</template>

<script>
  export default {
    data: () => ({
      count: 0
    })
  }
</script>
<template>
  <h1>{{count}}</h1>
</template>

<style scoped>
  p {
    color: red;
  }
</style>

<script>
  export default {
    data: () => ({
      count: 0
    })
  }
</script>
<template lang="pug">
  h1 {{count}}
</template>

<style lang="scss" scoped>
  p {
    color: red;
  }
</style>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'
 
  @Component
  export default class App extends Vue {
    data = 0
  }
</script>
<script>
  // ..
</script>

<test>
  import { shallowMount } from '@vue/test-utils'

  test('renders correctly', () => {
    expect(shallowMount().is('div')).toBeTruthy()
  })
</test>

How?

<template>
  <h1>{{count}}</h1>
</template>

<script>
  export default {
    data: () => ({
      count: 0
    })
  }
</script>
<h1>0</h1>

Compilation

export default {
  data: () => ({
    count: 0
  }),
  render() {
    var _vm = this;
    var _h = _vm.$createElement;
    var _c = _vm._self._c || _h;
    return _c('h1', [_vm._v(_vm.count)])
  }
}
<template>
  <h1>{{count}}</h1>
</template>

<script>
  export default {
    data: () => ({
      count: 0
    })
  }
</script>
export default {
  data: () => ({
    count: 0
  }),
  render() {
    return this.createElement('h1', [
      this.createTextNode(this.count)
    ])
  }
}

Create instance

const vm = new Vue.extend(CounterComponent)

vm.$mount()

The Virtual DOM

h1
0
const VNode = {
  type: 'h1',
  children: [
    {
      type: 'text',
      value: '0',
      children: null
    }
  ]
}

Patch

h1
0

<h1>

0

document.createElement('h1')
document.createTextNode('0')

Mount component

??

Instance created

Render vNode

Patch

this.update()
this.count = 1
// ??
this.update()
h1
1

<h1>

0

textNode.data = '1'

1

Patch

let a = 3;

const state = {
  set a(newValue) {
    a = newValue
    console.log(`set ${a}`)
  }
}

Reactivity

let a = 3;

const state = {
  // ..
  get a() {
    console.log(`get ${a}`)
    return a
  },
}

const component = {
  count: state.a
}

// logs get 3
let subscribers = [];
let activeJob = null;

let a = 3

const state = {
  get a () {
    if (!subscribers.includes(activeJob)) {
      subscribers.push(activeJob)
    }
    return a
  },
  
  set a (newValue) {
    a = newValue
    subscribers.forEach(job => job())
  }
}
function job() {
  activeJob = job
  update()
  activeJob = null
}

job()

state.a = 12

Mount component

Make Reactive

Instance created

Render vNode

Patch

this.update()
<template>
  <div>
    <h1>{{count}}</h1>
    <button @click="count++">
      increment
    </button>
  </div>
</template>

<script>
  export default {
    data: () => ({
      count: 0
    })
  }
</script>

Why?

Users love

  • Reactivity
  • International
  • Easy onboarding
  • No decisions on ecosystem

Official ecosystem

  • Client-side routing
  • State management
  • Testing utils
  • CLI
  • Webpack loader

Adoption

  • Gitlab
  • Bugsnag
  • 9Gag
  • Alibaba
  • Tencent
  • Baidu

Future

  • Vue 3 (Proxies, time slicing, improved performance)
  • New test renderer

Thank you