DEV Community

Cover image for Design Patterns: Vue feels like React - TypeScript πŸ”₯
Natserract
Natserract

Posted on

Design Patterns: Vue feels like React - TypeScript πŸ”₯

Cover photo by Ricardo Gomez Angel on Unsplash.

When you first want to learn about a front-end technology, you will be confused by the many choices of tools, for example React, Vue, Angular, Svelte, etc. Of course we will not know if we do not try one of these, certainly all of these technologies have their pros and cons.

But in this article, we will not discuss which is the best, but we will discuss about how react developers can easily master these two frameworks (React & Vue), with the same pattern.

So, this is a long journey! Prepare yourself! πŸ˜ƒ


Setting Up Project

First we have to do is set up the project, let's start by creating a directory structure first.

1. The root directory structure

There are containers and presentational folders in the components folder. The difference is, the presentational component focuses on the UI element, while the container component which regulates the logic / storing data part, which will be displayed in the component container.


    β”œβ”€β”€ src
    | β”œβ”€β”€ assets
    | β”œβ”€β”€ components
    |   β”œβ”€β”€ container
    |   β”œβ”€β”€ presentational
    β”œβ”€β”€ redux
    | β”œβ”€β”€ action
    | β”œβ”€β”€ reducer
    β”œβ”€

You can freely set the directory structure that you like, this is my directory structure for creating projects

2. Use jsx & typescript

So, let's start by installing some of the dependencies that are needed. We can do this with type the following command:

npm i --save-dev typescript babel-preset-vca-jsx
npm i --save-dev @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime 
npm i --save-dev @babel/preset-typescript @types/webpack-env source-map-loader 
npm uninstall babel-plugin-transform-runtime 

We need to uninstall this package babel-plugin-transform-runtime, because we have installed the latest version @babel/plugin-transform-runtime

And then, we have to set some additional config because some dependencies require a supported Babel version

Note: I happen to use this boilerplate: Vue Webpack Template you can choose any boilerplate.

Update your babel core dan babel loader

npm i --save-dev babel-core@^7.0.0-0 babel-loader@^8.0.6 
npm i --save-dev @babel/core@^7.6.4 @babel/preset-env@^7.6.3 

After installing all dependencies, we have to set additional config on .babelrc open the file, then add config .babelrc we also need to set the webpack loader webpack config

Note: For typescript loader, you can use Awesome TypeScript Loader

And don't forget, you also need to add some config in.eslintrc.js

rules: {
    'import/extensions': ['error', 'always', {
      jsx: 'never',
      ts: 'never',
      tsx: 'never'
    }],
}

And next, create new file tsconfig.json and follow this config tsconfig.json

Note: If you wanna using TSLint, you can follow this step configuring TSLint

After all config has been added, hooray! it's time to replace all your project file extensions from .jsx/.js to .tsx/.ts

3. Installing additional dependencies

npm i --save @vue/composition-api vuejs-redux redux @types/redux 

Main Concept

As very popular front-end tools, both of these tools have the same features, such as two-way data-binding, templating, routing, components, dependency injection, and many more.

Similar but not the same, there are some differences between these two tools, namely in terms of writing syntax, rendering components, managing state and data. Therefore, in this section we will peel one by one how to implement the react pattern in vue.

Components and Props

Components are special types of instructions such as JavaScript functions that will be displayed as separate parts and can be reused.

Here I use Vue.component and createComponent (vue composition api) you can use both

In rendering a component, the two are very different. React defines components as classes or functions, while Vue defines components as objects.

export default createComponent({
    name: 'ComponentProps',
    props: {
        name: String,
        authorName: Array as () => string[]
    },
    setup(props) {
        return () => (
            <div className="components-props">
                <h2>{props.name}</h2>
                <p>{props.authorName}</p>
            </div>
        )
    }
})

We no longer need to use template again, just JSX like React πŸ™‚

render () {
  return (
      <ComponentProps 
         name="Your name here" 
         commentId={['Name1', 'Name2']} 
      />
  )
}

Conditional Rendering

Conditional rendering works the same way like conditions work in JavaScript, we can use ternary or conditional operator.

