33
12

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 3 years have passed since last update.

DMM WEBCAMPAdvent Calendar 2019

Day 16

【 初心者向け】ReactNativeで電卓アプリを作ろう【 チュートリアル】

Last updated at Posted at 2019-12-15

こんにちは!DMM WEBCAMPでメンターをしている人です!
アドベントカレンダー16日目は、(僕の中で)最近話題のReact Nativeを使って電卓を作ってみます。

チュートリアル形式でReact未経験の方でも分かるように専門用語を出来るだけ削って書いていきます。

是非トライしてみてください!

1.React Nativeとは?🤔

Facebookが開発した「React」というJavaScriptのフレームワークの亜種で、iOSやAndroidのアプリを作ることが出来る代物です。Reactは仮想DOMが実装されているので表示が早いというメリットがあります。

・jsなら任せて!
・Reactならわかるぞ!
・さくっとアプリ作りたい!
・スマホのアプリが作りたいけどSwift・Kotlinやる時間がない・・・
なんて方にオススメのアプリケーションフレームワークです。

さらに踏み込んだ解説が知りたい方はこちらの記事がオススメです。
「React Nativeとは何なのか」

2.今回やること

この記事では、四則演算が出来る電卓アプリを作っていきます。

ezgif.com-video-to-gif (2).gif

3.開発環境&構築

・Androidの開発を行う場合は、「Android studio」
・iOSの開発を行う場合は、「Xcode」が必要になります。

今回はiOSのアプリを作るのでAndroidの環境構築は省かせていただきます。ごめんなさい:rolling_eyes:
(Androidの開発が行いたい方はこちらの記事が分かりやすかったです!)

まずはnodeとwatchmanというものをインストールします。

$ brew install node
$ brew install watchman

次にexpoのダウンロードです。
nodeをインストールしたときにnpmが入っているのでnpmコマンドが使えます。

$ npm install expo-cli --g

Expoを深く知りたい方はこちらの記事がGoodです。

これで完了!といいたいところですがExpoはXcodeがないと開けないため、Xcodeのダウンロードをしましょう。
Xcodeで開いたiOSシュミレーター(PC上で開かれるiPhone)の中にExpoアプリがインストールされるのでXcode必須です。

Xcodeとは

XcodeはAppleが開発したソフトウェア開発者向けの主にMacやiPhone、iPadのアプリケーションを作るのに向いています。XcodeのおかげでMac上でiOSのシュミレーターが起動できます。

ダウンロードしたことがない方はApple Storeからダウンロードしてください!

ただ、めちゃくちゃサイズがデカいのでインストールに時間がかかります。

待ち時間にReactNativeの公式ドキュメント でも読んで時間を潰しちゃいましょう。

ここまできたら準備完了です!

プロジェクトの新規作成

以下のコマンドを実行してReactNativeアプリを新規作成しましょう!

$ expo init お好きな名前

プロジェクトのテンプレート選択

expo init ○○○後に、画像のようにテンプレートをどうするか聞かれますがここは「blank」を指定しましょう。
blankのスクリーンショット

名前の設定をしよう

スクリーンショット 2019-12-11 12.09.42.png テンプレート選択した後はこのような画面になると思います。

ここでは実際のアプリ名を設定します。(アプリアイコンの下に表示される名前)

リリースを考えているアプリの場合変な名前にしてはいけません!!!

Yarnは使わなくてOK!

Yarn v1.19.1 found. Use Yarn to install dependencies? (Y/n) 

このような質問がされる場合があります。
これは「Yarnが見つかったけどYarnは使う?どうする?」と聞かれています。
今回はnpmを使用しているので「n」を押してEnterです。

最終行にこのような記述があればアプリの作成完了です!

:(省略)
:
Your project is ready at 〜〜略〜〜〜
To get started, you can type:

  cd アプリ名
  npm start

npm start を実行してこの画面が表示されれば下ごしらえも完了です!
start.jpg

Railsに比べてシックで落ち着いたスタート画面ですね、おしゃれ。

補足:自分のスマホで開いてみよう

