0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

vue-composition-apiで計算機作ってみた。

Last updated at Posted at 2020-04-30

vue-composition-apiで計算機作ってみた。

触ってみました。vue-composition-api。
vue3系から組み込まれる新機能だそうで。
とりあえず使ってみます。

※今回、四則演算までは実装していません。

自己紹介

28歳から独学でプログラミングを始める。もちろん未経験。
約10ヶ月の学習期間をへて事業会社のIT部門に内定をいただく。
しかし、CTOが2ヶ月で退任しコードを書く仕事ができなくなり「このままじゃ技術が伸びない!」と思い、内定から3ヶ月ほどで再度転職活動を始める。
今年の2月から新しい会社に努めており、現在はvueを用いて自社サービスを開発しています。

ぎゅっと縮めると実務でコードを触ってるのは4ヶ月ほどなのでクソコードかもしれませんが、学習の備忘録として記事にしています。
何か間違ってる点やご指摘があれば是非教えてください。

結論

実際書いてみてこのような印象を受けました。
メリット

  • 処理ごとでfunctionをまとめられるので可読性上がりそう
  • 型推論が効くため安全

デメリット

  • 従来のvueの書き方とは違うので最初は戸惑う
  • 情報が少ないためベストプラクティスが分からない
  • tsを使うならもちろんtsの学習も必要

実際どんな感じになるの?

例えば今までだと

sample.vue
<template>
  <h1>hello vue</h1>
</template>

<script>
export default {
  data() {
    return {
      hoge: 'hoge',
      foo: 'foo',
    }
  },
  computed() {},
  created() {},
  mounted() {},
  methods: {
    handleHoge() {},
    handleFoo() {},
  },
}
</script>

今はコードが少ないのであまりメリットを感じませんが、こんな感じでdataでプロパティを定義して、methodsまで離れていたりして、コードを読む時行ったり来たりを繰り返して読まなければなりませんでした。

それがこんな感じでまとめられるようになります。

sample.vue
<template>
  <h1>hello vue</h1>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api'

function useHoge() {
  const hoge = ref('hoge')
  const handleHoge = () => {
    // なんらかの処理
    console.log(hoge.value)
  }
  return {
    hoge,
    handleHoge,
  }
}

function useFoo() {
  const foo = ref('foo')
  const handleFoo = () => {
    // なんらかの処理
    console.log(foo.value)
  }
  return {
    foo,
    handleFoo,
  }
}

export default defineComponent({
  setup() {
    const { hoge, handleHoge } = useHoge()
    const { foo, handleFoo } = useFoo()
    return {
      hoge,
      handleHoge,
      foo,
      handleFoo,
    }
  },
})
</script>

変数の定義とメソッドを同じ関数内にまとめることができるようになります。
もちろんcomputedなども使えます。

RFCのまさにこの画像のようになります。
62783026-810e6180-ba89-11e9-8774-e7771c8095d6.png

ざっくりcomposition-apiがどんなものかわかったら実際に計算機のコードをみてみます。

コード

今回は試しにatomic designで実装してみました。
ただ、本来どこでデータを持すべきなのかメソッドを定義するべきなのかということだったり、organismsとmoleculesの違いは?などいろいろ疑問点が出てきたため、これからも試していきたいと思います。

operator

コードの中で「どの二項演算子が入力されてるのか」で分岐させる場所があるためobjectにしておきました。
直接'+'や'÷'を書いて分岐させない方が良さそうな気がするので。

operator.ts
export const Operator: Record<string, string> = {
  plus: '+',
  minus: '-',
  multiplication: '×',
  division: '÷',
  equal: '=',
  allClear: 'AC'
}

pages

大元となるpagesです。
おそらく、コードを読み始める時は上位ファイルから読んでいくだろという推測で、**「pagesのファイルを見ればどんなデータが使われてるかが分かる」**という考えのもと計算機ボタンの配列を定義しました。
デザインはgoogleの計算機を参考にしました。
ボタンが数字なのか記号なのかで、styleを変えたいためtypeを定義し、atomsのclassバインディングでstyleを変更できるようにしました。

pagesCalc.vue
<template>
  <template-calc :operator="Operator" :number-unit-array="numberUnitArray" />
</template>

<script lang="ts">
import templateCalc from '@/components/templates/templateCalc.vue'
import { defineComponent, reactive } from '@vue/composition-api'
import { Operator } from '../../common/operator'
export default defineComponent({
  components: {
    templateCalc
  },
  setup() {
    const numberUnitArray: string[][] = reactive([
      [
        { value: '(', type: 'operator' },
        { value: ')', type: 'operator' },
        { value: '%', type: 'operator' },
        { value: 'AC', type: 'operator' }
      ],
      [
        { value: '7', type: 'number' },
        { value: '8', type: 'number' },
        { value: '9', type: 'number' },
        { value: '÷', type: 'operator' }
      ],
      [
        { value: '4', type: 'number' },
        { value: '5', type: 'number' },
        { value: '6', type: 'number' },
        { value: '×', type: 'operator' }
      ],
      [
        { value: '1', type: 'number' },
        { value: '2', type: 'number' },
        { value: '3', type: 'number' },
        { value: '-', type: 'operator' }
      ],
      [
        { value: '0', type: 'number' },
        { value: '.', type: 'number' },
        { value: '=', type: 'calculation' },
        { value: '+', type: 'operator' }
      ]
    ])

    return {
      Operator,
      numberUnitArray
    }
  }
})
</script>

templates

今のところpagesからorganismsの受け渡しのファイルとなっています。