export default createComponent({
    name: 'ConditionalRendering',
    props: {
        show: Boolean
    },
    setup(props) {
        return () => props.show ? <p>True Condition</p> : <p>False Condition</p>
    }
})
render() {
   return <ConditionalRendering show={false}/>
}

Handling Events

In Vue JS, when handling events, vue gives us directions for using v-on directive to handle these events. Since we already use JSX, so we don't need that anymore, we can use the JSX attribute like in React :)

export default createComponent({
    setup(props) {
        return () => (
            <button onClick={props.handleButtonClick}>
                Click Event
            </button>
        )
    },
    props: {
        handleButtonClick: Function as () => void
    }
})
render () {
  return (
       <HandlingEvent 
          handleButtonClick={() => alert("Click event. This works!")} 
       />
  )
}

Children in JSX

Children is a component that is used to display anything that you include between the opening and closing tags when calling the component.

In order to access this component, we can use slots function to be used as a content distribution outlet.

export default Vue.component('Children', {
    render() {
        return (
            <div className="children">
                {this.$slots.default}
            </div>
        )
    }
})
render () {
  return (
     <div className='container'>
        <Children>
          {/* what is placed here is passed as children */}
        </Children>
     </div>
  )
}

Lifecycle & Hooks

Lifecycle is a method that regulates the stages of the lifecycle in a component, and has their respective uses

  • setup: is called right after the initial props resolution when a component instance is created. Lifecycle-wise, it is called before the beforeCreate hook.
  • onBeforeMountfunctions that are executed before the rendering process is run.
  • onMountedfunction that are called only once after the first rendering is done. Usually this function is used for performing any side-effect causing operations such as AJAX requests.
  • onUnmountedfunction that are executed to eliminate or delete a component from DOM.
import {
    createComponent,
    reactive as useState,
    onBeforeMount as componentWillMount,
    onMounted as componentDidMount,
    onUnmounted as componentWillUnmount
} from '@vue/composition-api';

const LifecycleHooks = createComponent({
    setup() {
        const state = useState<{ loading: boolean, users: object }>({
            loading: false,
            users: []
        })

        componentWillMount(() => {
            console.log("Component before mount")
        })

        componentDidMount(() => {
            const API_URL = 'https://jsonplaceholder.typicode.com/users'
            fetch(API_URL)
                .then(res => res.json() as Promise<any>)
                .then(data => {
                    state.users = data,
                        state.loading = !state.loading;
                })
                .catch((err: Error) => {
                    throw err
                })
            console.log("Component Mounted")
        });

        componentWillUnmount(() => {
            console.log("Component Will Unmount")
        })

        return () => (
            <div className="lifecycle-hooks">
                {state.loading ? JSON.stringify(state.users) : <span>Loading...</span>}
            </div>
        )
    }
})

export default LifecycleHooks

Yes, I used as ... for importing the module, this is only the naming, so it looks the same as the method name in React

  • reactive function is the equivalent of Vue 2's Vue.observable() it will return a new object that looks exactly the same as obj, and returns a reactive proxy of the original.
  • watch function expects a function. It tracks reactive variables inside, as the component does it for the template. When we modify a reactive variable used inside the passed function, the given function runs again.
import {
    createComponent,
    reactive as useState,
    watch as useEffect
} from '@vue/composition-api';

const LifecycleHooks = createComponent({
    setup() {
        const state = useState<{ count: number }>({
            count: 0
        })

        /* => Re-run it whenever the dependencies have changed */
        useEffect(() => state.count, (nextState, prevState) => {
            console.log(nextState, '<= this is nextState')
            console.log(prevState, '<= this is prevState');
        })

        return () => (
            <div className="lifecycle-hooks">
                <button onClick={() => state.count++}>
                    Update Value
                </button>
            </div>
        )
    }
})

Redux & Vue

Surely you must already know what is Redux ?, yes you're right! Redux is an Agnostic State Management Library framework for Javascript Apps. Not like Vuex, redux can be used in any framework.

Redux has 4 main concepts: reducers, actions, action creators, and store. In Redux states are immutable and pure functions. Here are some things to know more about redux in vue:

Actions

Actions are simple Javascript objects that represent payloads of information that send data from your application to your store. Actions have a type and an optional payload.

export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const RESET = 'RESET'


export const increment = () => {
    return { 
        type: INCREMENT 
        // your payload here
    }
}

