Christopher Bloom, @illepic

Phase2 Technology

slides.com/illepic/vuejs-typescript

<script lang="ts">

  enum Awesome {

    VueJS =       ,

    TypeScript =       ,
  }


  const title = `${Awesome.VueJS} ❤️ ${Awesome.TypeScript}!`;

</script>

Follow along!

This deck proceeds up and down as well as left to right! Move down before moving right!

 

Embedded CodeSandbox examples are best opened in their own tab!

const getSeamusMood = async () => {
  // Fetch cats
  const response = await fetch("//catapi.com/cats");
  const cats = await response.json();

  // Find Seamus cat
  const seamus = cats && cats.find(cat => cat.name === "Seamus");
  // How grumpy is Seamus?
  return seamus.disposition;
};

Spot the runtime error

Uncaught TypeError: Cannot read property 'disposition' of undefined

If Seamus isn't found, then

Why is that JavaScript "unsafe"?

  1. What is the shape of the server response?
  2. What is the shape of a cat?
  3. What happens when Seamus isn't found?

To bring safety and clarity to our Vue code, we need to establish a few concepts.

What we're going to learn

  1. Vanilla Vue
  2. ES6 class basics
  3. Decorators!
  4. Class-based Vue!
  5. TypeScript!
  6. TypeScript in Class-based Vue, finally!

Cat Feeding Simulator v1

  • Show buttons for each of my cats
  • Clicking a button sets the current cat
  • Feeding the current cat changes their mood (and noises)
  • Reset the cat's mood via button click, simulating 5 minutes passing since the cat ate
  • Computed state for both selected cat and disposition

(v1: Vanilla Vue)

iframe: vanilla vue

Vue is great! But ...