なんと自分の携帯にも開発しているアプリを開くこともできます。

まず。ご自身のスマホにExpoのアプリ(AppleStoreもしくはGooglePlayにて「Expo」で検索です!)をインストールします。

次に、アプリを読み取るQRコードをための作業をPCでします。

アプリが立ち上がった後のターミナルで

Press ? to show a list of all available commands

と出たら、「?」を入力します。次に「d」を押すとブラウザでデベロッパツールが開かれます。

デベロッパーツールにあるQRコードを読み取ると作っているアプリのURLが出るのでタッチしましょう!これでExpoアプリが開かれます!!

4.実装!

まず初めに、開発するアプリケーションの階層から見ていきましょう。

.expo
 ├ packager-info.json
 ├ setting.json
.expo-shared
 ├ assets.json
.assets
 ├ icon.png
 ├ splash.png
.node_module
 ├ 色々入ってます
.gitignore
App.js ← 主にこれを使います
babel.config.js
package-lock.json
package.json
style.js ← こちらを新規作成します

5.アプリ作成後に書かれている記述の意味は?

アプリの新規作成後にデフォルトで開かれるのは「App.js」というファイルです。そこにいろいろな記述を書いていき、最終的にはかっこいい電卓を作ります。

では、App.jsに最初から書かれている記述を見ていきましょう。

App.js
import React from 'react'; // ①
import { StyleSheet, Text, View } from 'react-native';  // ②