export const decrement = () => {
    return { 
        type: DECREMENT 
    }
}

export const reset = () => {
    return { 
        type: RESET 
    }
}

Reducers

Reducers specify how the application's state changes in response to actions sent to the store. Reducers can then be combined to one root reducer to manage all of your applications state.

type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'RESET' };

const Counter = (state: number = 0, action: Action) => {
    switch (action.type) {
        case 'INCREMENT': {
            return state + 1;
        }
        case 'DECREMENT': {
            return state - 1;
        }
        case 'RESET': {
            return state
        }
        default: return state
    }
}

export default Counter

Using combineReducers to calls all reducers when dispatching an action in one root reducer function. It's very useful:)

import { combineReducers } from 'redux'
import userReducer from './reducer/user.reducer'

export default combineReducers({
    user: userReducer
    // your another reducer here
})

Store

A store is a place where you store the state of your application. Store, holds the whole state tree of your application that refers to the object with a few methods on it together. There is only a single store in a Redux application.

import Vue from 'vue'
import { createStore } from 'redux'

import Provider from 'vuejs-redux';
import RootReducer from './rootReducer'

const store = createStore(RootReducer);

export default Vue.component('Provider', {
    render() {
        return (
            <Provider 
                mapStateToProps={this.mapStateToProps} 
                mapDispatchToProps={this.mapDispatchToProps} 
                store={store}> 
                {this.$scopedSlots.default}
            </Provider>
        )
    },

    props: ['mapStateToProps', 'mapDispatchToProps'],

    components: {
        Provider
    }
})

We can also create a custom provider that receive mapStateToProps and mapDispatchToProps as props and importing the store and passing it to every Provider.

import Vue from 'vue';
import ContextConsumer from './redux';
import * as actions from './redux/action/user.action';

import ComponentContainer from './components/container/component-wrap';

export default Vue.component('App', {
  render() {
   return (
      <ContextConsumer 
          mapStateToProps={this.mapStateToProps} 
          mapDispatchToProps={this.mapDispatchToProps}>
            {({ incrementAction, userData }) => (
                <ComponentContainer>
                    <SingleComponent
                      value={userData.user}
                      handleClick={incrementAction} 
                    />
                </ComponentContainer>
            )}
      </ContextConsumer>
    )
  },

  components: {
    ContextConsumer
  },

  methods: {
    mapStateToProps(state) {
      return {
        userData: state
      }
    },
    mapDispatchToProps(dispatch) {
      return {
        incrementAction: () => dispatch(actions.increment())
      }
    }
  }
})

Higher-Order Components

A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API. They are a pattern that emerges from React’s compositional nature.

If you understand the concept of higher-order functions (HOF), of course it will be very easy to make HOC, because HOC is an implementation of HOF :)

import Vue from 'vue'

const useDataFetchingHOC = (WrappedComponent: JSX.IntrinsicElements) => (urlParam: string) => {
    return Vue.component('HOCFetch', {
        data: () => ({
            fetchData: null
        }),
        mounted: function() {
            fetch(urlParam)
                .then(response => {
                    if (!response.ok) { throw new Error(response.statusText) }
                    return response.json() as Promise<any>;
                })
                .then(data => this.fetchData = data)
                .catch((err: Error) => {
                    throw err
                })
        },

        render(createElement) {
            return !this.fetchData ? createElement('span', 'Loading Fetch...') :
                createElement(WrappedComponent, {
                    attrs: this.$attrs,
                    props: this.$props,
                    on: this.$listeners
            })
        }
    })
};

export default useDataFetchingHOC
import { createComponent } from '@vue/composition-api'
import useDataFetchingHOC from '../presentational/hoc-component'

const dataSourceUrl = "https://jsonplaceholder.typicode.com/users";

const ContentSite = createComponent({
    setup() {
      return () => (
        <div className="content">
          <p>Yes, i'm in HOC</p>
        </div>
      )
    }
  })

export default useDataFetchingHOC(ContentSite)(dataSourceUrl)

Thanks For Reading

Thanks for reading, i hope you have enjoyed this article, and that it gave you some sort of inspiration for your own work. To be sure, Vue and React are very cool front-end tools, and are in great demand by many users. So, keep trying and learning new things, and don't forget always trust yourself! 😎

