🫠

Reactしか書けない人がVue.jsプロジェクトに突っ込まれた時にみる記事

2024/08/16に公開

前書き

普段はReact.jsやSolid.jsを使っており、で最後にVue.jsを触ったのはVue2時代です。Vue2触ったりNuxt2触ったりしました。
今回インターン先がVue.jsだったり、tsxの変換層の多さにちょっとだけ嫌気がさしてきたので逃げずに腹括ります。

前提条件

私のプロジェクトでは以下の三パターンの書き方が入り混じっていたので、それぞれを書いていきます。
ぜひ書く際ではなく読む際にもご活用ください。

  1. OptionsAPI : いろいろ型のシステムとかが使いづらい。非推奨。
  2. CompositionAPI ✖️ defineComponent: 古いCompositionAPIの書き方。非推奨。
  3. CompositionAPI ✖️ script setup: 24/8時点で最新の書き方。個人的にReactと似たような書き味になってると思ってる

どれもJS(<script>内)の書き方が変わったという感じで、template内は大体同じです。

参考

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と同じように渡される関数が引数を受け取ることを期待することもできる

emittest.vue
<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で囲う必要がある。

親コンポーネント.vue(CompositionAPI✖️DefineCompoents)
<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>
子コンポーネント.vue
<template>
  <div>
    <slot name="header"></slot> <!-- 名前付きスロット -->
    <slot></slot> <!-- デフォルトスロット -->
  </div>
</template>

<script>
export default {
  name: 'ChildComponent',
};
</script>

感想

VSCodeの補完にまぁまぁ裏切られる。(特にBootstrap系のやつ)

Discussion