templateCalc.vue
<template>
  <div class="calculator">
    <organisms-calc :operator="operator" :number-unit-array="numberUnitArray" />
  </div>
</template>

<script>
import organismsCalc from '@/components/organisms/organismsCalc'
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  components: {
    organismsCalc
  },
  props: {
    operator: {
      type: Object,
      required: true
    },
    numberUnitArray: {
      type: Array,
      required: true
    }
  }
})
</script>

<style lang="scss" scoped>
.calculator {
  width: 100%;
  max-width: 375px;
  padding: 10px;
  margin: auto;
}
</style>

organisms

ここでmainの処理をしています。
useAddInput・・・入力された値の処理
useShowHistory・・・計算した履歴(未実装)
という風に処理ごとでfunctionを分けられるので、今までのVueの書き方と違い行ったり来たりをしなくていいので可読性は上がりそうだなと思いました。

ただ、refとreactiveの使いどころがあまり分からず、とりあえず今はfunctionごとにstateを設定し、toRefsを用いてreturnするという実装方法を用いてみました。

organismsCalc.vue

<template>
  <div class="calculator_wrapper">
    <div class="title_wrapper">
      <molecules-title />
    </div>
    <div class="input_wrapper">
      <molecules-calc-display
        :input-value="inputValue"
        :handle-show-history="handleShowHistory"
      />
    </div>
    <div class="button_wrapper">
      <molecules-calc-button
        :number-unit-array="numberUnitArray"
        :handle-add-input="handleAddInput"
      />
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, Ref, toRefs } from '@vue/composition-api'
function useAddInput(operator: Record<string, string>) {
  const state = reactive<{ historyArray: string[]; inputValue: string }>({
    historyArray: [],
    inputValue: ''
  })
  const handleInputClear = (): void => {
    state.inputValue = ''
  }
  const handleInputBinomialOperator = (cmd: string): void => {
    state.inputValue += ' ' + cmd + ' '
  }
  const handleCalculation = (
    numArray: number[],
    operatorArray: string[],
    operator: Record<string, string>
  ): void => {
    operatorArray.map((element, index) => {
      if (index === 0) {
        switch (element) {
          case operator.plus:
            state.inputValue = String(numArray[index] + numArray[index + 1])
            break
          case operator.minus:
            state.inputValue = String(numArray[index] - numArray[index + 1])
            break
          case operator.multiplication:
            state.inputValue = String(numArray[index] * numArray[index + 1])
            break
          case operator.division:
            state.inputValue = String(numArray[index] / numArray[index + 1])
            break
        }
      } else {
        switch (element) {
          case operator.plus:
            state.inputValue = String(
              Number(state.inputValue) + numArray[index + 1]
            )
            break
          case operator.minus:
            state.inputValue = String(
              Number(state.inputValue) - numArray[index + 1]
            )
            break
          case operator.multiplication:
            state.inputValue = String(
              Number(state.inputValue) * numArray[index + 1]
            )
            break
          case operator.division:
            state.inputValue = String(
              Number(state.inputValue) / numArray[index + 1]
            )
            break
        }
      }
    })
  }
  const handleInputValueToArray = (): void => {
    const inputValueArray = state.inputValue.split(' ')
    const operatorArray: string[] = []
    const numArray: number[] = []
    inputValueArray.map(element => {
      if (
        element === operator.plus ||
        element === operator.minus ||
        element === operator.multiplication ||
        element === operator.division
      ) {
        operatorArray.push(element)
      } else {
        numArray.push(Number(element))
      }
    })
    handleCalculation(numArray, operatorArray, operator)
    state.historyArray.push(state.inputValue)
  }
  const handleAddInput = (cmd: string): void => {
    if (cmd === operator.allClear) {
      handleInputClear()
      return
    }
    if (
      cmd === operator.plus ||
      cmd === operator.minus ||
      cmd === operator.multiplication ||
      cmd === operator.division
    ) {
      handleInputBinomialOperator(cmd)
      return
    }
    if (cmd === operator.equal) {
      handleInputValueToArray()
      return
    }
    state.inputValue += cmd
  }

  return {
    ...toRefs(state),
    handleAddInput
  }
}

function useShowHistory(inputValue: Ref<string>) {
  const handleShowHistory = () => {
    console.log(inputValue)
  }

  return {
    handleShowHistory
  }
}
export default defineComponent({
  props: {
    operator: {
      type: Object,
      required: true
    },
    numberUnitArray: {
      type: Array,
      required: true
    }
  },
  setup(props) {
    const { operator } = props
    const { inputValue, handleAddInput } = useAddInput(operator)
    const { handleShowHistory } = useShowHistory(inputValue)

    return {
      inputValue,
      handleAddInput,

      handleShowHistory
    }
  }
})
</script>

<style lang="scss" scoped>
.input_wrapper {
  margin-bottom: 20px;
}
</style>

moleculesとatomsはpropsで受け取って表示させてるだけなので省きます。

感想

楽しかった。
すごい馬鹿みたいな感想ですが、新しいものを学ぶというものは手探りではありますが、楽しいものだなと思います。

個人的には早く使ってみたいなと思います。
実務でのvueファイルがファットになってきて可読性が非常に悪いので、composition-apiで処理ごとにまとめられれば少しはよくなりそうだな〜と思っています。

あとtsもしっかり学ぶ必要があるので、しっかキャッチアップしていこうと考えています。
読んでいただきありがとうございました。
何かご指摘があれば是非教えてください。

参照ページ

Composition API RFC

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?