DEV Community

Cover image for Add Vue to your acknowledged stack
Alexander Vechy
Alexander Vechy

Posted on • Edited on

Add Vue to your acknowledged stack

TL;DR

Vue.js can't be called "same good as React" (or even "better"). React, as a code library, its tricks and architectural decisions (like Fiber or Time-slicing, Suspense and Hooks), push JS development way further than we could ever expect, it also taught me to think functional which helps a lot in writing any applications using any technology. But Vue.js approach, as for me, slightly different. It gives you focus on the product you develop rather than code you write. At the same time, I believe that 99% of projects could be developed with Vue instead of React with no differences in functionality and performance. But Vue makes you happy. It has so huge amount of small helpers, tips, and tricks, that when you try to build stuff with React again you think "Why the hell I should I write all this boilerplate over and over, and over, and over again?". Vuex is one of the core libraries (see what it means) that give you single-source-of-troth store with jeez convenient way of usage, decreasing you code-base, which leads to fewer places for bugs. vue-router is another core library, that gives you all you need with minimal setup, but very flexible if you need something complicated. I won't even mention powerful UI and UX improvements provided by transition and transition-groups in Vue out of the box, that makes any app better. Do I think that Vue is better than React? Nope, React is still more popular and blows my mind once a year (again Fiber, Suspense). But would I use React for any next project? Nope, nope, nope. With Vue.js developer experience is way better, I would rather go with it.

Let's start

Okay, I know that React developers are very busy, no time for more intro. Let's create a new Vue project:

npx @vue/cli create simple-sample
Enter fullscreen mode Exit fullscreen mode

We can now select features we want in our setup:
Vue features setup

I selected TypeScript because we like safe types, I don't need any preprocessors, because PostCSS included by default, and vuex with vue-router because those are important parts of Vue ecosystem. We want to use class syntax (yeah, it's not default) because classes are familiar and look good. So we have our setup like:
vue project setup

Quick dependencies installation and now we can see the project structure:

Initial project structure

shims- just a setup for TS, to use this awesome typed JavaScript in .vue Single File Components. You probably heard about SFC already: we don't have to, but we can write our components in one file and be happy with it!
Why? Well, because your component usually is a skeleton (template), behavior (script) and look (style). So let's create our vue file in components folder* and write our component. I called it DevToHeader.vue.

(quicktip: Vetur is a Vue syntax helper for VS Code)

