
For the past three years, I’ve been using both React and Vue in different projects, ranging from smaller websites to large scale apps.
Last month I wrote a post about why I prefer React over Vue. Shortly after I joined Adam Wathan on Full Stack Radio to talk about React from a Vue developer’s perspective.
We covered a lot of ground on the podcast, but most things we talked about could benefit from some code snippets to illustrate their similaraties and differences.
This post is a succinct rundown of most Vue features, and how I would write them with React in 2019 with hooks.
Did I miss anything? Are there other comparisons you’d like to see? Or do you just want to share your thoughts about Vue or React? Talk to me on Twitter!
Table of contents
- Templates
- Props
- Data
- Computed properties
- Methods
- Events
- Lifecycle methods
- Watchers
- Slots & scoped slots
- Provide / inject
- Custom directives
- Transitions
Templates
React alternative: JSX
Vue uses HTML strings with some custom directives for templating. They recommend using .vue
files to seperate the templates and script (and optionally styles).
<!-- Greeter.vue -->
<template>
<p>Hello, {{ name }}!</p>
</template>
<script>
export default {
props: ['name']
};
</script>
React uses JSX, which is an extension of ECMAScript.
export default function Greeter({ name }) {
return <p>Hello, {name}!</p>;
}
CONDITIONAL RENDERING
React alternative: Logical &&
operator, ternary statements, or early returns
Vue uses v-if
, v-else
and v-else-if
directives to conditionally render parts of a template.
<!-- Awesome.vue -->
<template>
<article>
<h1 v-if="awesome">Vue is awesome!</h1>
</article>
</template>
<script>
export default {
props: ['awesome']
};
</script>
React doesn’t support directives, so you need to use the language to conditionally return parts of a template.
The &&
operator provides a succinct way to write an if
statement.
export default function Awesome({ awesome }) {
return (
<article>
{awesome && <h1>React is awesome!</h1>};
</article>
);
}
If you need an else
clause, use a ternary statement instead.
export default function Awesome({ awesome }) {
return (
<article>
{awesome ? (
<h1>React is awesome!</h1>
) : (
<h1>Oh no 😢</h1>
)};
</article>
}
You could also opt to keep the two branches completely separated, and use an early return instead.
export default function Awesome({ awesome }) {
if (!awesome) {
return (
<article>
<h1>Oh no 😢</h1>
</article>
);
}
return (
<article>
<h1>React is awesome!</h1>
</article>
);
}
LIST RENDERING
React alternative: Array.map
Vue uses the v-for
directive to loop over arrays and objects.
<!-- Recipe.vue -->
<template>
<ul>
<li v-for="(ingredient, index) in ingredients" :key="index">
{{ ingredient }}
</li>
</ul>
</template>
<script>
export default {
props: ['ingredients']
};
</script>
With React, you can “map” the array to a set of elements using the built in Array.map
function.
export default function Recipe({ ingredients }) {
return (
<ul>
{ingredients.map((ingredient, index) => (
<li key={index}>{ingredient}</li>
))}
</ul>
);
}
Iterating objects is a bit trickier. Vue allows you to use the same v-for
directive for keys & values.
<!-- KeyValueList.vue -->
<template>
<ul>
<li v-for="(value, key) in object" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
</template>
<script>
export default {
props: ['object'] // E.g. { a: 'Foo', b: 'Bar' }
};
</script>
I like to use the built in Object.entries
function with React to iterate over objects.
export default function KeyValueList({ object }) {
return (
<ul>
{Object.entries(object).map(([key, value]) => (
<li key={key}>{value}</li>
))}
</ul>
);
}
CLASS AND STYLE BINDINGS
React alternative: Manually pass props
Vue automatically binds class
and style
props to the outer HTML element of a component.
<!-- Post.vue -->
<template>
<article>
<h1>{{ title }}</h1>
</article>
</template>
<script>
export default {
props: ['title'],
};
</script>
<!--
<post
:title="About CSS"
class="margin-bottom"
style="color: red"
/>
-->
With React, you need to manually pass className
and style
props. Note that style
must be an object with React, strings are not supported.
export default function Post({ title, className, style }) {
return (
<article className={className} style={style}>
{title}
</article>
);
}
{/* <Post
title="About CSS"
className="margin-bottom"
style={{ color: 'red' }}
/> */}
If you want to pass down all remaining props, the object rest spread operator comes in handy.
export default function Post({ title, ...props }) {
return (
<article {...props}>
{title}
</article>
);
}
If you miss Vue’s excellent class
API, look into Jed Watson’s classnames library.
Props
React alternative: Props
Props behave pretty much the same way in React as Vue. One minor difference: React components won’t inherit unknown attributes.
<!-- Post.vue -->
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
props: ['title'],
};
</script>
export default function Post({ title }) {
return <h3>{title}</h3>;
}
Using expressions as props in Vue is possible with a :
prefix, which is an alias for the v-bind
directive. React uses curly braces for dynamic values.
<!-- Post.vue -->
<template>
<post-title :title="title" />
</template>
<script>
export default {
props: ['title'],
};
</script>
export default function Post({ title }) {
return <PostTitle title={title} />;
}
Data
React alternative: The useState
hook
In Vue the data
option is used to store local component state.
<!-- ButtonCounter.vue -->
<template>
<button @click="count++">
You clicked me {{ count }} times.
</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
}
};
</script>
React exposes a useState
hook which returns a two-element array containing the current state value and a setter function.
import { useState } from 'react';
export default function ButtonCounter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
You can choose whether you prefer to distribute state between multiple useState
calls, or keep it in a single object.
import { useState } from 'react';
export default function ProfileForm() {
const [name, setName] = useState('Sebastian');
const [email, setEmail] = useState('sebastian@spatie.be');
// ...
}
import { useState } from 'react';
export default function ProfileForm() {
const [values, setValues] = useState({
name: 'Sebastian',
email: 'sebastian@spatie.be'
});
// ...
}
V-MODEL
v-model
is a convenient Vue directive that combines passing down a value
prop with listening to an input
event. This makes it look like Vue does two-way binding, while it’s still just “props down, events up” under the hood.
<!-- Profile.vue -->
<template>
<input type="text" v-model="name" />
</template>
<script>
export default {
data() {
return {
name: 'Sebastian'
}
}
};
</script>
Vue expands the v-model
directive to the following:
<template>
<input
type="text"
:value="name"
@input="name = $event.target.value"
/>
</template>
React doesn’t have a v-model
equivalent. You always need to be explicit:
import { useState } from 'react';
export default function Profile() {
const [name, setName] = useState('Sebastian');
return (
<input
type="text"
value={name}
onChange={event => setName(event.target.name)}
/>
);
}
Computed properties
React alternative: Variables, optionally wrapped in useMemo
Vue has computed properties for two reasons: to avoid mixing logic and markup in templates, and to cache complex computations in a component instance.
Without computed properties:
<!-- ReversedMessage.vue -->
<template>
<p>{{ message.split('').reverse().join('') }}</p>
</template>
<script>
export default {
props: ['message']
};
</script>
export default function ReversedMessage({ message }) {
return <p>{message.split('').reverse().join('')}</p>;
}
With React, you can extract the computation from the template by assigning the result to a variable.
<!-- ReversedMessage.vue -->
<template>
<p>{{ reversedMessage }}</p>
</template>
<script>
export default {
props: ['message'],
computed: {
reversedMessage() {
return this.message.split('').reverse().join('');
}
}
};
</script>
export default function ReversedMessage({ message }) {
const reversedMessage = message.split('').reverse().join('');
return <p>{reversedMessage}</p>;
}
If performance is a concern, the computation can be wrapped in a useMemo
hook. useMemo
requires a callback that returns a computed result, and an array of dependencies.
In the following example, reversedMessage
will only be recomputed if the message
dependency changes.
import { useMemo } from 'react';
export default function ReversedMessage({ message }) {
const reversedMessage = useMemo(() => {
return message.split('').reverse().join('');
}, [message]);
return <p>{reversedMessage}</p>;
}
نظرات کاربران
0 نظر