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
We can now select features we want in our 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:
Quick dependencies installation and now we can see the 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 justprop
) - 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 yourvalue
orkey
easily (I hear "meh, whyvalue
first?", well, you usually dovalue in yourArray
) -
v-if
,v-else-if
andv-else
for conditional rendering, your nice replacement of ternary operators in JSX. Use likev-if="userLoggedIn"
(or simplev-show
todisplay: 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 thatsetState
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>
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>
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;
}
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()
}
// ...
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;
// ...
}
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;
}
// ...
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`
}
Now our img
tag:
<img v-if="username" :src="photoSrcPath" alt="User avatar" />
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);
}
// ...
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 {}
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>
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..?
Top comments (4)
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.
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?
for me it's ahead of Vue in one obvious and one personal points:
At least it's not "HTML in HTML files" :P