Quick intro to templates

  • Templates are valid html
  • if you need to bind some data to template, you use v-bind (nobody does that**, use :), e.g. :prop="{ react: 'cool' }" (same as React, :prop="true" is equal to just prop)
  • if you need to listen to some event, you use v-on or shortly @. e.g. @click="functionName" or feel the power of @customEvent="handlerOfThisEventName" or @click="$event => handlerFuncName($event, 'my custom data')" or @mousedown="mouseDownDataInYourComponent = true"
  • You need to remember only a few directives:
    • v-for directive is for loops, iterates through your collection like: v-for="(value, key) in youObjectOrArray", so now you can use your value or key easily (I hear "meh, why value first?", well, you usually do value in yourArray)
    • v-if, v-else-if and v-else for conditional rendering, your nice replacement of ternary operators in JSX. Use like v-if="userLoggedIn" (or simple v-show to display: none; of (!)mounted components, you will quickly find out how awesome this helper, no css or inline styles needed now!)
    • v-model - your hero that saves you from writing methods that setState for each dynamic input. You now can have <input v-model="searchText" /> that is the same as <input :value="searchText" @input="updateSearchTextValue)" /> (can you guess what this example from docs does: <input v-model.number="age" type="number">?
    • you can see or create a custom one, they usually start with v-* and adds some cool features.
  • To render some data you use mustaches: <h2>{{ variableName }}</h2>, no need those for just text: <h2>search</h2>.

That's basically it! Having this knowledge, let's define our template:

<template>
  <header class="main-header">
    <img src="../assets/logo.png" alt="logo" />
    <input placeholder="search" v-model="searchText" />
    <button @click="openModal">Write a post</button>
    <img v-if="user" :src="user.photo" alt="User avatar" />
    <button v-else>Login</button>
  </header>
</template>
Enter fullscreen mode Exit fullscreen mode

No questions here, right? Maybe only where is this dynamic data comes from, like user or functions like goToNewPostPage?

Let's define data and logic

Now we can go to a script tag. We selected class-based sytax for easier transition from React and we have TypeScript support just for fun. Let's start:

<script lang="ts">
</script>
Enter fullscreen mode Exit fullscreen mode

Now let's go to the body:

// think about this as import React from "react"
import { Component, Vue } from "vue-property-decorator";

// with this decorator we're saying to compile regular Vue component from our class
@Component
export default class DevToHeader extends Vue {
    user:User = null;
    searchText:string = ""; // two-way binding in v-model works with this guy

    openModal(event: Event) {
      this.$emit('openCreatePostModal', event);
    }
}

type User = IUser | null;

interface IUser {
  photo: string;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

In this way, we defined data in our component and method that $emits data. Remember that @customEvent="handlerForIt"? Well, now a parent of our header can listen to the event @openCreatePostModal="handlerForIt" and the handler will receive event as an argument. And we can pass any data we want to our parent.

some vue-specific methods or data always start from $ sign.

Q: Where is our componentDidMount?
Well, just define a mounted method:

  // ...
  async mounted() {
    this.user = await fetchUserData()
  }
  // ...
Enter fullscreen mode Exit fullscreen mode

User updates -> component updates -> view updates. Easy.

Q: What about static getDerivedStateFromProps(props, state)?
Okay, let's pretend that we get username from parent and we want to change the path to the avatar depending on username. For this, we to change a bit:

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

@Component
export default class DevToHeader extends Vue {
    @Prop({
      type: String, // your `prop-types` checks out of the box
      default: null // we don't really need that
    })
    username:string | null = null; // now for TypeScript
    // our photo src path that we will use as img :src
    photoSrcPath: string | null = null;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

All props are available as instance properties, the same way as our self-defined data. Let's add adding path now:

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

// ... or component class
    // watch for 'username' property
    @Watch('username', {
      immediate: true // call this function also on component mount
    })
    changePhotoPath(username:string | null) { // takes (newValue, oldValue)
      this.photoSrcPath = username ? `/user/${username}/data/avatar.png` : null;
    }
// ...
Enter fullscreen mode Exit fullscreen mode

So we change our state based on property change, is it the most common case for getDerivedStateFromProps? And yes, you can watch for your "state" data propertes as well. Watchers are very powerful 💪.

But how can we handle it in a Vue way? Computed properties! Since we don't need to change any other data in our component, don't have complex logic and we don't need to make any async requests, it makes sense to have a simple property that will change based on username. And computed properties are the way to go, they are performant, they have cache and easy to write and use:

  // remove photoSrcPath data property
  // define computed property:
  get photoSrcPath():string {
    return `/user/${this.username}/data/avatar.png`
  }
Enter fullscreen mode Exit fullscreen mode

Now our img tag:

  <img v-if="username" :src="photoSrcPath" alt="User avatar" />
Enter fullscreen mode Exit fullscreen mode

Of course you can have any kind of stuff in computed, like I had once a bunch of filters for the same input collection:

// ...
    get filteredByA() {
      return this.collection.filter(filterByA).map(setFlags);
    }

    get filteredByB() {
      return this.collection.filter(filterByB)
    }

    get filteredByC() {
      return this.collection.filter(filterByC).map(setFlags);
    }
// ...
Enter fullscreen mode Exit fullscreen mode

No need to store it in state, implement shouldComponentUpdate or stuff. And again, they're very performant.

Add our component

Let's go to the views/Home.vue and add our component there:

import { Component, Vue } from "vue-property-decorator";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
import DevToHeader from "@/components/DevToHeader.vue";

@Component({
  components: {
    HelloWorld,
    DevToHeader // becomes 'DevToHeader': DevToHeader
  }
})
export default class Home extends Vue {}
Enter fullscreen mode Exit fullscreen mode

Now we pass into decorator some options, specifically components. In this way, we're saying to Vue compiler which components we're going to use in our template. Vue automatically changes PascalCase to kebab-case to use in templates (or you can name it yourself, like 'hello-w': HelloWorld). So inside our Home.vue template we can use our component:

  <div class="home">
    <dev-to-header
      username="Alex"
      @openCreatePostModal="$router.push('/newPost')"
    />
    <img alt="Vue logo" src="../assets/logo.png">
    <hello-w msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
Enter fullscreen mode Exit fullscreen mode

We pass "Alex" as a username prop and attach a listener to our component. Our header didn't know, but there is no modal, we should just go to another page (yeah, we should rename this event now), so I wrote an inline function here. Remember inliners🔝? They're not very good from DX perspective, but for some simple stuff, to avoid boilerplate, why not? We're people after all...

So this inliner actually calls this.$router.push('/newPost'), so what's $router?

vue-router

Did you have an experience of your router setup being rewritten a couple of times because of React-Router upgrades? Look at this setup that almost didn't change with time:

Already see bundle split on page level thanks to dynamic import?

Vue.use(Router) adds a couple of global components for you, that you can use in templates as <router-view/> and <router-link to="/about">About</router-link>. And superproperties to your Vue instances: $route which contains your current route info, like params, query, metadata and $router which gives you methods to manipulate router programmatically. Good stuff, good stuff.

vuex

Thanks to Vue.js reactivity system, you don't need thunks, sagas and connect. You just define store, as in the example project, and use it as one more superproperty this.$store in your components. Async actions, mutations, modules, middleware - everything is just there. Need one more really awesome abstraction that can reduce your codebase - vuex-pathify looks pretty.

You are a weirdo and love JSX

JSX is supported, it's a babel abstraction and Vue uses the same render method approach as React.

React.createContext?

Yup, also there. You define provide property in parent component and inject: ['nameOfPropertyToInject'] in your any depth children component.

Just try it

There is no point to resist of trying new tools. I often don't understand people who don't like Vue even without really trying it. At the end of the day, this is the tool to improve your productivity, your users happiness. If it doesn't work for you, then leave it, but don't give up early. I had an issue with changing mind back from everything should be immutable, calling this.smth = ... made me feel like I'm doing something wrong or cheating. No, it's just because we used to write React code (just JS, yes 🙃). Can't not to mention that I also started to improve UX of any app by adding transitions, because they're very easy to set up and use in Vue.

Thanks for reading, see you on Twitter or maybe in live..?

* (I received questions about how to know when to put a component to views folder and when to components. Well, if your component is reused, say on different pages/views or other components than put it in components folder.
** Yeah, I know about stuff like v-bind="$attrs" let me keep this article shorter? :)
Cover photo: https://blog.pusher.com/building-external-modules-vuejs/

Top comments (4)

Collapse
 
oivoodoo profile image
Alexandr K

I tried to use React in my side project, got headache on understanding how to begin the project at all. when I met Vue I found that it's pretty easy to begin, easy to read documentation and libraries in GitHub they are pretty stable on use. I am not doing frontend daily but vue helped me a lot to make pretty stable dashboard during the day.

React is still on top in case if you are looking for work. But Vue is much easy to begin and use in short time and in case if you don't have technical limitations by the project.

Collapse
 
xowap profile image
Rémy 🤖

I'm curious, I've never given any real interest to React (HTML in JS? yuck) so I wonder how exactly it is ahead of Vue?

Collapse
 
alvechy profile image
Alexander Vechy

for me it's ahead of Vue in one obvious and one personal points:

  • obvious: larger community and ecosystem (not like a real problem)
  • personal: Fiber architecture is just amazing. Vue.js approach is quite straightforward in how it renders DOM and updates Nodes. Fiber allows to render stuff by chunks, which is the huge look and feel improvement in some of the cases. and React's so-called Suspense looks pretty same cool, it's something everybody gonna use.
Collapse
 
qm3ster profile image
Mihail Malo

At least it's not "HTML in HTML files" :P