DEV Community

loading...
Cover image for Vue.js developers' guide to React

Vue.js developers' guide to React

_masahiro_h_ profile image Masahiro Harada ・10 min read

If you already learned one language or framework, it's easier to learn similar ones. Instead of reading docs from top to bottom, you just think, "how to do X with Y?".

In this article, I introduce React way of implementing Vue.js features for Vue.js devs who want to learn React as well.

(I don't encourage anyone to switch from Vue.js to React! It's better -- and also fun -- to getting to know both, right?)

Components

How to create a component?

Vue.js way

Vue component is consists of 3 blocks -- <template>, <script>, <style>. And the file should have .vue extension.

<template>
  <div>
    <h1>Sample</h1>
    <h2>This is a component.</h2>
  </div>
</template>

<script>
export default {
  // data and behaviors.
}
</script>

<style>
/* how this component looks */
</style>
Enter fullscreen mode Exit fullscreen mode

React way

In React, you have two ways to create components -- function and class.

Below is an exmaple of functional way of creating a component.

import React from 'react';

function Sample() {
  return (
    <div>
      <h1>Sample</h1>
      <h2>This is a component.</h2>
    </div>
  );
}

export default Sample;
Enter fullscreen mode Exit fullscreen mode

A functional component is a function that returns React element. It should looks like JavaScript returning HTML, but it's not. It's JSX.

In order to use JSX, you have to import React though it doesn't seem to be referenced directly. (But in React v17.0, you can choose not to import React just for JSX. Read this official post for details.)

Below is another way of creating react components with class syntax.

import React from 'react';

class Sample extends React.Component {
  render() {
    return (
      <div>
        <h1>Sample</h1>
        <h2>This is a component.</h2>
      </div>
    );
  }
}

export default Sample;
Enter fullscreen mode Exit fullscreen mode

Class component's render method returns React element.

So, what is the difference between the two and which way should you choose to write your own React components?

In v16.7 and below, class components have state management (data in Vue.js) and lifecycle methods -- both are crucial for useful components -- and functional ones didn't.

But, from v16.8, React introduced Hooks into functional components. Hooks take care of state management and "side effects" (operations that should happen after rendering).

Although a few lifecycle methods are not "translated" in hooks, functional components can do pretty much the same jobs as class components. And React team recommends functional way for your first choice.

When you’re ready, we’d encourage you to start trying Hooks in new components you write.
In the longer term, we expect Hooks to be the primary way people write React components.
Should I use Hooks, classes, or a mix of both?

So if you start brand new React project, or you are a React beginner, I think you should consider writing in functional way first. And if you want to use class-only features, then introduce class components. It's totally OK that functional components and class components live together.

In this article, I explain about functional way.

Templating

Vue.js way

Vue component's <template> has its own syntax such as v-bind, v-for, v-if.

<template>
  <div>
    <h1>Hello, {{ name }} !</h1>
    <a :href="link">Click me</a>
    <ul>
      <li v-for="item in items" :key="item.key">
        {{ item.title }}
      </li>
    </ul>
    <p v-if="isActive">Paragraph</p>
    <p v-show="isShow">Second paragraph</p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

React way

In React, you use JSX.

return (
  <div>
    <h1>Hello, {name} !</h1>
    <a href={link}>Click me</a>
    <ul>
      {items.map(item => (
        <li key={item.key}>{item.title}</li>
      ))}
    </ul>
    {isActive && <p>Paragraph</p>}
    <p style={{ display: isShow ? 'initial' : 'none' }}>Second paragraph</p>
  </div>
);
Enter fullscreen mode Exit fullscreen mode
  • JSX is not a template engine. It has only one special syntax -- {} -- and the rest are just JavaScript.
  • statement inside of {} is evaluated as JavaScript.
  • There is no equivalent for v-show. So basically you should manually manipulate display of style attribute.
  • I'll talk about CSS classes later.

Just like Vue's <template>, functional component must return only one root element. But React has convenient helper component <React.Fragment>. It lets you return multiple elements in order for you not to wrap elements with useless <div> only for the sake of a framework's requirement.

return (
  <React.Fragment>
    <h1>...</h1>
    <h2>...</h2>
    <h3>...</h3>
  </React.Fragment>
);
Enter fullscreen mode Exit fullscreen mode

<React.Fragment> is not rendered as DOM. You just get <h1>, <h2> and <h3> in the exmaple above.

And, <React.Fragment> has its syntax sugar. Its name can be omit. That is, the snippet above can be written as below.

