Transitioning from React to Vue.js: my 5 struggles

October 26, 2019 ☼ ReactVuesypeScript

I’ve recently started working on a Vue.js codebase. It’s a fairly complex financial kind of SPA. Coming from React, with little experience with Vue.js, I had actually a really good first few weeks. I was very productive and the framework at the time, felt very intuitive and developer friendly.

Everything was 👌.

But…

When I started to refactor some part of the codebase and getting into more complex problems like data loading, async work in general, error handling and performance I found myself scratching my head quite a few times.

What’s the best way to do this? Why is not re-rendering? Oh no, this is hard to unit-test! How can I make my components more reusable?

As I said, I didn’t have much experience with Vue.js, and the majority of my problems seemed to boil down to the fact that I was still thinking in React” way.

Disclaimer: this is my personal experience in transitioning from React to Vue.js. This is NOT a blog post about one is better than the other. These are my shortcomings. What I didn’t know when I started.

The 5 struggles (Vue v2)

1. Reactivity gotchas

Models are just plain JS Objects. Mutate a property and view re-render. Simple.

Not quite…

Vue cannot detect property addition or deletion

const vm = new Vue({
      data: {
        field: 'value'
      }
    })

vm.newField = 'new value' // `vm.newField` is NOT reactive

This means that you have to declare all root-level reactive data properties upfront in your components.

Solution: read this part of the docs: Reactivity in Depth

2. Easier to couple components to VueX

I use VueX as my state management library. It’s very powerful and makes it easier to reason about state and how it can be mutated in a predictable fashion.

Since you can access the Store from everywhere in your app I tend to use it in my component without thinking too much. It ends up in a tight coupling. If I want to change my data layer I need to touch every single components now. Plus it’s not obvious how you could inject a generic service to fetch data for example.

import { mapActions, mapMutations } from 'vuex';
import { ErrorTypes } from '../store/error';

export default Vue.extend({
  name: 'MyComp',
  created() {
    this.fetchData();
  },
  watch: {
    '$route': 'fetchData'
  },
  methods: {
    ...mapActions({
      asyncWorkInit: 'asyncWork/init',
    }),
    ...mapMutations('error', ['setError']),
    async fetchData() {
      try {
        await this.asyncWorkInit();
      } catch (error) {
        this.setError({ type: ErrorTypes.FATAL, error: createErrorObj(error) });
      }
    }
  }
});

It makes testing even harder! Now I always need to mock the Store for unit-testing my component.

describe('MyComp.vue', () => {
  let wrapper: Wrapper<Vue>;
  let localVue: VueConstructor;
  let store: Store<{}>;

  beforeEach(() => {
    localVue = createLocalVue();
    localVue.use(Vuex);
    localVue.use(VueRouter);

    store = new Vuex.Store(mockStoreFactory({}));
    wrapper = shallowMount(MyComp, {
      localVue,
      store,
    });
  });
});

3. Scoped Slots

I had to read 4 times the docs and a bunch of other blog posts to make Scoped Slots click in my head.

In the end it’s a way to pass data/methods from the child component, back into the parent.

Only used them once so far. Felt great.

4. Share logic between components

Vue.js is an amazing framework and lets you create the right abstraction for what you need. But which one should I use? I struggled the most with Mixins and HOCs.

Mixins: Local, Global

Mixins allow the developer to extract shared functionality and avoid code repetition. Problem iss I don’t know where the methods are coming from (Mixin or component) and I can override Mixin methods in my components if I name them the same.

HOC (High Order Component)

It has the same end goal of a Mixin but without the shortcomings.

My issue with that is that it takes quite a bit of effort to make a reusable HOC (that plays nice with TS).

A library to the rescues vue-hoc

import { createHOC } from 'vue-hoc';
import MyComponent from '../my-component';

const options = {
  name: 'MyEnhancedComponent',
  computed: {
    myComputedProperty(){
      return this.someProp + ' computed';
    }
  },
  created(){
    console.log('Created')
  }
};

const renderWith = {
  props: {
    someProp(){
      return this.myComputedProperty;
    }
  },
  listeners: {
    someEvent(arg){
      this.$emit('someOtherEvent', arg);
    }
  }
};

const enhanced = createHOC(MyComponent, options, renderWith);

Source: https://github.com/jackmellis/vue-hoc/blob/master/packages/vue-hoc/README.md

Ended up with Container Components

5. TypeScript support

Syntax?

As you start developing your app you have to chose if you want to use the standard syntax export default Vue.extend({...}) or the class components module and its decorators @Component, @Prop, @Watch....

Which one? Good question. I’m still not sure, but I went with the standard extend syntax since I didn’t want to change completely syntax and add another library on top.

Props?

This is how (at the moment) you add Types to props in Vue.js:


type MyProps = {
  prop1: string
  prop2: AnotherType
}
export default Vue.extend({
  props: {
    propExample: {
      type: Object as () => MyProps
    }
  }
})

Whaaat? Must be a better way.

I should have used vue-class-component :(

Methods?

Straight from the docs:

Because of the circular nature of Vue’s declaration files, TypeScript may have difficulties inferring the types of certain methods. For this reason, you may need to annotate the return type on methods like render and those in computed.

…rest?

<template> and <style> tags are still not typed.

You can use Vetur but this is only an editor-based check and it cannot be used inside the CI.

Vue-Router has the same issue as above.


As I spend more time working with Vue.js I’ve started to develop a better intuition about what abstractions should I use or how to solve some specific problem.

FREE TIP: read the Vue docs. All of them. Seriously.

Learning a new framework comes with a price. Vue.js does an amazing job of making it very small but, as your application grows, you find yourself digging deeper and ponder the choices that you make.

Good judgment is the result of experience… Experience is the result of bad judgment.

—Fred Brooks


If you have any suggestions, questions, corrections or if you want to add anything please DM or tweet me: @zanonnicola