前書き
色々あってReactを触る機会があったため、学習記録して記事にしてみる。
ざっくり感想
今までVueを使っていた側からするとReactについての感想はこんな感じ
- やりたいことは一緒だけど考え方が真逆
- JSX/TSXなのでテンプレート内にスクリプトを制限なく書けるのが楽
- stateの変更=コンポーネント関数の再実行なので、Vueよりも状態管理を意識しなくても良い
- propsやemitは全部関数の引数として宣言できるので型定義やデフォルト引数の扱いが楽
- コンポーネントの名前を変更する時、ファイル名と関数名の両方を変えないといけないのを忘れがち
- CSSが別ファイルなので煩雑に感じる
なんとなく「リアクティブを手軽に扱いたい」Vueと、「単純かつ堅牢なリアクティブの仕組みが欲しい」Reactといった具合になっているんじゃないかと思う。
それぞれの書き方の違い
ここからは具体的な話。
inputタグとpタグだけのシンプルなコンポーネントを作って比較してみる。
まずはVue。Compotisiton APIとsetup属性を使って書くとこのようになる。専用のファイル形式を用いてHTMLとJavascript、CSSを分離して書く。どちらかというとHTMLの中にJavascriptを書く感じ。
<script setup lang="ts">
import { ref } from "vue";
const text = ref("");
</script>
<template>
<div>
<div class="red">
<p>text:{{text}}</p>
</div>
<div>
<input type="text" v-model="text" />
</div>
</div>
</template>
<style scoped>
.red {
color: red;
}
</style>
一方のReactはこのようになる。JSX/TSXでテンプレートを書く事が特徴。Javascriptの中にテンプレートを書くような感じ。
import { useState } from 'react';
import './style.css';
export default function SampleInput() {
const [text, setText] = useState('');
return (
<div>
<div>
<p className="red">text:{text}</p>
</div>
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
</div>
);
}
Reactで使うJSX/TSXはあくまでJavascriptの拡張なので、Vueのようにエディタの拡張機能やプラグインに頼らなくても結構シンタックスや補完が効くし、型の扱いも楽。
VueとReactの機能を比較する。
v-model
そもそもv-modelは糖衣構文で、構成しているそれぞれの機能については同等の物がある。なのでv-modelと同じ挙動を再現することはどちらでも可能ということになる。
<template>
<div>
<input type="text" v-model="text" />
<!-- Vue3以降はこのように分解できる。 -->
<input type="text" :modelValue="text" @update:modelValue="text = $event" />
</div>
<template>
イベントの値に$を付けるところを除けばReactの書き方とあまり変わらない気がする。
refとreactive
useStateが近い。ただし値の変更=再実行という形のReactとは考え方が微妙に異なる。
// Vue
const text =ref<string>("")
// React
const [text ,setText] =useState<string>("")
Vueの場合はproxyオブジェクトを使っているため、スクリプト内では.valueプロパティから値を参照する必要がある。
computed
Reactの場合は普通に変数を宣言すればそれがcomputedとして機能する。
実質Reactコンポーネント自体が算出プロパティのようなものなのでは?という気がする。
// Vue
const text =ref<string>("")
const upperCaseText = computed(() => text.value.toUpperCase());
// React
const [text ,setText] =useState<string>("")
const upperCaseText = text.toUpperCase());
watch
useEffectが近い機能を持っている。
// Vue
const text = ref("");
watch(() => {
console.log(text.value);
});
// React
const [text, setText] = useState('');
useEffect(() => {
console.log('text:', text);
}, [text]);
似たような機能だが、リアクティブの仕組みが異なるので同じ使い方ができるわけではない。VueのWatchは明示しない限り初回実行時に呼ばれないが、useEffectは逆に関数の実行に呼び出される。
mounted
第2引数の配列が空のuseEffectを使う。関数実行時に1度だけ実行され、あとは依存関係がないため実行されない。という仕組み。
// Vue
onMounted(() => {
console.log("mounted");
});
// React
useEffect(() => {
console.log("mounted");
}, []);
nextTick
SSR時に確実にクライアント側で処理を走らせる時に使う。
この機能に直接相当するものはReactには無いらしい。
defineProps/defineEmits/withDefault
コンポーネント関数の引数内で全て宣言すれば良い。Vueよりも遥かに楽。
// Vue
withDefaults(defineProps<{ msg: string }>(), { msg: "default message" });
defineEmits<{(e:"update",v:string):void}>();
// React
type ComponentProps = {
msg: string;
onUpdated: (msg: string) => void;
};
function coponent(props: ComponentProps={msg:"default message",onUpdated:()=>{}}){...}