return (
  <>
    <h1>...</h1>
    <h2>...</h2>
    <h3>...</h3>
  </>
);
Enter fullscreen mode Exit fullscreen mode

Odd but handy, huh?

CSS classes

Vue.js way

Vue.js offers v-bind:class as a way to manipulate HTML class attribute.

<button class="btn" :class="{ 'btn-primary': isPrimary }">...</button>
<button class="btn" :class="['btn-primary', 'btn-small']">...</button>
Enter fullscreen mode Exit fullscreen mode

React way

There is no special way in React. className is just an equivalent for class attribute. class is one of reserved keywords in JavaScript so JSX calls this className.

return <button className="btn btn-primary">...</button>;
Enter fullscreen mode Exit fullscreen mode

Although, classnames library will help you dealing with HTML classes.

import classNames from 'classnames';
Enter fullscreen mode Exit fullscreen mode

It's just like v-bind:class.

const buttonClass = classNames({
  btn: true,
  'btn-primary': isPrimary
});

return <button className={buttonClass}>...</button>;
Enter fullscreen mode Exit fullscreen mode

HTML

Vue.js way

For injecting HTML string, you use v-html in Vue.js.

<div v-html="htmlString"></div>
Enter fullscreen mode Exit fullscreen mode

React way

In React, there is a prop named dangerouslySetInnerHTML. It literally warns you that inserting HTML string carelessly is a dangerous move. dangerouslySetInnerHTML accepts an object that has __html property with HTML strings as its value.

return <div dangerouslySetInnerHTML={{ __html: htmlString }} />;
Enter fullscreen mode Exit fullscreen mode

Events

Vue.js way

In Vue.js, events are represented by @ syntax (sugar for v-on).

<button @click="handleClick">Click me</button>

<form @submit.prevent="handleSubmit">...</form>
Enter fullscreen mode Exit fullscreen mode

React way

React takes more HTML-like approach. Event handler is passed to prop named onEventName -- e.g. onChange, onSubmit.

const handleClick = e => {/* blah blah... */};

return <button onClick={handleClick}>Click me</button>;
Enter fullscreen mode Exit fullscreen mode

There is no event modifiers like .prevent.

States (data)

Vue.js way

In Vue.js, component's internal state is defined by a return value of the data method.

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Inside other parts of the component, you can reference its state value through this.