No TypeScript :(

How do we get there?

1a. Decorators

Ever seen code like this out in the wild?

@log()
@immutable()
class Example {
  @time('demo')
  doSomething() {
    //
  }
}

Obviously these Decorator annotations prefixed with "@" provide some kind of code enhancement, but what?

1b. Decorators

"When the smoke clears, decorators are just functions that take in a piece of code, try to make it nicer and extend it (decorate it!), and return that extended piece of code. A higher order function if you will."

function saysMeow(fn) {
  return function(name) {
    const catStatement = `${name} says "meow".`;
    fn(catStatement);
  };
}

function printCat(name) {
  console.log(name);
}

const printCatNameAndSound = saysMeow(printCat);
printCatNameAndSound("Seamus"); // output: Seamus says "meow".

1c. Decorators

So if functions that can wrap another function and modify output, why do we need Decorators?

 

Classes. It's hard or impossible to wrap and enhance classes in JavaScript.

class Cat {
  printCat(name) {
    console.log(name);
  }
}
const cat = new Cat();
cat.printCat("Seamus"); // output: "Seamus"

How do we wrap/enhance Cat.printCat() with a higher order function?! (Like, say, saysMeow()?)

1d. Decorators

Decorating a class method:

function saysMeow(
  target, // The entire class we're decorating
  name, // Name of the method we're decorating
  descriptor // Access to the actual method!
) {
  const fn = descriptor.value; // Hold original function (printCat())
  // Overwrite the original function (printCat())
  descriptor.value = name => {
    // Call original (with current class scope), with a new arg!
    fn.call(target, `${name} says "meow".`);
  };
}

class Cat {
  @saysMeow
  printCat(name) {
    console.log(name);
  }
}

1e. Decorators

Decorating a full class:

// Function that takes a custom argument
function feedCat(fedRecently = false) {
  // Return *actual* class decorator, only receives target
  return function(target) {
    // Cat isn't hungry if fed recently
    target.prototype.isHungry = !fedRecently;
  };
}

@feedCat(true)
class Cat {
  printCat(name) {
    console.log(name);
  }
}

const cat = new Cat();
console.log(cat.isHungry); // false

2. Class-based Vue

export default {
  // Variables scoped to this component
  data () {
    return {
      message: 'Hello'
    }
  },
  // Observed variables
  computed: {
    reversedMessage () {
      return this.message.split('').reverse().join('')
    }
  },
  // Functions available within scope (and template)
  methods: {
    changeMessage () {
      this.message = "Good bye"
    }
  },
  // Child components this component calls
  components: { },
  // Variables passed from parent template
  props: { },

  // Lifecycle hooks
  created () { },
  mounted () { },
  updated () { },
  destroyed () { }
}
import { Component } from 'vue-property-decorator';

@Component
export default class extends Vue {
  // data
  message = 'Hello'
  // computed
  get reverseMessage () {
    return this.message.split('').reverse().join('')
  }
  // method
  changeMessage () {
    this.message = 'Good bye'
  }
  // Lifecycle hooks
  created () { },
  mounted () { },
  updated () { },
  destroyed () { }
}

Object-based

Class-based

Cat Feeding Simulator v2

  • Using native ES6 classes provides member variables, methods, getters, setters
  • Simple Decorator enhances class to provide autocompletion and reserved keywords like lifecycle event names
  • Fewer "Vue-isms" more "ES6-isms"

(v2: Class Vue)

iframe: class-based vue

3a. TypeScript

  • TypeScript is both a language and a command line compiler.
  • TypeScript is a "superset" of JavaScript. All valid JavaScript is valid TypeScript.
  • TypeScript supports "annotations" to help you code.
  • All TypeScript annotations and helpers are stripped out at compile time when TypeScript becomes standard JavaScript

3b. TypeScript

// ES6
const adder = (n1, n2) => n1 + n2;
// TS
const adder = (n1: number, n2: number) => n1 + n2;

Basic type annotations allow the compiler help us avoid errors. 

// TS
const lengther = (n1: number) => n1.length;
Property 'length' does not exist on type 'number'. ts(2339)

This is an error we wouldn't usually find until runtime/production.

3c. TypeScript: Basic Types

// boolean
let isDone: boolean = false;

// number (all numbers are floating point)
let decimal: number = 6;

// string
let color: string = "blue";

// arrays
let list: number[] = [1, 2, 3];

// tuples
let customer: [string, boolean]; // name and active customer
customer = ['Christopher Bloom', true]; // customer[1] is true!

// enums
enum Color {Red, Green, Blue}
let c: Color = Color.Green;

3d. TypeScript: Interfaces

// JavaScript
function catPetability(cat) {
  if(cat.disposition === 'surly') {
    return 'DO NOT pet this cat';
  }
  return 'Pet this cat right meow';
}

Interfaces help describe the shape of data.

What keys are available on cat? What is the "shape" of cat?

3e. TypeScript: Interfaces

// TypeScript
interface Cat {
  // disposition is a "Union" type!
  disposition: 'surly' | 'snuggly' | 'ambivalent';
}
// Annotate argument with Cat type
function catPetability(cat: Cat) {
  if(cat.disposition === 'surely') {
    return 'DO NOT pet this cat';
  }
  return 'Pet this cat right meow';
}

Wouldn't it be better if we could describe our expectations?

TS2367: This condition will always return 'false' since the types 'Disposition' and '"surely"' have no overlap.

Spelling is hard.

4a. TypeScript in Vue

Just use vue-cli to generate a project preconfigured for TypeScript

$ vue create my-ts-project

4b. TypeScript in Vue

Then add lang="ts" to <script> tag

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

import cats from './cats';

@Component
export default class App extends Vue {
  public cats = cats;
}
</script>

4c. TypeScript in Vue

True type-checking within <script>

Cat Feeding Simulator v3

  • Now with interfaces for cats!
  • disposition is now a Union for type-safe grumpiness!
  • TypeScript forced us to guarantee return of an ICat before accessing members of the object!

(v3: TypeScript Class Vue)

iframe: ts class-based vue

Thanks!

Sources

Spare Slides

4c. TypeScript in Vue

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

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA!: number
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC!: string | boolean
}
export default {
  props: {
    propA: {
      type: Number
    },
    propB: {
      default: 'default value'
    },
    propC: {
      type: [String, Boolean]
    },
  }
}

Prop decorators!

Equivalent to:

VueJS & TypeScript

By Christopher Bloom

VueJS & TypeScript

A quick crash course in TypeScript followed by an introduction to using TypeScript within Vue. Video: https://www.youtube.com/watch?v=5-P6Uk5m7Z8

  • 2,869