EY-Office ブログ

Vue3に入門してみた(TypeScriptを使ったReactとVue3の比較)

以前 Vueに入門してみた(React、Vueどちらを使うべきか)を書いた時点ではVue3はベータ版でしたが、Vue3 (One Piece) が正式リリースされたのでTypeScriptを使い前回のコードを書き直しながら学んでみました。

React、Vueどちらを使うべきか

ReactとVue

ReactとVueは、根本的な設計思想が違います。

  • Reactは統一性のあるシンプルで強力な(コンピュターサイエンス的な)技術を使い作られています
    • 例: JSX, 関数コンポーネント、Hooks
  • Vue.jsは利便性を重視し、従来のHTML、CSS、JSの文脈から大きくずれないように作られています

しかし、技術的には似たライブラリーです。

  • 宣言的なUI
  • 高速化にvirtual DOMを利用
  • コンポーネント指向
  • コンポーネントの状態をStateやdataで管理している
  • 親コンポーネントから子コンポーネントへにはプロパティー(props)を通して値が渡される

などなど共通点は多いですが、Vue3になりアプリケーションのコードもReactと近くなったと感じました。

サンプルコードの解説

注意:まだVue3の情報は少なく、Vueのエキスパートではない私の書いたVue3のコードは最適なコードではない可能性もあります(エラーや警告は出てはいません)。

アプリの画面イメージ

アプリの画面イメージ

トップレベル・コンポーネント

Vue3ではコンポーネントはdefineComponent関数に渡すオブジェクトで定義します。setup関数内に処理を書きますが、ReactのStateに相当するリアクティブなプロパティはreactive関数で定義するようになり、よりReactに似てきました。

遠目に見るとJSX<template>のみが違うようにしか見えません😀

  • React
import React, { useState } from 'react'
import JyankenBox, { Te } from './JyankenBox'
import ScoreBox, { ScoreType, Judgment } from './ScoreBox'

const Jyanken: React.FC = () => {
  const [scores, setScrores] = useState<ScoreType[]>([])

  const pon = (human: Te) => {
    const computer: Te = Math.floor(Math.random() * 3)
    const judgment: Judgment = (computer - human + 3) % 3
    const score = {human: human, computer: computer, judgment: judgment}
    setScrores([score, ...scores])
  }

  return (
    <>
      <h1>じゃんけん ポン!</h1>
      <JyankenBox actionPon={te => pon(te)} />
      <ScoreBox scores={scores} />
    </>
   )
}

export default Jyanken
  • Vue3
<template>
  <div>
    <h1>じゃんけん ポン!</h1>
    <JyankenBox v-bind:actionPon="pon"/>
    <ScoreBox v-bind:scores="state.scores"/>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive } from 'vue'
import JyankenBox, { ActonPonType, Te } from './JyankenBox.vue'
import ScoreBox, { ScoreType, Judgment } from './ScoreBox.vue'

type State = {
  scores: ScoreType[];
}

export default defineComponent({
  name: 'Jyanken',
  components: {
    JyankenBox, ScoreBox
  },
  setup () {
    const state = reactive<State>({ scores: [] })
    const pon: ActonPonType = (human: Te) => {
      const computer: Te = Math.floor(Math.random() * 3)
      const judgment: Judgment = (computer - human + 3) % 3
      const score = { human: human, computer: computer, judgment: judgment }
      state.scores = [score, ...state.scores]
    }
    return { state, pon }
  }
})
</script>

JyankenBoxコンポーネント

TypeScriptで書いたReactではパラメーター(props)の定義はTypeScriptの型に任せていますが、Vue3ではパラメーターはdefineComponentのprops:に定義しないといけないのが少しモヤモヤします。

しかし、遠目に見るとJSX<template>React.CSSProperties<style>のみが違うようにしか見えません😀

  • React
import React from 'react'

export enum Te { Guu = 0, Choki, Paa}

type JyankenBoxProps = {
  actionPon: (te: number) => void
}
const JyankenBox: React.FC<JyankenBoxProps> = ({actionPon}) => {
  const divStyle: React.CSSProperties = {margin: "0 20px"}
  const buttonStyle: React.CSSProperties = {margin: "0 10px",
     padding: "3px 10px", fontSize: 14}
  return (
    <div style={divStyle}>
      <button onClick={() => actionPon(Te.Guu)} style={buttonStyle}>グー</button>
      <button onClick={() => actionPon(Te.Choki)} style={buttonStyle}>チョキ</button>
      <button onClick={() => actionPon(Te.Paa)} style={buttonStyle}>パー</button>
    </div>
  )
}

