Masahiro Harada
Posted on October 24, 2020
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>
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;
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;
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>
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>
);
- 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 manipulatedisplay
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>
);
<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>
</>
);
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>
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>;
Although, classnames library will help you dealing with HTML classes.
import classNames from 'classnames';
It's just like v-bind:class
.
const buttonClass = classNames({
btn: true,
'btn-primary': isPrimary
});
return <button className={buttonClass}>...</button>;
HTML
Vue.js way
For injecting HTML string, you use v-html
in Vue.js.
<div v-html="htmlString"></div>
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 }} />;
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>
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>;
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>
Inside other parts of the component, you can reference its state value through this
.
methods: {
increment() {
this.count += 1;
}
}
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>;
}
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);
You can set object as state's value.
const [user, setUser] = useState({ name: "John Doe", age: 20 });
Forms
Vue.js way
In Vue.js, v-model
handles form inputs.
<input type="text" v-model="name" />
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" />;
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>
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>;
}
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>
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;
computed properties
Vue.js way
Vue.js has "computed properties".
<p>Hello, {{ fullName }} !</p>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
};
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
}
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>;
}
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) {
// ...
}
}
}
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() {/* ... */}
}
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);
});
}, []);
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.
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>;
}
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" />
<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>
React way
In React, properties / attributes passed from parent to children are also called props
.
<Child name={test} item={sampleData} />
1st argument of a functional component is the props.
import React from 'react';
function Child(props) {
// props.name
// props.item
}
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
emitting events
Vue.js way
In Vue.js, child component notifies event with $emit
method to its parent.
onSomethingHappened() {
this.$emit('hello');
}
Then, register a handler for a notified event with @
syntax.
<Child @hello="parentMethod" />
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>;
}
function Parent() {
const parentMethod = () => {/* blah blah... */};
return <Child onHello={parentMethod} />;
}
slot
Vue.js way
Vue.js has slot
for inserting child elements.
<Content>
<p>Hello world</p>
</Content>
Content
component be like:
<template>
<article>
<h1>This is a title.</h1>
<slot></slot>
</article>
</template>
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>
React way
In React, children
prop has inserted elements.
<Content>
<p>Hello world</p>
</Content>
function Content({ children }) {
// children -> <p>Hello world</p>
return (
<article>
<h1>This is a title.</h1>
{children}
</article>
);
}
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>} />
So you can just do this for inserting multiple elements.
return (
<MyComponent
header={<MyHeader />}
content={<MyContent />}
footer={<MyFooter />}
/>
);
function MyComponent({ header, content, footer }) {
return (
<div>
<header>{header}</header>
<main>{content}</main>
<footer>{footer}</footer>
</div>
)
}
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!
Posted on October 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.