Categories
Vue

Developing Vue Apps with Class-Based Components — Type Annotations

If we prefer to use classes, we can use the class-based components API that comes with Vue.

In this article, we’ll look at how to developing Vue apps with class-based components.

TypeScript Autocomplete for External Hooks

We can add autocomplete for external hooks within our class-based components written in TypeScript.

For instance, we can write:

vue-router-hook-types.ts

import Vue from "vue";
import { Route, RawLocation } from "vue-router";

declare module "vue/types/vue" {
  interface Vue {
    beforeRouteEnter?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void;

    beforeRouteLeave?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void;

    beforeRouteUpdate?(
      to: Route,
      from: Route,
      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
    ): void;
  }
}

main.ts

import Vue from "vue";
import App from "./App.vue";
import Foo from "./views/Foo.vue";
import Bar from "./views/Bar.vue";
import VueRouter from "vue-router";
import "./vue-router-hook-types";
Vue.config.productionTip = false;
const routes = [
  { path: "/foo", component: Foo },
  { path: "/bar", component: Bar }
];
const router = new VueRouter({
  routes
});
Vue.use(VueRouter);
new Vue({
  router,
  render: (h) => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <router-link to="/foo">Foo</router-link>
    <router-link to="/bar">Bar</router-link>
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: "App",
};
</script>

views/Bar.vue

<template>
  <div>bar</div>
</template>
<script>
import Vue from "vue";
import Component from "vue-class-component";
Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate",
]);
@Component
export default class Foo extends Vue {
  beforeRouteEnter(to, from, next) {
    console.log("beforeRouteEnter");
    next();
  }
  beforeRouteUpdate(to, from, next) {
    console.log("beforeRouteUpdate");
    next();
  }
  beforeRouteLeave(to, from, next) {
    console.log("beforeRouteLeave");
    next();
  }
}
</script>

views/Foo.vue

<template>
  <div>foo</div>
</template>
<script>
import Vue from "vue";
import Component from "vue-class-component";
import "vue-class-component/hooks";

Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate",
]);
@Component
export default class Foo extends Vue {
  beforeRouteEnter(to, from, next) {
    console.log("beforeRouteEnter");
    next();
  }
  beforeRouteUpdate(to, from, next) {
    console.log("beforeRouteUpdate");
    next();
  }
  beforeRouteLeave(to, from, next) {
    console.log("beforeRouteLeave");
    next();
  }
}
</script>

We have the vue-router-hook-types.ts with the type definitions for the Vue Router hooks.

In main.ts , we have:

import "./vue-router-hook-types";

to import the type definitions.

Then in the Foo.vue and Bar.vue components, we can see the beforeRouteEnter, beforeRouteUpdate, and beforeRouteLeave hooks in the autocomplete menu when we’re typing them into the component class code.

We can also annotate types of methods in our code.

For instance, we can write:

<template>
  <div>
    <button @click="increment">increment</button>
    <p>{{ count }}</p>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