export default JyankenBox
  • Vue3
<template>
  <div>
    <button v-on:click="actionPon(Te.Guu)">グー</button>
    <button v-on:click="actionPon(Te.Choki)">チョキ</button>
    <button v-on:click="actionPon(Te.Paa)">パー</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'

export enum Te { Guu = 0, Choki, Paa}
export type ActonPonType = (te: number) => void

export default defineComponent({
  props: {
    actionPon: Function as PropType<ActonPonType>
  },
  setup () {
    return { Te }
  }
})
</script>

<style scoped>
div {
  margin: 0 20px;
}
button {
  margin: 0 10px;
  padding: 3px 10px;
  font-size: 14px;
}
</style>

ScoreBoxコンポーネント

遠目に見るとJSX<template>React.CSSProperties<style>のみが違うようにしか見えません😀

(Vue3ではパラメーターはdefineComponentのprops:に定義しないといけないのが少しモヤモヤしますが・・・ )

  • React
import React from 'react'

export enum Judgment { Draw = 0, Win, Lose }
export type ScoreType = {
  human: number;
  computer: number;
  judgment: Judgment;
}

type ScoreListProps = {
  scores: ScoreType[]
}
const ScoreBox: React.FC<ScoreListProps> = ({scores}) => {
  const teString = ["グー","チョキ", "パー"]
  const judgmentString = ["引き分け","勝ち", "負け"]

  const tableStyle: React.CSSProperties = {marginTop: 20, borderCollapse: "collapse"}
  const thStyle: React.CSSProperties = {border: "solid 1px #888", padding: "3px 15px"}
  const tdStyle: React.CSSProperties = {border: "solid 1px #888", padding: "3px 15px",
      textAlign: "center"}
  return (
    <table style={tableStyle}>
      <thead>
        <tr>
          <th style={thStyle}>あなた</th>
          <th style={thStyle}>コンピュター</th>
          <th style={thStyle}>勝敗</th>
        </tr>
      </thead>
      <tbody>
        {scores.map((scrore, ix) =>
          <tr key={ix}>
            <td style={tdStyle}>{teString[scrore.human]}</td>
            <td style={tdStyle}>{teString[scrore.computer]}</td>
            <td style={tdStyle}>{judgmentString[scrore.judgment]}</td>
          </tr>
        )}
      </tbody>
    </table>
  )
}

export default ScoreBox
  • Vue3
<template>
  <table>
    <thead>
      <tr>
        <th>あなた</th>
        <th>コンピュター</th>
        <th>勝敗</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(scrore, index) in scores" :key="index">
        <td>{ { teString[scrore.human] } }</td>
        <td>{ { teString[scrore.computer] } }</td>
        <td>{ { judgmentString[scrore.judgment] } }</td>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts">
import { defineComponent, ref, PropType } from 'vue'

export enum Judgment { Draw = 0, Win, Lose }
export type ScoreType = {
  human: number;
  computer: number;
  judgment: Judgment;
}

export default defineComponent({
  props: {
    scores: Array as PropType<ScoreType[]>
  },
  setup () {
    const teString = ref<string[]>(['グー', 'チョキ', 'パー'])
    const judgmentString = ref<string[]>(['引き分け', '勝ち', '負け'])
    return { teString, judgmentString }
  }
})
</script>

<style scoped>
table {
  margin-top: 20px;
  border-collapse: collapse;
}
td, th {
  border: solid 1px #888;
  padding: 3px 15px;
  text-align: center;
}
</style>

まとめ

Vue3になり、ReactとVueはより近くなった気がします。TypeScriptも使え、VSCodeの補完が<template>の中でも使えるようになったのはありがたいです。

Reactを書ける開発者がVue3を書く、またVue3を書ける開発者がReactを書くのが簡単になり、iOS/Andriodのようにお互いに影響を与えながら発展していくのではないでしょうか。

ただし、私はReactが好きです 😀

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