The complete source code for this project is available at Gitlab.

Top comments (19)

Collapse
 
intermundos profile image
intermundos • Edited

See no added benefit in working with Vue as react. If you like jsx and React way, use React. Why bother with all this, especially when there is no certain way to prove that this setup will work flawless. IMHO

Collapse
 
natserract profile image
Natserract

Haha, you didn't read my full article correctly, this is the react pattern implementation in vue, how do react developers understand the vue with the same pattern. If you do not want to bother, you can sit and be quiet, then notice how javascript is very flexible in almost everything, you can use any method in your development process

Collapse
 
intermundos profile image
intermundos

Haha, might be its me gotten your article wrong. What pattern are you talking about? Take Vue and start using it as React? You may notice that JS is perhaps very flexible but still see no reason to build another bicycle other than just for fun and to prove you can turn Vue into React. As said before - want to use React like pattern - use React. Kudos from the quiet and good luck with this abomination you build there.

Thread Thread
 
natserract profile image
Natserract

LOL, I didn't change vue to React. I just used the pattern and what i mean is flexible is not for fun. Maybe you only take one word in this article, I don't know what you mean "want to use React like pattern - use React" Haha, even for ordinary people who already know that, are you kidding?

Thread Thread
 
natserract profile image
Natserract

As I said earlier, javascript is very flexible, you can do anything. The development of technology is very fast, and as developers we must also learn the new technology (if needed). In this article I explain how we easily master these two front-end technologies with same pattern. Is there anything unclear?

Thread Thread
 
wkrueger profile image
wkrueger

"react" was mostly just a tagline.

Switching html to JSX is just a minor templating change. It's like changing from - say - HTML to pug.

If you havent used typescript you would never understand... but JSX plays VERY well together with typescript because since it is just a syntax sugar for javascript, you get excellent TS goodies (type checking, completions, tooltip docs) on templates.

Even if vue or angular templates have their own language services, its not the same thing. You wont't ever get a language service on templates as good as just TS acting over JS.

Now, on the other side, the composition api seems something a bit more controversial. But since it is mainained by vue itself, I'd trust it!

Collapse
 
fly profile image
joon

I have a feeling that this way of setting up a project could help Vue users who are trying to switch to React.(or vice versa)
Thank you for the post!

Collapse
 
meistertea profile image
MeisterTea

Who could want to switch to react after trying vue except being forced to?

Collapse
 
fly profile image
joon

I have a dev friend(whom I sent this post to) who used vue but decided to switch because of the political preferences of the Vue devs. It can be surprising what makes some people switch their tech stacks :)

Collapse
 
natserract profile image
Natserract

I choose both 😁

Collapse
 
edwinharly profile image
Edwin Harly

I've tried both and personally I prefer react

Collapse
 
fantasticsoul profile image
εΉ»ι­‚ • Edited

try setup(powered by concent) in react:
js ver: codesandbox.io/s/concent-guide-xvcej
ts ver: codesandbox.io/s/concent-guide-ts-...
function component and class component share business logic code easily.

Collapse
 
funksoulninja profile image
Anthony Falcon

Vue is great to work with, but the TypeScript types support is lacking when compared to React or Angular. If you're not typing your code, Vue (especially with nuxt) is very fun to work with. Though, with very large and complex applications, I believe Angular or React is the best way to have secure and robust code.

Collapse
 
willvincent profile image
Will Vincent

You could always use jsx with vue, that's not a new thing. Honestly I skimmed the article so that may not have been a point made though... anyway, just because you can doesn't mean you should. Vue's template syntax is easy to use and understand, that's a huge part of its appeal.. jsx is fugly

Collapse
 
natserract profile image
Natserract

Hmmm, finally I know, this is the reason why some developers avoid JSX, because they say it's β€œcomplicated”, β€œfugly”, or blabla 🀣

Collapse
 
ghalex profile image
Alexandru Ghiura

Congrats on your article. I use and love both (React & Vue).

Collapse
 
paul_melero profile image
Paul Melero
Collapse
 
wkrueger profile image
wkrueger • Edited

I haven't used vue yet, but it completely nails in so many aspects, such as providing a good default form library, good starters for zero-build projects... and this: flexible templating!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.