@Component(({
  watch: {
    count(val: number) {
      this.log(val)
    }
  }
})
export default class HelloWorld extends Vue {
  count: number = 0

  log(val: number): void {
    console.log(val)
  }

  increment(){
    this.count++
  }
}
</script>

We have the log method that returns void and takes a val parameter with type number ,

We use that in the count watcher which is defined in the argument we pass into Component .

This way, we won’t have to worry about passing parameters with data types we don’t expect.

And the same applies to return types of methods.

Conclusion

We can annotate data types easily with TypeScript to avoid making mistakes in our Vue class-based components.

Categories
Vue

Developing Vue Apps with Class-Based Components — TypeScript, Superclasses, Hooks, and Mixins

If we prefer to use classes, we can use the class-based components API that comes with Vue.

In this article, we’ll look at how to developing Vue apps with class-based components.

TypeScript, Superclasses, and Mixins

We can add props and inherit superclass components with the mixins method in our Vue TypeScript project.

For instance, we can write:

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component, { mixins } from "vue-class-component";

const GreetingProps = Vue.extend({
  props: {
    name: String,
  },
});

@Component
class Super extends Vue {
  lastName = "smith";
}

@Component
export default class HelloWorld extends mixins(GreetingProps, Super) {
  get message(): string {
    return `Hello, ${this.name} ${this.lastName}`;
  }
}
</script>

We create the Super component class with the 1astName property.

And we have the GreetProps class that we create with the Vue.extend method so we can accept props in a way that’s acceptable by TypeScript.

Then we call the mixins method with the GreetProps and Super methods so we can inherit from both classes.

We inherit from both classes, so this.name is 'james' and this.lastName is 'smith' .

We can define properties with type definitions.

For instance, we can write:

<template>
  <div>
    <p v-for="p of persons" :key="p">{{ p.firstName }} {{ p.lastName }}</p>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

interface Person {
  firstName: string;
  lastName: string;
}

@Component
export default class HelloWorld extends Vue {
  persons!: Person[] = [
    { firstName: "james", lastName: "smith" },
    { firstName: "jane", lastName: "doe" },
  ];
}
</script>

We create the Person interface and use that for defining the type of the persons class property.

The ! means the class property isn’t nullable.

Now if the persons array entries have extra properties, we’ll get an error from the TypeScript compiler.

Refs and TypeScript

To define and assign refs with TypeScript class-based Vue components, we write:

<template>
  <div>
    <input ref="input" />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  $refs!: {
    input: HTMLInputElement;
  };

  mounted() {
    this.$refs.input.focus();
  }
}
</script>

We have to set the type for each $refs property we assign.

input is an HTML input element, so we set it to the HTMLInputElement type.

Then we call call focus on it to focus it.

With the type annotation added, we get autocomplete when we type in the code in the mounted hook.

Hooks Autocomplete

To add autocomplete for hooks, we write:

main.ts

import Vue from "vue";
import App from "./App.vue";
import "vue-class-component/hooks";
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <HelloWorld />
  </div>
</template>

<script lang='ts'>
import HelloWorld from "./components/HelloWorld.vue";

export default {
  name: "App",
  components: {
    HelloWorld,
  },
};
</script>

components/HelloWorld.vue

<template>
  <div>
    {{ message }}
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  data() {
    return {
      message: "hello world",
    };
  }
}
</script>

Once we add:

import "vue-class-component/hooks";

in main.ts , we get autocomplete when we type in data in the HelloWorld component.

Conclusion

We can add hooks autocomplete, mixins and superclass inheritance, and refs type annotation within our Vue class-based components written in TypeScript.

Categories
Vue

Developing Vue Apps with Class-Based Components — Mixins and TypeScript

If we prefer to use classes, we can use the class-based components API that comes with Vue.

In this article, we’ll look at how to developing Vue apps with class-based components.

Mixins

We can create mixins for Vue class-based components with the mixins function.

For instance, we can write:

<template>
  <div>
    <p>{{ hello }} {{ world }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component, { mixins } from "vue-class-component";

@Component
class Hello extends Vue {
  hello = "Hello";
}

@Component
class World extends Vue {
  world = "World";
}

@Component
export default class HelloWorld extends mixins(Hello, World) {
  created() {
    console.log(this.hello, this.world);
  }
}
</script>

We call mixins with the Hello and World class components.

Then the hello and world class properties will be accessible from the Helloworld component class.

To access them within the class, we can access them from the this object.

And we can also access them in the template.

this Value

We can’t use arrow functions as methods in our component class since we need to access this inside.

Arrow function doesn’t bind to this , so we cant’ use them as class methods.

For instance, if we write:

<template>
  <div>
    <p @click="bar">{{ foo }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  foo = 123;

  bar = () => {
    this.foo = 456;
  };
}
</script>

Then bar won’t update the value of foo since the value of this isn’t the class instance.

Constructor

We shouldn’t add constructor in our class components since they don’t fit with the Vue lifecycle.

For instance, we shouldn’t write:

<template>
  <div>
    <p>{{ foo }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  foo = 123;

  constructor() {
    this.foo = 456;
  }
}
</script>

We can’t access reactive properties in the constructor and we’ll get an error if we try in the constructor.

Creating Class Components with TypeScript

We can create class-based components with TypeScript.

For instance, we can write:

src/components/HelloWorld.vue

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

const GreetingProps = Vue.extend({
  props: {
    name: String,
  },
});

@Component
export default class HelloWorld extends GreetingProps {
  get message(): string {
    return `Hello, ${this.name}`;
  }
}
</script>

src/App.vue

<template>
  <div id="app">
    <HelloWorld name="james" />
  </div>
</template>

<script lang='ts'>
import HelloWorld from "./components/HelloWorld.vue";

export default {
  name: "App",
  components: {
    HelloWorld,
  },
};
</script>

tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

We add the compilerOptions.esModuleInterop property and set it to true so that we can import ES modules in our code.

In App.vue , we register the HelloWorld component.

And in HelloWorld.vue , we call Vue.extend to create the GreetProps class with the props.

Then we can use the extends keyword to create a class that extends GreetingsProp to accept the name prop.

In the template, we display the message , which we get from the message getter.

Conclusion

We can use Vue class-based components with TypeScript and define mixin classes easily in our Vue project.

Categories
Vue

Developing Vue Apps with Class-Based Components — Custom Decorators and Superclass Component

If we prefer to use classes, we can use the class-based components API that comes with Vue.

In this article, we’ll look at how to developing Vue apps with class-based components.

Custom Decorators

We can add our own decorators into our Vue class-based components.

For instance, we can write:

<template>
  <div>
    <button @click="increment(2)">increment</button>
    <p>{{ count }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component, { createDecorator } from "vue-class-component";

const Log = createDecorator((options, key) => {
  const originalMethod = options.methods[key];

  options.methods[key] = function wrapperMethod(...args) {
    console.log(`Invoked: ${key}(`, ...args, ")");
    originalMethod.apply(this, args);
  };
});

@Component
export default class HelloWorld extends Vue {
  count = 0;

  @Log
  increment(val) {
    this.count += val;
  }
}
</script>

We create the Log decorator with the createDecorator method.

We pass in a callback with the options and key parameter.

options has the items in the Vue class component.

key has the name of the properties in the Vue component class.

options.methods lets us access the methods.

Then we can override the method by assigning another function as its value.

Inside the wrappedMethod , we call originalMethod.apply to call the originalMethod with this being the component class instance.

args is the arguments we pass into the component method.

Since we pass in the 2 as the value of val into increment , that will be the value in the args array when we call increment

key is the 'increment' method name when we click on the button.

Extending a Component

One benefit of using a class-based component is that we can create a superclass component that has the shared parts we want to inherit.

For instance, we can write:

<template>
  <div>
    <p>{{ superValue }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
class Super extends Vue {
  superValue = "Hello";
}

@Component
export default class HelloWorld extends Super {}
</script>

We have the Super component class that has the superValue property.

And we have the HelloWorld component class that extends the Super class.

HelloWorld has the superValue property inherited from Super , so we can use it in the template.

Also, we can access the value within the child component class.

For instance, we can write:

<template>
  <div>
    <p>{{ superValue }}</p>
  </div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
class Super extends Vue {
  superValue = "Hello";
}

@Component
export default class HelloWorld extends Super {
  created() {
    console.log(this.superValue);
  }
}
</script>

We log the this.superValue property inherited from the Super class in the created hook.

And we should see 'Hello' logged.

Conclusion

We can add custom decorators and superclass components into our Vue app when we use class-based components.

Categories
Vue

Developing Vue Apps with Class-Based Components — Hooks and Computed Properties

If we prefer to use classes, we can use the class-based components API that comes with Vue.

In this article, we’ll look at how to developing Vue apps with class-based components.

Computed Properties in Class-Based Components

To add computed properties into class-based components, we can add getters and setters into our class.

For instance, we can write:

<template>
  <div>
    <input v-model="name" />
  </div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  firstName = "jane";
  lastName = "smith";

  get name() {
    return `${this.firstName} ${this.lastName}`;
  }

  set name(value) {
    const [firstName, lastName] = value.split(" ");
    this.firstName = firstName;
    this.lastName = lastName || "";
  }
}
</script>

We have an input that’s bound to the name computed property with v-model .

In the component class, we have the firstName and lastName reactive properties.

Below that, we have the name getter method that returns the firstName and lastName combined.

And then we add a name setter that gets the value and split it with split .

Then we assign the split values back to this.firstName and this.lastName .

Hooks

The mounted hook lets us run code when the component is mounted.

And the render hook lets us render template content in the class component:

<script>
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class HelloWorld extends Vue {
  mounted() {
    console.log("mounted");
  }

  render() {
    return <div>Hello World</div>;
  }
}
</script>

We replaced the template tag with the render method.

Register Components

We can register components with the components property in the object we pass into the Component decorator.

For instance, we can write:

<template>
  <Greeting />
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";

const Greeting = {
  template: `<div>hello</div>`,
};

@Component({
  components: {
    Greeting,
  },
})
export default class HelloWorld extends Vue {}
</script>

to register the Component that we created.

Vue Router Hooks

If we want to add other hooks into our Vue class-based components, we’ve to register them.

For instance, we can write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Foo from "./views/Foo";
import Bar from "./views/Bar";
import VueRouter from "vue-router";
Vue.config.productionTip = false;

const routes = [
  { path: "/foo", component: Foo },
  { path: "/bar", component: Bar }
];

const router = new VueRouter({
  routes
});

Vue.use(VueRouter);

new Vue({
  router,
  render: (h) => h(App)
}).$mount("#app");

App.vue

<template>
  <div id="app">
    <router-link to="/foo">Foo</router-link>
    <router-link to="/bar">Bar</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

views/Foo.vue

<template>
  <div>foo</div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";
Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate",
]);
@Component
export default class Foo extends Vue {
  beforeRouteEnter(to, from, next) {
    console.log("beforeRouteEnter");
    next();
  }