export default function App() {  // ⑤
  return (
    <View style={styles.container}> // ③
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
};

const styles = StyleSheet.create({ // ④
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

importってなに?

①と②はReact Nativeを使って開発するときのおまじないのようなものです。

①は、"react"モジュールからimport(=輸入する)して、Reactの記述を使用できるようにしています。

アプリケーションの中にnode-moduleというフォルダがあり、更にその中のreactというフォルダからimportしてきて色々読み込んでいます。

②は、"react-native"モジュールからimportして、StyleSheetやTextクラスを使えるようにしています。

こちらもnode-moduleの中のreact-nativeフォルダからimportしてきています。

**「なんで全部importしておかないの?」**と思った方いませんか?僕は思いました。

実は、全ファイルやコンポーネントをimportするより、各々が使いたいファイルやコンポーネントを選んでimportする方が読み込み速度が上がるらしいです。言われてみれば納得です:writing_hand_tone1:

Reactの描画速度が早いと言われる理由はこんなところにもあるんです!

コンポーネントについて

React Nativeでは描画するために独自のコンポーネントを使用する必要があります。
理由としては、divやspanタグが使えないので、Viewなどのコンポーネントを使う必要があるというわけです。
(この概念が難しいと言うか、コードを理解不能にしている要因の1つですが、慣れると余裕です:beer:

現状のコードの中だと、VIewTextStyleSheetがReact Nativeのコンポーネントです。

importせずにこれらのコンポーネントを使用するとエラーを吐いてしまいます。

だから自分が使用したいコンポーネントに合わせて②に追記していく感じです。

例えば入力フォームを作成したい場合は、以下のように追記します。

App.js
import React from 'react'; 
import { StyleSheet, Text, View, TextInput } from 'react-native';  //←追加!!!

export default function App() {  
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
        <TextInput style={styles.input} />  //←追加!!! 
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  input: {     //←追加!!!
    width: 100,
    borderColor: "gray",
    borderWidth: 1,
  },
});

** <TextInput /> ** <TextInput> ~~ </TextInput>の省略系です、便利!

コンポーネントはたくさん種類があり公式ドキュメントにたくさん載っています。

StyleSheet.createってなに?

続いて、③と④についてです。この2つはスタイル(見た目)を担っている部分です

何をしているかというとStyleSheetというコンポーネントを作成し、その中にスタイルの記述をたくさん書いています。

では、文字を赤くさせたい!なんて時はどうすればよいでしょうか。

3ステップです。

  1. stylesの中に、新しく「redColor」を定義します。(ここの命名は自由です)
    2.「redColor」を反映させたいコンポーネントに**style={styles.redColor}**を足します
  2. 完成
App.js
import React from 'react'; 
import { StyleSheet, Text, View, TextInput } from 'react-native'; 

export default function App() {  
  return (
    <View style={styles.container}>
      <Text style={styles.redColor}>Open up App.js to start working on your app!</Text>   //← 追記!!
        <TextInput style={styles.input} /> 
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  input: {     
    width: 100,
    borderColor: "gray",
    borderWidth: 1,
  },
   redColor: {        //← 追記!!!
    color: "red" 
  },
});

いちいちこんなことするの面倒臭い!って方には、コンポーネントに直接スタイルを書くのがオススメです。こんな風に直書きができます。

   <TextInput
     style={{width:100, borderColor: 'gray', borderWidth: 1 }} 
   />

ハマりポイント スタイルはキャメルケースで!

注意点としてはReact Nativeではプロパティーの部分はキャメルケースのみ使えます。

キャメルケースとは、**「backgroundColor」**のような、単語と単語の間にスペースや"-"を置かずに、文字の始まりを大文字にする形式です。
(ラクダのこぶに似てることからキャメルケースと呼ばれます。他にもスネークケース、ケバブケースがあります)

普段**「background-color」としていたところを「backgroungColor」**としなければなりません。

ようやくコンポーネントが描画される!


export default function App() {  // ⑤
  return (
    <View style={styles.container}> 
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
};

ラストです。
ここではimportしたViewとTextコンポーネントを使用しています。

HTML/CSS経験者であればこの書き方に違和感を感じますよね。

意訳ですが、HTML/CSSに変換して記述してみるとこんな感じです。
コンポーネントの使い方とイメージがつかめば全然怖くありません。

意訳.html
<div style="省略">
  <p>Open up App.js to start working on your app!</p>
</div>

続いて、以下の部分です。

export default function App() {  // ⑤

};

exportとは先ほど出てきたimportの反対の意味です。

export内で定義した記述を他の場所で使えるようにするものです。

例えばsample.jsを新規作成し、そこにexport~~の記述をしたコンポーネントを2つ書きます。

これをApp.jsで受け取りたいとしましょう。

sample.js

import React, {Component} from 'react';
import {Text, View} from 'react-native';

export class Sample1 extends Component {
    render(){
        return(
            <View>
              <Text>サンプルの1です</Text>
            </View>
        );
    };
};

export class Sample2 extends Component {
    render(){
        return(
            <View>
              <Text>サンプルの2です</Text>
            </View>
        );
    };
};
App.js

import React from 'react'; 
import { StyleSheet,  View,  } from 'react-native';
import {Sample1 , Sample2} from "./sample"; //←ここでimportしています


export default function App() {  
  return (
    <View style={styles.container}>
      <Sample1 />  //←ここでimportしたSample1を
      <Sample2 />  //←ここでimportしたSample2を使っています
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

defaultオプション付きのexportは、そのファイルの中においてデフォルトでexportされるコンポーネントを決めることができます。default付きexportはimportする時の名前はなんでもよく、ついていないものはclass名をimportする側で同じにしなければなりません。(面倒くさい)

例えば先ほどのSample1クラスにdefaultを設定します

sample.js

export default class Sample1 extends Component {
//略

次にApp.jsでimportします。

App.js
import hogehoge ,{Sample2} from "./sample"; //←import名を適当にしてます

//略

    <View style={styles.container}>
      <hogehoge />  //←ここでimportしたhogehogeを使える!
      <Sample2 />  
    </View>

defaultが付いていないものは**{ }**で囲う必要があるので要注意です!

export defaultについて詳しくはこちらです。

#6.スタイルを適応させよう

次にレイアウトを整えるためにスタイル(≒CSS)を作っています。

ここはそれほど重要ではないので、何をしているか理解したらコピペしちゃって大丈夫です。

また、App.jsにスタイルの記述をしてもいいのですが個人的に見辛いのでファイルを分けました。

2ステップです。

①App.jsと同じ階層にstyle.jsを新規作成する

②以下のコードを貼り付ける

style.js

import { StyleSheet } from "react-native";

const styles = StyleSheet.create({
    container: {
      backgroundColor: '#FFF',
    },
    countArea: {
      marginTop: 140,
      justifyContent: "center",
      alignItems: "center", 
      marginBottom:10
    },
    buttonArea: {
      marginTop: 5,
      justifyContent: 'center',
      alignItems: 'center'  
    },
    value: {
      fontSize: 30
    },
    topbutton: {
      marginTop: 50
    },
    button: {
      marginTop: 5,
      justifyContent: 'center',
      alignItems: 'center',
      width: 50,
      height: 40,
      borderRadius: 5,
      borderWidth: 1,
      borderColor: '#fff'
    },
    buttonText: {
      fontSize: 25,
      color: '#fff'
    },
    buttonUp: {
      backgroundColor: '#A53434',
      fontWeight: "bold"
    },
    buttonUpText: {
      backgroundColor: 'red'
    },
    buttonMinus: {
      backgroundColor: '#343EA5'
    },
    buttonTimes: {
      backgroundColor: 'purple'
    },
    buttonDivide: {
      backgroundColor: "green"
    },
    buttonReset: {
      borderColor: '#AFADAD',
      marginBottom: 200
    },
    buttonResetText: {
      color: '#000',
      fontSize:11
    },
    buttonNum: {
      backgroundColor: 'grey',
      fontWeight: "bold",
      justifyContent: 'center',
      alignItems: 'center',
      flexDirection: 'row',
      width: 60,
      height: 40,
      borderRadius: 5,
      borderWidth: 1,
      borderColor: '#fff'
    },
    buttonEqual: {
      backgroundColor: "black"
    }
  });

  export default (styles);

"react-native”から”StyleSheet”コンポーネントをimportしています。

こうすることによって

3行目でStyleSheet.createが使えるようになります。

この記述をすることによって、このコンポーネントはスタイルの記述をしますよ〜!と明示することが出来ます。
(StyleSheet.createしなくても動作はしますが、一応書いたほうがいいです。詳しくはこちら の記事です。)

そして、最終行では、このstyle.jsがどこでも使えるようにexportしています。

これをApp.jsでimportすればスタイルの土台は完成ですね。

#7.電卓のレイアウトを作ろう

スタイルを作成したので、次はそれを適応させるためのコンポーネントを作っていきます。
App.jsに書いている記述は1度全て消してから以下のコードを書いていきましょう!ここからスタートです!!

まずは先ほどのstyle.jsをApp.jsでimportします。

ついでに、この後使う「TouchableOpactity」というコンポーネントもimportしておきます。

App.js
import React, {Component} from 'react';
import { Text, View, TouchableOpacity } from 'react-native'; //←StyleSheetとInputTextがある方は削除でOK!
import styles from "./style"  //←追記!

次にコンポーネントの作成を行っていきます。

App.js
//↓import ~~~の下に↓

export default class App extends Component {
    render() {
      return (
        <View>
          <View style={styles.countArea}>
            <Text style={styles.value}>0  ?  0  =  0</Text>
          </View>
        </View>
      );
    };
};

 画像のようなものが画面に写ればOKです!
スクリーンショット 2019-11-29 15.38.39.png

計算用ボタンの設置

次に電卓のボタンを作っていきましょう。

先ほどimportした「TouchableOpacity」がここで出番です。

TouchableOpacityとは、簡単に言うとクリックできる要素のことです。
これを用いることでボタンが簡単に作れます。

App.js
//↓import ~~~の下に↓

export default class App extends Component {
    render() {
      return (
        <View>
          <View style={styles.countArea}>
            <Text style={styles.value}>0  ?  0  =  0</Text>
          </View>

          <View style={styles.buttonArea}>
            <View style={{ flexDirection: 'row'}}>
               <TouchableOpacity style={[styles.button, styles.buttonUpText]}>
                 <Text style={styles.buttonText}>+</Text>
               </TouchableOpacity>
            </View>
           </View>
        </View>
      );
    };
};

画像のようなボタンが表示されたでしょうか?

スクリーンショット 2019-11-29 16.26.34.png

あとは、数字のボタンや四則演算のボタンを設置していきます!

一気に書いていきましょ〜!

App.js
export default class App extends Component {
    render() {
      return (
        <View>
          <View style={styles.countArea}>
            <Text style={styles.value}>0  ?  0  =  0</Text>
          </View>

          <View style={styles.buttonArea}>
//足し算ボタン
            <View style={{ flexDirection: 'row'}}>
               <TouchableOpacity style={[styles.button, styles.buttonUpText]}>
                 <Text style={styles.buttonText}>+</Text>
               </TouchableOpacity>
//引き算ボタン
               <TouchableOpacity style={[styles.button, styles.buttonMinus]} >
                 <Text style={styles.buttonText}>-</Text>
               </TouchableOpacity>
//掛け算ボタン
               <TouchableOpacity style={[styles.button, styles.buttonTimes]} >
                 <Text style={styles.buttonText}>x</Text>
               </TouchableOpacity>
//割り算ボタン
               <TouchableOpacity style={[styles.button, styles.buttonDivide]} >
                 <Text style={styles.buttonText}>÷</Text>
               </TouchableOpacity>
//=ボタン
               <TouchableOpacity style={[styles.button, styles.buttonEqual]} >
                 <Text style={styles.buttonText}>=</Text>
               </TouchableOpacity>
//リセットボタン
               <TouchableOpacity style={[styles.button, styles.buttonReset]} >
                 <Text style={[styles.buttonResetText]}>リセット</Text>
               </TouchableOpacity>
             </View>
           </View>
        </View>
      );
    };
};
スクリーンショット 2019-11-30 16.40.46.png 画像のようにボタンが6つ表示さえれば計算ボタンは完成です。

数字ボタンの設置

次は0〜9の数字ボタンを作っていきます。

先ほどの計算ボタンはボタンごとに色や文言が変わるので個別に作成しました。

しかし、0〜9の数字ボタンは値やボタンの文字が変わるだけです。なので繰り返し処理を使って簡潔に記述しましょう!

まずは、以下のように配列を定義してあげます。

App.js
//〜〜〜略
exporet ~~
  render() {
    return (
      <View>
      //略
      </View>
    );
  };
};
//↓ここの下に記述

const ZeroToFour = [];
for (let i = 0; i< 5; i++) {
  ZeroToFour[i] = i
};

const FiveToNine = [];
for (let i = 5; i< 10; i++) {
  FiveToNine[i] = i
};

まずは繰り返し処理に使う配列を2つ用意します。ZeroToFourは0〜4、FiveToNineは5〜9のボタンを作ります。
(レイアウト調整のためここは敢えて配列2つ使っています、10個まとめて作るとレイアウトが綺麗に整えられませんでした。甘えです、ごめんなさい)

App.js
//略
export default class App extends Component {
    render() {
      return (
        <View>
          <View style={styles.countArea}>
            <Text style={styles.value}>0  ?  0  =  0</Text>
          </View>

          <View style={styles.buttonArea}>

//↓追加↓
            <View style={{flexDirection: 'row'}}>
              {ZeroToFour.map(i => (
                  <TouchableOpacity style={styles.topbutton,styles.buttonNum} key={i} >
                    <Text style={styles.buttonText}> {i} </Text>
                  </TouchableOpacity>
              ))};
            </View> 

            <View style={{ flexDirection: 'row'}}>
              {FiveToNine.map(i => (
                  <TouchableOpacity style={styles.topbutton, styles.buttonNum} key={i}>
                    <Text style={styles.buttonText}> {i} </Text>
                  </TouchableOpacity>
              ))};
            </View>

//ここより上を追加
//足し算ボタン
            //<View style={{ flexDirection: 'row'}}>
              //<TouchableOpacity style={[styles.button, styles.buttonUpText]}>
                 //<Text style={styles.buttonText}>+</Text>
               //</TouchableOpacity>

少し複雑な記述です。

ここはZeroToFourとFiveToNineの値を繰り返し処理(map)で出力させています。

実際は見本のように描画されていますが10個分の記述を書くとなると非常に長いので繰り返し処理を用いてボタンを作っています。

styleの**flexDirection: "row"**は、要素を横並びにするスタイルです。
このおかげでかっこいい電卓のレイアウトが出来ましたね。

見本

        <View style={{flexDirection: 'row'}}>
              <TouchableOpacity style={styles.topbutton,styles.buttonNum}>
                <Text style={styles.buttonText}> 0 </Text>
              </TouchableOpacity>
                           .
                           . (1~8省略)
                           .
              <TouchableOpacity style={styles.topbutton,styles.buttonNum}>
                <Text style={styles.buttonText}> 9 </Text>
              </TouchableOpacity>
        </View>
完成版ボタンのスクショ

ようやくここまででレイアウトが整いました。お疲れ様です:pensive::relaxed:

後半では実際にデータをやりとりしたり計算する処理を作っていきます。

前半戦の完全版コードをこちらに置いておきます。うまくいかない方は確認してみましょう!

後編はこちらです!!!

#8.数字ボタンを押してその値を描画しよう!
レイアウトが完成したので次は機能を作っていきます。
複雑な部分が多いので頑張っていきましょう:angel:

まずは、数字ボタンが押されたらその数字が描画されるところまでやっていきましょう。

①「ボタンが押されたら値(= 押したボタンの数字)が送信される」という記述を作る

②送信された値を受け取る

③その値を描画する

という流れで作っていきます。

ezgif.com-video-to-gif.gif

①「ボタンが押されたら値が送信される」という記述を作る

早速作っていきましょう。ボタンを作っているコードに追加していきます。

App.js

        <View style={styles.buttonArea}>
          <View style={{flexDirection: 'row'}}>
            {ZeroToFour.map(i => (
                <TouchableOpacity style={styles.topbutton,styles.buttonNum} key={i} onPress={() => this.onNum({i})}>
                  <Text style={styles.buttonText}> {i} </Text>
                </TouchableOpacity>
            ))}
          </View> 

          <View style={{ flexDirection: 'row'}}>
            {FiveToNine.map(i => (
                <TouchableOpacity style={styles.topbutton, styles.buttonNum} key={i} onPress={() => this.onNum({i})} >
                  <Text style={styles.buttonText}> {i} </Text>
                </TouchableOpacity>
            ))}
          </View>

**onPress={() => this.onNum({i})}**という記述を追加しています。

onPressはjsやJQueryの「click関数」とほぼ同じ意味です。

{i}には繰り返し処理中のindexが入るので、0〜9の値が入りますよね。
これを利用して押されたボタンの数字を取得して送信しているという仕組みです。

②送信された値を受け取る

値の送信は完了したので、その値を処理して受け取る作業をしていきます。

①に追記したonNumは未定義のものなので、定義してあげましょう。

App.js


//import 略

export default class App extends Component {
//ここから
  constructor(props){
    super(props);
    this.state = {
      leftNum: 0
    }
  }
  onNum(input){
    console.log(input); //←デバック用
    this.setState({ leftNum:  input["i"] })
  } 
//ここまで


//render(){
  //return(

onPressイベントから送信された値は、onNumイベントで受け取っています。引数(input)に値が送られてきています。

console.log(input)でどんなものが送られてきたか確認してみましょう!

「7」のボタンを押すと、こんなデータが送信されていることが分かりました。

7のボタン
//inputの中身
Object {
  "i": 7,
}

オブジェクトとして送信されていて、"i"がkey、7がvalueですね。

では、ここから「7」を取得したい場合は以下のようにすれば良いですよね。

input["i"] 
//valueの7が取れる

そして、最後に取得した値を保存します。

保存
this.setState({ leftNum:  input["i"] })

この記述を行うことでstateにあるlaftNumにinput["i"]の値を保存しています。

setStateはstateを変化させたい時に使用するもので「stateにsetする」的なイメージを持ってくれたらOKです。

こうすることで、

    this.state = {
      leftNum: 0
    }

0だったleftNumが

    this.state = {
      leftNum: 7
    }

7に変化します!

③保存した値を描画する

ラストは保存した値を描画する作業です。これは秒です、2秒です。

先ほどまで、「0 ? 0 = 0」としていたところを少し変更します!

1番左の0の部分を{this.state.leftNum}にするだけです。

これで現在のleftNumの値を描画してくれるというわけです。(すごい!)

App.js
//略

  render() {
    return (
      <View>
        <View style={styles.countArea}>
          <Text style={styles.value}>{this.state.leftNum}  ?  0  =  0</Text>
        </View>

9. 2桁以上の入力をできるようにしよう!

ここまででようやく押したボタンの数値が適応されるようになりました。でも、現状だと数字ボタンを押しても値が変更されるだけで1桁しか表示されません。

1桁しか計算できない電卓なんで全然かっこよくないですよね。むしろダサいです。

しかし、僕は2桁以上の入力を出来るようにするやり方に悩みに悩みました、何日も悩みました。1度考えることを辞めた僕はなぜか閃きました。

「1回文字型に変換して足して、数値型に戻せば良いんだ!!!!」

(僕はこのやり方しか思いつかなかったので他にもあれば教えていただきたいです。)

数値型のまま、既存のstateの値と入力された値を足すと普通に足し算されてしまいます。

悪い例
//デフォルトでleftNumに2、input["i"]に4があるとする
this.state.leftNum + input["i"]

2 + 4 = 6になってしまう
成功例
//デフォルトでleftNumに2、input["i"]に4があるとする
String(this.state.leftNum) + String(input["i"])

(文字列の)"2" + (文字列の)"4" = "24"

既存の値と入力された値の両者を文字列に変換して、+してあげると文字列同士の足し算が行われるのでこれを利用しました。

これを使って記述を変更しましょう!

App.js
  onNum(input){
    //console.log(input);
    //this.setState({ leftNum:  input["i"] })
    const num = Number(String(this.state.leftNum) + String(input["i"])) //文字列同士の足し算を行い、最後に数値型に変換
    this.setState({ leftNum:  num }) //leftNumに計算した値をset!
  } 

だんだん完成に近付いてきました。現在70%くらいの完成度です。ラストスパートです!

#10.四則演算の選択を可能に!

左側の数値の入力は可能になったので、計算方法の選択が出来るようにしましょう。

数字ボタンの時とやることは同じなのでサクッといきましょう!

App.js

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      leftNum: 0,
      mark: "?",
    }
  }

//~~~略

  render() {
    return (
      <View>
        <View style={styles.countArea}>
          <Text style={styles.value}>{this.state.leftNum}  {this.state.mark}  0  =  0</Text>
        </View>

//~~~略
//~~~略
        <View style={{ flexDirection: 'row'}}>
        //以下4つにonPressを追記
          <TouchableOpacity style={[styles.button, styles.buttonUpText]} onPress={() => this.onMark("plus")}>
            <Text style={styles.buttonText}>+</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.button, styles.buttonMinus]} onPress={() => this.onMark("minus")}>
            <Text style={styles.buttonText}>-</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.button, styles.buttonTimes]} onPress={() => this.onMark("times")}>
            <Text style={styles.buttonText}>x</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.button, styles.buttonDivide]} onPress={() => this.onMark("divide")}>
            <Text style={styles.buttonText}>÷</Text>
          </TouchableOpacity>

やっていることは3つです。

①stateに**[+ ー ➗X]**を表すための「mark」を追加

②常に今のmarkの値を描画するために{ this.state.mark }を追加

③四則演算のボタンにonPressイベントを追加

この3つは数字ボタンと流れは同じですよね。

この状態でボタンを押してもエラーが出るので、入力したボタンの値を受け取って正常に描画させましょう。

入力してきた値が"plus"ならmarkを「+」に変更というようなイメージです
たくさん条件分岐させていきます。

App.js
onMark(input){
    if (input == "plus"){
      this.setState({ mark: "+"  })
    } else if (input == "minus") {
      this.setState({ mark: "-" })
    } else if (input == "times") {
      this.setState({ mark: "x"} )
    } else if (input == "divide") {
      this.setState({ mark: ""})
    }
  }
}

(↑の➗マークが見えづらくなってます。)

11.右側の数字を入力可能に使用!

現状は左の数値と四則演算のマークの入力は可能になっています。

次は右側の数値の入力ができるようにしましょう!ここも今までと同じ流れで完了です。

App.js
export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      leftNum: 0,
      mark: "?",
      rightNum: 0, //←追記
    }
  }
  onNum(input){ //以下追記
    if (this.state.mark == "?"){
      let num = Number(String(this.state.leftNum) + String(input["i"]))
      this.setState({ leftNum: num })
    } else {
      let num = Number(String(this.state.rightNum) + String(input["i"]))
      this.setState({ rightNum: num })
    } 
  }

//~~~~~略

  render() {
    return (
      <View>
        <View style={styles.countArea}> //以下追記
          <Text style={styles.value}>{this.state.leftNum}  {this.state.mark}  {this.state.rightNum}  =  0</Text>
        </View>

ここまでのコードはこちらです。困ったら確認してください:relaxed:

#12. 「=」を押したら計算を実行する!

ようやく計算式の入力が出来るようになりました。

「=」が押されたら四則演算のマークに応じて計算が行われるようにしましょう。

まずは「=」ボタンの所に追記しましょう。

App.js
 <TouchableOpacity style={[styles.button, styles.buttonEqual]} onPress={() => this.onEqual()} >
   <Text style={styles.buttonText}> = </Text>
 </TouchableOpacity>

onEqualを定義して、markの値によって条件分岐を行います。

そして、結果の値をsetすれば完了です。

App.js

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      leftNum: 0,
      mark: "?",
      rightNum: 0,
      result: 0 //追記
    }
  }
      :
      :
      :
  onEqual(){
    if (this.state.mark == "+") {
      this.setState( { result: this.state.leftNum + this.state.rightNum })
    } else if (this.state.mark == "-") {
      this.setState( { result: this.state.leftNum - this.state.rightNum})
    } else if (this.state.mark == "x") {
      this.setState( { result: this.state.leftNum * this.state.rightNum})
    } else if (this.state.mark == "") {
      this.setState( { result: this.state.leftNum / this.state.rightNum})
    }
  }

  render() {
    return (
      <View>
        <View style={styles.countArea}>
          <Text style={styles.value}>{this.state.leftNum}  {this.state.mark}  {this.state.rightNum}  =  {this.state.result}</Text> //追記
        </View>

「=」ボタンを押したらonEqualが発火します。

onEqualイベントは、markの値によって条件分岐しています。

例えばmarkが「➗」だったら、leftNumとrightNumを割り算して、resultにsetしています。

これでようやく計算ができるようになりました!!!!!

次でラストです!!!

#13.リセットを反映させよう!

最後はリセットボタンです。iPhoneアプリの電卓なら「AC」にあたるところです。

リセットボタンを押したらstateにあるleftNumとrightNumを「0」に、markを「?」に変更します。

App.js

:
:onEqualの下に記述
:
  onReset(){
    this.setState({ leftNum: 0, mark: "?", rightNum:0, result:0 })
  }


 リセットボタンに追記

   <TouchableOpacity style={[styles.button, styles.buttonReset]} onPress={() => this.onReset()}>
      <Text style={[styles.buttonResetText]}>リセット</Text>
   </TouchableOpacity>

これで値のリセットが完了しました!

#まとめ

ようやく電卓が完成しました。お疲れ様でした。
Reactが初めての方はとても大変でしたよね。

今回のアドベントカレンダーでアウトプットの重要性が身に染みてわかりました。
この記事を書くために調べて、実践して、改善してと非常に良い勉強になりました。

とりあえず本当にお疲れ様でした!
完成版コードの確認をしたい方はこちらです。

33
12
4

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
33
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?