Reactしか書けない人がVue.jsプロジェクトに突っ込まれた時にみる記事
前書き
普段はReact.jsやSolid.jsを使っており、で最後にVue.jsを触ったのはVue2時代です。Vue2触ったりNuxt2触ったりしました。
今回インターン先がVue.jsだったり、tsxの変換層の多さにちょっとだけ嫌気がさしてきたので逃げずに腹括ります。
前提条件
私のプロジェクトでは以下の三パターンの書き方が入り混じっていたので、それぞれを書いていきます。
ぜひ書く際ではなく読む際にもご活用ください。
- OptionsAPI : いろいろ型のシステムとかが使いづらい。非推奨。
- CompositionAPI ✖️ defineComponent: 古いCompositionAPIの書き方。非推奨。
- CompositionAPI ✖️ script setup: 24/8時点で最新の書き方。個人的にReactと似たような書き味になってると思ってる
どれもJS(<script>内)の書き方が変わったという感じで、template内は大体同じです。
参考
- Vue 3 for React developers: Side-by-side comparison with demos
- ライフサイクルフック
- [React]useEffectはコンポーネントがレンダリング後に副作用として動作
compositon APIとかいうやつ[CompositionAPI ✖️ defineComponent, CompositionAPI ✖️ script setup]
これはVue3系で新しくついかされたやつ。<script setup>
とかexport default defineComponent
とかあったらこれ。
Typescript対応的に優しいみたい。
script setupを使った方法は3.2から使えるようになったやつでこっちの方が新しいらしい。
useState[共通]
ref()とかreactive()を使う
{isShow??<Main/>}
[共通]
v-ifを使う
attribute={variable_name}[共通]
:attribute="variable_name"
htmlの流儀に合わせてケバブケースで書いてもVueがcamelCaseに変換してくれる。
つまり受け取り側のコンポーネントがfirstAttributeとpropsを定義していても、以下のように書くことができる。
:first-attribute="variable_name"
別にこのように書くこともできるが、VSCodeの補完で提案されるのは上記の書き方。
:firstAttribute="variable_name"
マウント(useEffect)[共通]
Reactでいう「描画」のこと。レンダーではない。
全てを描画し切った状態のことを指す。
VueのonMounted
はReactでいうuseEffect
もしuseEffectの第二引数に入れるみたいなことをする場合はwatchを使う
watchの第一引数にはref()かreactive()された値を入れる
import {OtherComponent} from "@/components/otherComponent"
CompositionAPI ✖️ defineComponent
全てをjsで書かない都合上外部のComponentをtemplateで使うには何か別の方法でimportする必要があります。その際Vue.jsではdefineComponentの第一引数.componentsに追加するみたいです。
<script lang="ts">
import OtherComponent from '@/compoents/OtherComponent'
export default defineComponent({
components: { OtherComponent }, // ここで宣言することによってtemplate内で使える
setup() {
// ...
},
})
</script>
CompositionAPI ✖️ script setup
scriptタグの中でimportするだけで使える
useRef[共通]
ref()を使う
ref=はそのまま文字列で指定しちゃう
<template>
<hoge ref="refを宣言した変数名"/>
<template/>
()=>({props}:Props)
compositionAPI ✖️ defineComponent
defineComponentの第一引数に渡す。
あくまでJSの型システムでなんとかする方法って感じで面白い。
Typescriptのtype aliasなどは使えないので、それを使い場合はscript setupを使った方法にする
defineComponent({
props: {
firstProps: {
type: String,
required: true,
},
ocrResultQualifiedInvoiceIssuerCodesMap: {
type: Object as OriginalClass,
required: true,
},
},
})
compositionAPI ✖️ script setup
type aliasとかtypescriptの型システムを使いたい場合こちら
defineProps<{
title?: string
likes?: number
}>()
デフォルト値を設定する場合はさらにwithDefaultでラップする
withDefaults(defineProps<{
title: string
likes?: number
}>(), {
title: "defaultTitle"
})
script内でpropsにアクセスしたい場合は返り値を取得しておく。template内で使う分にはなくても良い。その場合template内で直接プロパティ名を取得すれば良い
const props = defineProps<{
title?: string
likes?: number
}>()
// 以下のコードでアクセスできる
// props.title
onClick={()=>console.log("hello")}
<button @click="console.log("hello")"/>
vueでは以上のようにイベントの発火ができるし、一つだけなら処理をそのまま書くことができる(どういう仕組み???)
Emitについて
イメージとしてはReactでは全てPropsにしていたところを、VueだとPropとEmitに分ける感じ。
EmitはイベントといってもReactでonHogeにしてたやつの代わりに使うイメージで問題なさそう。
しかし今のところReactのようにPropsにコールバック変数を渡してもうまく動く。(使い分けはよくわからない)
(ReactユーザーとしてはPropsに渡したいが、たぶんVueユーザー的にはそういう使い方するのは良くないんだろうなぁと思ったり・・・)
Propsと同じように渡される関数が引数を受け取ることを期待することもできる
<script setup lang="ts">
const emit = defineEmits({
change: (id: number) => {
// バリデーションの合格/不合格を示すための
// `true` または `false` を返す
},
update: (value: string) => {
// バリデーションの合格/不合格を示すための
// `true` または `false` を返す
}
})
</script>
<template>
<div>
<button @click="emit('change',1)"></button>
<button @click="emit('update','updated!')"></button>
</div>
</template>
以上のコードではemitをつかってchangeイベントとupdateイベントを定義している。
発火: 渡したonChangeとかを呼ぶ
<template>
<div>
<button @click="emit('change',1)"></button>
<button @click="emit('update','updated!')"></button>
</div>
</template>
button内でそれぞれのイベントを発火している。ReactでいうPropsで渡されたコールバック変数を呼んでいるイメージ
useMemo()
computed()
children.props()
children.props()をVueでする場合は少しコード量が多くなるがその代わりVue.jsでは複数のコンポーンネントを子コンポーネントとして渡すことができる。
これは呼び出し側の話だが、子コンポーネントに無名スロットしかない時はそのまま渡せるが、そうじゃない場合はtemplateで囲う必要がある。
<template>
<div>
<h1>親コンポーネント</h1>
<ChildComponent>
<template #header>
<h2>カスタムヘッダー</h2> //ここで小コンポーネントのheaderに表示する内容を指定している
</template>
<template #default>
<p>これはデフォルトのコンテンツです。</p>
</template>
</ChildComponent>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
};
</script>
<template>
<div>
<slot name="header"></slot> <!-- 名前付きスロット -->
<slot></slot> <!-- デフォルトスロット -->
</div>
</template>
<script>
export default {
name: 'ChildComponent',
};
</script>
感想
VSCodeの補完にまぁまぁ裏切られる。(特にBootstrap系のやつ)
Discussion