  beforeRouteUpdate(to, from, next) {
    console.log("beforeRouteUpdate");
    next();
  }

  beforeRouteLeave(to, from, next) {
    console.log("beforeRouteLeave");
    next();
  }
}
</script>

views/Bar.vue

<template>
  <div>bar</div>
</template>

<script>
import Vue from "vue";
import Component from "vue-class-component";
Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate",
]);

@Component
export default class Bar extends Vue {
  beforeRouteEnter(to, from, next) {
    console.log("beforeRouteEnter");
    next();
  }

  beforeRouteUpdate(to, from, next) {
    console.log("beforeRouteUpdate");
    next();
  }

  beforeRouteLeave(to, from, next) {
    console.log("beforeRouteLeave");
    next();
  }
}
</script>

We add Vue Router into our Vue app with the routes array in main.js .

Then we create the VueRouter instance with the routes array.

And we call Vue.use(VueRouter); to register the components and component methods.

And we pass router into the Vue instance.

In App.vue , we add the router-link s and router-view to let us render the routes and the links that we can click on to navigate through the routes.

Then we register the Vue Router hooks in Foo.vue and Bar.vue with Component.registerHooks .

We pass in the strings with the hook names into the array.

Then finally, we add the methods with the same name in the component classes.

Now when we navigate through the pages, the hooks will run.

Conclusion

We can add computed properties and hooks into our Vue class-based components.