📝
フロント開発初心者がVueとReactのコードを比較してみた
この記事について
- Reactのフロント開発を1年程度経験した人間が学習目的でVueに触れて、Reactとどのくらい違うのか気になったので検証してみました
- 技術的な深い部分には触れず、単純なコード量や書きやすさなどを比較しています
- 自分と同じく、フロントをこれから触れる人へ参考程度になれば幸いです
使用したVueとReactのバージョンはこちら
Vue 3.2.13
React 18.2.0
比較したコード
- inputやbuttonのイベント処理、Apiの実行など基本的な機能が揃っているとよいかと思い、Vue学習用に進めていた公式チュートリアルのSTEP5〜STEP10の内容を、1つの画面にまとめVueとReactそれぞれで作成しました
作成した画面
Vueのコード
CompositionApiで作成しています
/Sample.vue
<script setup>
import { computed, ref, onMounted, watch } from "vue";
const text = ref("");
const awesome = ref(true);
let id = 0;
const newTodo = ref("");
const todos = ref([
{ id: id++, text: "Learn HTML", done: true },
{ id: id++, text: "Learn JavaScript", done: true },
{ id: id++, text: "Learn Vue", done: false },
]);
const hideCompleted = ref(false);
const filteredTodos = computed(() =>
hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value
);
const pElementRef = ref(null);
const todoId = ref(1);
const todoData = ref(null);
function onInput(e) {
text.value = e.target.value;
}
function toggle() {
awesome.value = !awesome.value;
}
function addTodo() {
todos.value.push({ id: id++, text: newTodo.value, done: false });
newTodo.value = "";
}
function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo);
}
onMounted(() => {
pElementRef.value.textContent = "mounted!";
});
async function fetchData() {
todoData.value = null;
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
);
todoData.value = await res.json();
}
fetchData();
watch(todoId, fetchData);
</script>
<template>
<h1>Sample</h1>
<input :value="text" @input="onInput" placeholder="Type here" />
<p>ここに表示:{{ text }}</p>
<br />
<button @click="toggle">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<br />
<form @submit.prevent="addTodo">
<input v-model="newTodo" />
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? "Show all" : "Hide completed" }}
</button>
<br />
<p ref="pElementRef">hello</p>
<br />
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>
Reactのコード
/Sample.jsx
import {useEffect, useRef, useState} from 'react';
export default function Index() {
const [text, setText] = useState('');
const [awesome, setAwesome] = useState(true);
let id = 0;
let defaultTodos = [
{id: id++, text: 'Learn HTML', done: true,},
{id: id++, text: 'Learn JavaScript', done: true,},
{id: id++, text: 'Learn React', done: false,}
];
const [newTodo, setNewTodo] = useState('');
const [todos, setTodos] = useState(defaultTodos);
const [hideCompleted, setHideCompleted] = useState(false);
const pElementRef = useRef(null);
const [todoId, setTodoId] = useState(1);
const [todoData, setTodoData] = useState(null);
const handleChangeText = (e) => {
setText(e.target.value);
}
const handleClickToggle = () => {
setAwesome(!awesome);
}
const handleOnClickAddTodo = () => {
setTodos([...todos, {id: id++, text: newTodo, done: false,}]);
setNewTodo('');
}
const handleOnChangeDeleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
}
const handleOnChangeCheckTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? {...todo, done: !todo.done} : todo
));
}
const filteredTodos = () => {
return hideCompleted ? todos.filter(todo => !todo.done) : todos;
}
useEffect(() => {
pElementRef.current.textContent = 'mounted!';
}, []);
const fetchTodo = async () => {
setTodoData(null);
try {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId}`);
const data = await res.json();
setTodoData(data);
} catch (error) {
console.error(`error: ${error}`);
}
}
useEffect(() => {
fetchTodo();
}, [todoId]);
return (
<div>
<h1>Sample</h1>
<input type={"text"} placeholder={'Type here'} onChange={handleChangeText}/>
<p>ここに表示:{text}</p>
<br />
<button onClick={handleClickToggle}>toggle</button>
{awesome ? (<h1>React is awesome!</h1>) : <h1>Oh no 😢</h1>}
<br />
<input type={"text"} value={newTodo} onChange={(e) => setNewTodo(e.target.value)} />
<button onClick={handleOnClickAddTodo}>Add Todo</button>
<ul>
{
filteredTodos().map((todo) => {
return (
<li key={todo.id}>
<input type="checkbox" checked={todo.done} onChange={() => handleOnChangeCheckTodo(todo.id)} />
<span>{todo.text}</span>
<button onClick={() => handleOnChangeDeleteTodo(todo.id)}>X</button>
</li>
);
})
}
</ul>
<button onClick={() => setHideCompleted(!hideCompleted)}>{hideCompleted ? 'Show all' : 'Hide completed'}</button>
<br />
<p ref={pElementRef}>hello</p>
<br />
<p>Todo id: {todoId}</p>
<button onClick={() => setTodoId(prevId => prevId + 1)}>Fetch next todo</button>
{!todoData ? (<p>Loading...</p>) : <pre>{JSON.stringify(todoData, null, 2)}</pre>}
</div>
);
}
コードで異なる部分を比較してみる
特に印象深く感じた部分をピックアップしてみました
constの定義
- Vueでは
const text = ref("")
のようにref
を使用し、text.value
で参照、更新します - Reactでは
const [text, setText] = useState("")
のようにuseState
を使用し、text
で参照、setText
で更新します
Vue
const text = ref("");
text.value = e.target.value;
React
const [text, setText] = useState('');
setText(e.target.value);
ライフサイクルフック(画面描画時に一度だけ実行)
- Vueでは
onMounted
を使用します - Reactでは
useEffect
を使用します
Vue
onMounted(() => {
pElementRef.value.textContent = "mounted!";
});
React
useEffect(() => {
pElementRef.current.textContent = 'mounted!';
}, []);
依存関係の状態変化
- Vueでは
computed
やwatch
を使用することで、依存関係の状態変化を検知し、自動的に再計算します - Reactでは、computedは同様の処理をfilteredTodos関数を独自に作成、watchはuseEffectを使用して同様に処理しています
Vue
const filteredTodos = computed(() =>
hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value
);
watch(todoId, fetchData);
React
const filteredTodos = () => {
return hideCompleted ? todos.filter(todo => !todo.done) : todos;
}
useEffect(() => {
fetchTodo();
}, [todoId]);
テンプレート内の記述
- Vueでは
v-if
やv-for
と独特の構文を使用します - Reactでは
JSX
構文を使用します
Vue
<template>
~省略~
<input :value="text" @input="onInput" placeholder="Type here" />
~省略~
<button @click="toggle">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
~省略~
<form @submit.prevent="addTodo">
<input v-model="newTodo" />
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
~省略~
</template>
React
return (
<div>
~省略~
<input type={"text"} placeholder={'Type here'} onChange={handleChangeText}/>
~省略~
<button onClick={handleClickToggle}>toggle</button>
{awesome ? (<h1>React is awesome!</h1>) : <h1>Oh no 😢</h1>}
~省略~
<input type={"text"} value={newTodo} onChange={(e) => setNewTodo(e.target.value)} />
<button onClick={handleOnClickAddTodo}>Add Todo</button>
<ul>
{
filteredTodos().map((todo) => {
return (
<li key={todo.id}>
<input type="checkbox" checked={todo.done} onChange={() => handleOnChangeCheckTodo(todo.id)} />
<span>{todo.text}</span>
<button onClick={() => handleOnChangeDeleteTodo(todo.id)}>X</button>
</li>
);
})
}
</ul>
~省略~
</div>
);
比較してみて
- Vueを書いているときは、Reactよりも書きやすくコード量も少なく書けている印象でしたが、結果にそこまで差はありませんでした
- 自分はReactを本格的に触ってまだ1年程度ですが、Vueの方が書きやすいと感じました
- 人生で一番最初に触れたのがHTML + smartyで、ReactよりはVueの書き方が近いように感じたので、同様の方ならVueの方がとっつきやすい印象になるかもしれません
- 逆にReactから入った方だと 、Vueの構文を新たに覚えるのが大変かもしれないです
VueでTypeScriptも触ってみた
- Reactと比べて、VueはTypeScriptのサポートがまだイマイチということだったので、試しにTypeScriptでも書いてみました
/Sample.vue
<script setup lang="ts">
import { computed, ref, onMounted, watch } from 'vue'
interface Todo {
id: number
text: string
done: boolean
}
const text = ref<string>('')
const awesome = ref<boolean>(true)
let id = 0
const newTodo = ref<string>('')
const todos = ref<Todo[]>([
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
])
const hideCompleted = ref<boolean>(false)
const filteredTodos = computed(() =>
hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value
)
const pElementRef = ref<HTMLElement | null>(null)
const todoId = ref<number>(1)
const todoData = ref<any>(null)
const onInput = (e: Event) => {
text.value = (e.target as HTMLInputElement).value
}
const toggle = () => {
awesome.value = !awesome.value
}
const addTodo = () => {
todos.value.push({ id: id++, text: newTodo.value, done: false })
newTodo.value = ''
}
const removeTodo = (todo: Todo) => {
todos.value = todos.value.filter((t) => t !== todo)
}
onMounted(() => {
if (pElementRef.value) {
pElementRef.value.textContent = 'mounted!'
}
})
const fetchData = async () => {
todoData.value = null
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
todoData.value = await res.json()
}
fetchData()
watch(todoId, fetchData)
</script>
<template>
<h1>Composition Api Sample</h1>
<input :value="text" @input="onInput" placeholder="Type here" />
<p>ここに表示:{{ text }}</p>
<br />
<button @click="toggle">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<br />
<form @submit.prevent="addTodo">
<input v-model="newTodo" />
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
<br />
<p ref="pElementRef">hello</p>
<br />
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>
- ざっと書いてみた感じでは、ReactでTypeScriptを書くのとあまり変わらない印象でした
感想
- かなり簡単なコードでの比較となるのであまりあてにはならないかもしれませんが、VueとReactで書き方に大きな違いはないと感じました
- ここにcssなどのデザイン周りのコードや特殊な機能を持つ画面を追加していくと、また違った印象になるかもしれません
- 個人的にはもっとVueに触れてみたいなという気持ちが湧きました
参考
Vue
Vue(TypeScript)
React
Discussion
長文失礼します。 React の部分について、参考になれば。
React に込められた設計の意図を理解するとかなり使いやすくなるので、良かったら公式 Docs を読んでみてください。(まだ Google 検索に公開されたばかりなので、検索に載っていませんが良記事ばかりです。)
Vue の computed に対応するのは「再レンダリングのたびに全て再計算される」デフォルト挙動
React のコンポーネントの内側に書いた式は、再レンダリングのたびにすべて再計算されます。(map 関数の中に渡した関数のようなものです。)
なので、Vue において computed を使っていた箇所は、全て「ただの式」に置き換え可能です。
(一応、再計算が重いときのために
useMemo
もあります。)コンポーネントの中に初期値を書かない
(再びですが) React のコンポーネントの内側に書いた式は、再レンダリングのたびにすべて再計算されます。なので、初期値を構築する式は、コンポーネント内に直に書かないほうが分かりやすくなります。
コンポーネントの外側か、useState の引数に入れることができます。
useState の引数に、初期値を生成する関数
createDefaultTodos
を渡すことも可能です。(初期化関数 / initializer function)依存配列で嘘をつかない
依存配列についてですが、これは「fetchTodo は todoId が変わるたびに実行したいので、todoId を依存配列に入れよう」と考えるのではなく、「useEffect の中の関数で使われている変数をすべて列挙する」ようなものです。(しかも、何も考えずに eslint exhaustive-deps ルールに任せさえすれば、勝手に「更新されなくて困る」ことが無くなります)
ありがとうございます!
参考にさせていただきます 🙏