methods: {
  increment() {
    this.count += 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

React way

In React, you use useState hook. Hey, here comes the hook!

import React, { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => setCount(count + 1);

  return <button onClick="handleClick">{count}</button>;
}
Enter fullscreen mode Exit fullscreen mode

Hooks are functions for accessing React's magic. useState is the one for managing component's state.

useState takes state's default value as an argument, and returns array that contains 0. "state variable" and 1. "function that update state". State's value can only be updated through that function.

Call useState by individual states.

const [name, setName] = useState("John Doe");
const [age, setAge] = useState(20);
Enter fullscreen mode Exit fullscreen mode

You can set object as state's value.

const [user, setUser] = useState({ name: "John Doe", age: 20 });
Enter fullscreen mode Exit fullscreen mode

Forms

Vue.js way

In Vue.js, v-model handles form inputs.

<input type="text" v-model="name" />
Enter fullscreen mode Exit fullscreen mode

v-model lets you implement bi-directional data flow.

React way

React doesn't introduce sytax sugar for bi-directional data update. You have to implement it on your own by combining state and event.

const [name, setName] = useState('');
const handleInput = e => setName(e.target.value);

return <input type="text" onChange="handleInput" value="name" />;
Enter fullscreen mode Exit fullscreen mode

It's a bit annoying writing this boilerplate almost everytime dealing with forms. But I think this kind of simplicity, or "give you no sugar", "write what you need on your own as possible" style is very React-ish.

methods

Vue.js way

Inside of methods defined in methods, you can reference states (data). And the methods can be referenced inside your template.

<script>
export default {
  methods: {
    sayHello() {
      console.log(`Hello, ${this.name}!`)
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

React way

There's nothing like Vue's methods in React. React component is essentially just a JavaScript function, so you treat it as it is.

function MyComponent() {
  const [name, setName] = useState('John');

  function sayHello() {
    console.log(`Hello, ${name}!`);
  }

  return <button onClick={sayHello}>...</button>;
}
Enter fullscreen mode Exit fullscreen mode

ref

Vue.js way

In Vue.js, ref gives you direct access to the DOM.

<template>
  <div>
    <div ref="foo">...</div>
    <button @click="handleClick">Click me</button>
  </div>
</template>

<script>
export default {
  methods: {
    handleClick() {
      console.log(this.$refs.foo);
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

React way

React has similar functionality as Vue's ref.

With useRef hook, you can create a "ref object" for accessing DOM. The object's current property contains reference for the DOM.

import React, { useRef } from 'react';

function MyComponent() {
  const target = useRef(null);

  const handleClick = () => {
    console.log(target.current);
  };

  return (
    <>
      <div ref={target}>...</div>
      <button onClick={handleClick}>Click me</button>
    </>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

computed properties

Vue.js way

Vue.js has "computed properties".

<p>Hello, {{ fullName }} !</p>
Enter fullscreen mode Exit fullscreen mode
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    };
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Computed properties are functions that capture the results of computation, and behave like properties in the template block.

React way

I think useMemo hook is React version of "computed property".

import React, { useMemo } from 'react';

function MyComponent() {
  const [firstName, setFirstName] = useState("John");
  const [lastName, setlastName] = useState("Doe");

  const fullName = useMemo(() => {
    return `${firstName} ${lastName}`;
  }, [firstName, lastName]);

  return <p>Hello, {fullName} !</p>;
}
Enter fullscreen mode Exit fullscreen mode

useMemo takes function as 1st argument and array as 2nd argument, and returns memoized value.

  • React's functional component is re-rendered everytime props or states are updated.
  • But a function of useMemo's 1st argument only be re-calculated when values in an array passed as 2nd argument are updated.
  • If the values in 2nd argument array are not updated, cached value will be returned.

This behavior is similar to Vue's computed property, but it's not so much common pattern as computed property. You should use useMemo only when there's a real need for optimization (I learned this from this post).

watch

Vue.js way

Vue.js offers you watchers -- "more generic way to react to data changes".

export default {
  watch: {
    name(valueAfterUpdate, valueBeforeUpdate) {
      // ...
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

React way

React has no equivalent for watchers.

You can implement something like that using useEffect hook. I will show you that hook in next section.

But I oftern think that there're not so many use-cases for the watch option, because most of the time, it can be replaced with on-change event.

Lifecycles

Vue.js way

Vue.js has many lifecycle hooks.

export default {
  created() {/* ... */},
  mounted() {/* ... */},
  updated() {/* ... */},
  destroyed() {/* ... */}
}
Enter fullscreen mode Exit fullscreen mode

React way

In React functional component, there is no concept of lifecycle. It's much simpler here.

  • functional component is rendered and re-rendered when its props or states are updated.
  • if you want to do something just after rendering, put that operation in the useEffect hook.
import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    someApi.getItems().then(response => {
      setItems(response.data);
    });
  }, []);
Enter fullscreen mode Exit fullscreen mode

useEffect behaves differently depending on what is passed as a 2nd argument.

// if there is no 2nd argument,
// 1st argument is called on every renders.
useEffect(() => {});

// if 2nd argument is an empty array,
// 1st argument is called only on first render.
useEffect(() => {}, []);
// this is like "mounted" in Vue.js

// if 2nd argument contains one or more items,
// 1st argument is called on first render and when the items are updated.
useEffect(() => {}, [aaa, bbb]);
// this is like "mounted" plus "updated" & "watch", I guess.
Enter fullscreen mode Exit fullscreen mode

useEffect's 1st argument can return "clean up" function, that is called just before its component is removed from the DOM.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // ...

    return () => {
      // clean up function.
      // this is like "destroyed" in Vue.
    };
  }, []);

  return <div>...</div>;
}
Enter fullscreen mode Exit fullscreen mode

On the other hand, class components have constructor and lifecycle methods that work just like Vue.js. I don't show you around in this article, but you can learn about these from this post.

Interaction between components

props

Vue.js way

props option is used for passing data from parent component to its child.

<Child :name="test" :item="sampleData" />
Enter fullscreen mode Exit fullscreen mode
<script>
function Item(one, two) {
  this.one = one
  this.two = two
}

export default {
  // validate its value with "type" and "required".
  props: {
    name: {
      type: String,
      required: false,
      default: 'John Doe'
    },
    item: {
      type: Item,
      required: true
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

React way

In React, properties / attributes passed from parent to children are also called props.

<Child name={test} item={sampleData} />
Enter fullscreen mode Exit fullscreen mode

1st argument of a functional component is the props.

import React from 'react';

function Child(props) {
  // props.name
  // props.item
}
Enter fullscreen mode Exit fullscreen mode

You can use prop-types library for validation.

import React from 'react';
import PropTypes from 'prop-types';

function Child(props) {
  // props.name
  // props.item
}

Child.propTypes = {
  name: PropTypes.string,
  item: PropTypes.shape({
    one: PropTypes.string.isRequired,
    two: PropTypes.number.isRequired
  }).isRequired
};

Child.defaultProps = {
  name: 'John Doe'
};

export default Child
Enter fullscreen mode Exit fullscreen mode

emitting events

Vue.js way

In Vue.js, child component notifies event with $emit method to its parent.

onSomethingHappened() {
  this.$emit('hello');
}
Enter fullscreen mode Exit fullscreen mode

Then, register a handler for a notified event with @ syntax.

<Child @hello="parentMethod" />
Enter fullscreen mode Exit fullscreen mode

React way

React has no syntax for event emitting. You just pass a handler function as a prop -- i.e. parent component determine what to do and children execute that.

function Child({ onHello }) {
  const handleClick = () => {
    console.log('hello there');
    onHello();
  };

  return <button onClick={handleClick}>click me</button>;
}
Enter fullscreen mode Exit fullscreen mode
function Parent() {
  const parentMethod = () => {/* blah blah... */};

  return <Child onHello={parentMethod} />;
}
Enter fullscreen mode Exit fullscreen mode

slot

Vue.js way

Vue.js has slot for inserting child elements.

<Content>
  <p>Hello world</p>
</Content>
Enter fullscreen mode Exit fullscreen mode

Content component be like:

<template>
  <article>
    <h1>This is a title.</h1>
    <slot></slot>
  </article>
</template>
Enter fullscreen mode Exit fullscreen mode

When you have more that one blocks to insert, you can name each of those.

<MyComponent>
  <template #header>
    <MyHeader />
  </template>
  <template #content>
    <MyContent />
  </template>
  <template #footer>
    <MyFooter />
  </template>
</MyComponent>
Enter fullscreen mode Exit fullscreen mode

React way

In React, children prop has inserted elements.

<Content>
  <p>Hello world</p>
</Content>
Enter fullscreen mode Exit fullscreen mode
function Content({ children }) {
  // children -> <p>Hello world</p>
  return (
    <article>
      <h1>This is a title.</h1>
      {children}
    </article>
  );
}
Enter fullscreen mode Exit fullscreen mode

You can neither have multiple children nor name it.

But children is just a prop. The example above is essentially same as below:

<Content children={<p>Hello world</p>} />
Enter fullscreen mode Exit fullscreen mode

So you can just do this for inserting multiple elements.

return (
  <MyComponent
    header={<MyHeader />}
    content={<MyContent />}
    footer={<MyFooter />}
  />
);
Enter fullscreen mode Exit fullscreen mode
function MyComponent({ header, content, footer }) {
  return (
    <div>
      <header>{header}</header>
      <main>{content}</main>
      <footer>{footer}</footer>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Wrapping up

Here is my impression:

  • React is much simpler than Vue.js, and, more rooms for you to improvise.
  • Vue.js has more APIs but is more easy to learn.

In that sense, I think Vue.js is well designed. I recommend Vue.js especially for new JS framework learners. It's also my first JS framework that I succeeded to learn (I failed with angular.js before that).

But, now I like React more. It's simple and enough.

Which one do you prefer?

So, that's all folks! Thanks for reading. Hope you enjoyed and this helps your learning!

Discussion

pic
Editor guide
Collapse
aarone4 profile image
Aaron Reese

For me, Vue covers most of the common application functions with a clean and simple API. V-for, v-on, v-if, v-model all make complete sense and the resulting html code is easy to read. It insulated new developments from some of the complexity of JavaScript: map and filter, object destructuring, asynchronous code execution. The one I love and hate at the same time is the hijack of this. Hoisting it so that it infers the data and methods object is mad-genious but doesn't help new devs to grasp the context sensitive nature of this and also prevents fat-arrow functions as they can't support the this override.

Collapse
thelinuxlich profile image
Alisson Cavalcante Agiani

You should try a new version of this article with the Vue.js 3 changes, you might be positively surprised about Vue again.

Collapse
smolinari profile image
Scott Molinari

Good article, but this needs to include Vue 3's newest features. And, I highly disagree that React is simpler than Vue. I'd also not call React's being more flexible, i.e. "room to improvise" a good thing. Stricter API's means "best practices" aren't needed as much, which means learning curves aren't as steep or rather cognitive load is less.

Scott