Christopher Bloom
Frontend developer, lover of design systems, CSS architecture, and all things javascript.
<script lang="ts">
enum Awesome {
VueJS = ,
TypeScript = ,
}
const title = `${Awesome.VueJS} ❤️ ${Awesome.TypeScript}!`;
</script>
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;
};
Uncaught TypeError: Cannot read property 'disposition' of undefined
If Seamus isn't found, then
iframe: vanilla vue
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?
"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".
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()?)
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);
}
}
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
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
iframe: class-based vue
// 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.
// 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;
// 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?
// 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.
Just use vue-cli to generate a project preconfigured for TypeScript
$ vue create my-ts-project
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>
True type-checking within <script>
iframe: ts class-based 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:
By Christopher Bloom
A quick crash course in TypeScript followed by an introduction to using TypeScript within Vue. Video: https://www.youtube.com/watch?v=5-P6Uk5m7Z8
Frontend developer, lover of design systems, CSS architecture, and all things javascript.