気が向いたのでReact+Javascriptで電卓を作ってみました。
- 「1+1」とか「3*10」みたいな 2つの整数の四則演算 にか対応していません。(細かすぎると趣旨から外れるので)
- TypeScriptは使っていません。
- ソースコードはGithubにあげているので適当に見てください。
一緒に作りたい方向け
ハンズオン形式で作ってみたい方は以下を参照
想定しているレベル感
- 公式のチュートリアルをやったり、いろいろな教材を触ってみたがいまいちピンと来ていない
- useStateやpropsの使い方は分かるけどいつ使うべきかはわからない
- Reactの基礎を学んだが何か自分で作ってみたい
- Todoリストを作ったけどいまいちピンとこない
完成イメージ
設計
アプリ >> Appコンポーネント
ボタン >> Buttonコンポーネント
left ... 1つ目の数字
ope ... 演算子(+,-,*,/のこと)
right ... 2つ目の数字
ans ... 計算結果
数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。
環境構築
必要なツール
Node.js(NPM)
とgit
をインストールしておいてください。
読者の方の多くはもうすでに大丈夫だと思いますが念のため。
※不安な人は以下のコマンドを叩いて確認してください..
node -v
git -v
インストール
本当はcreate-react-app
してCSS書いてとかいろいろやらないといけないのですが、それでは本記事の趣旨にそぐわぬためテンプレートを用意しています。以下のコマンドを叩いて環境をサクッと整えてください。
git clone https://github.com/TBSten/react-calc.git
cd react-calc
npm install
git checkout base
npm run start
npm run start
で開発用サーバが立ち上がります。
特にエラーが出なければ以上で環境構築終了です!
テンプレートのコード確認
まずはテンプレートファイルの内容を見ていきます。
まずはプロジェクトフォルダ(先ほど環境構築したreact-calc
フォルダ)をお好みのテキストエディタで開いてください。(筆者はVSCodeを使ています)するとフォルダ構成は以下のようになっているはずです。
react-calc
+--node_module
+--public
+--src
...(その他いろいろ)
Reactで開発するときは基本的にプロジェクトフォルダ/src
フォルダ(以下src
フォルダ)内のファイルしかいじりません。なのでsrc
フォルダを開きます。src
フォルダ内は以下のようになっています。
src
+--styleフォルダ
+--index.js
+--App.js
...(その他いろいろ)
index.js
ファイルで基本的な設定(画面への描画やCSSの適用など)を行っています。その中でAppコンポーネントを読み込んでいるので、App.jsにいろいろ書いていきます。
function App() {
return (
<div className="calc">
<header>電卓</header>
<div className="display">
{/* ここに計算結果表示 */}
</div>
<div className="input">
<div className="numbers">
{/* ここに数字のボタンや「.」、「=」ボタン */}
</div>
<div className="operators">
{/* ここに「+」、「-」、「*」、「/」ボタン */}
</div>
</div>
</div>
);
}
export default App;
ところどころにコメントがうってある通り、その位置にJSXなどを記述していきます。
試しに{/* ここに計算結果表示 */}
を書き換えて電卓だよ
と表示してみましょう。
<div className="display">
電卓だよ
</div>
表示できました!後でここを計算結果に書き換えるイメージで今は大丈夫です。
Stateの実装
ここで設計で上げたStateの設計を振り返ってみます。
Stateleft ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果
まずはこれら4つのStateを使うことが分かっているので、これを実装します。
- useStateを使うのでimportする
-
const [state,setState] = useState(初期値);
の形式でそれぞれ記述する。
各Stateの初期値は数字のものは0(入力がないときはデフォルトで0になるように)、それ以外はnull(nullは「まだ分からない」を表す値です)を設定しておきましょう。
//ファイルの先頭
import {useState} from "react" ;
.....
//コンポーネント内
const [left, setLeft] = useState(0);
const [ope, setOpe] = useState(null);
const [right, setRight] = useState(0);
const [ans, setAns] = useState(null);
ここまででApp.jsの中身はは以下のようになっているはずです。
import {useState} from "react" ;
function App() {
const [left, setLeft] = useState(0);
const [ope, setOpe] = useState(null);
const [right, setRight] = useState(0);
const [ans, setAns] = useState(null);
return (
<div className="calc">
<header>電卓</header>
<div className="display">
電卓だよ
</div>
<div className="input">
<div className="numbers">
{/* ここに数字のボタンや「.」、「=」ボタン */}
</div>
<div className="operators">
{/* ここに「+」、「-」、「*」、「/」ボタン */}
</div>
</div>
</div>
);
}
export default App;
Stateを描画
このままだとStateを宣言しただけなのでこれを描画に使っていきましょう。
まず、先ほど電卓だよ
と書いたところにStateを表示していきましょう。とりあえず今はleft,ope,right という順番で表示させていきたいので、電卓だよ
の部分を以下のように変えましょう。
{left} {ope} {right}
こうすることでleft,ope,rightを順番に描画していきます。今は0,null,0なので以下のように表示されます。
※JSX内の{ null }
は空文字として扱われるためopeの部分に何も表示されていません。
これで表示がとりあえずできましたが、一番初めはopeとrightの部分は隠れていてopeに何か入力されたら(演算子が入力されたら)opeとrightを表示するように三項演算子を使ってみましょう。
{left} {ope} {right}
//↓↓↓
{left}
{ope === null ? "" : ope}
{ope === null ? "" : right}
こうすることで
- leftは常に表示
- opeは opeがnullじゃない時だけ表示
- rightは opeがnullじゃない時だけ表示
されるようになります。
ボタンを追加する
今のままではボタンがありません。ボタンを作っていきましょう。
設計にて次のように設計したので
アプリ >> Appコンポーネント ボタン >> Buttonコンポーネント
ボタンはButtonコンポーネントに分離していきましょう。コンポーネントは「src/components」フォルダ内に記述するとファイルの整理がきれいにできるので、**「src/components」フォルダ内にButton.jsx
ファイルを作成しましょう。**またそのなかでButtonコンポーネントを定義し、export default
して外部から使用できるようにしましょう。
export default function Button(props){
return (
//ここにJSXを書く
) ;
}
Buttonコンポーネントはただのボタンなので//ここにJSXを書く
にはbuttonタグを記述しましょう。
//.....
<button> </button>
//.....
またButtonコンポーネントは呼び出されるときに次のように呼び出すことで,textプロップスを出力してほしいです。
<Button text={"ぼたんだよー"} />
//↓↓↓↓↓ 出力
<button>ぼたんだよー</button>
なのでpropsからtextプロップスを受け取ってそれをbuttonタグ内で表示するようにしましょう。
//.....
<button>
{ props.text }
</button>
//.....
また今回のような汎用的なコンポーネントを作る際は作る時点で意図していなかったプロップス (例えば今のところtextプロップスしか渡されることを考えていないので,onClickプロップスなどが該当します) を渡されたらをとりあえずそれをbutton
タグに渡しておくようにするといいでしょう。
//.....
const {
text ,
...other
} = props ;
//.....
こうすることでprops.textが変数textへ、それ以外のpropsの中身はotherへオブジェクトとして渡されます。そしてこのotherをbuttonタグに渡しましょう。
//.....
<button {...other}>
{text}
</button>
//.....
これでButtonコンポーネントがひとまず完成しました。ここまででApp.js,Button.jsxコンポーネントは以下の通りになっているはずです。
export default function Button(props){
const {
text ,
...other
} = props ;
return (
<button {...other}>
{ text }
</button>
) ;
}
import {useState} from "react" ;
function App() {
const [left, setLeft] = useState(0);
const [ope, setOpe] = useState(null);
const [right, setRight] = useState(0);
const [ans, setAns] = useState(null);
return (
<div className="calc">
<header>電卓</header>
<div className="display">
{left}
{ope === null ? "" : ope}
{ope === null ? "" : right}
</div>
<div className="input">
<div className="numbers">
{/* ここに数字のボタンや「.」、「=」ボタン */}
</div>
<div className="operators">
{/* ここに「+」、「-」、「*」、「/」ボタン */}
</div>
</div>
</div>
);
}
export default App;
ボタンをAppに組み込む
先ほど作ったButtonコンポーネントをAppコンポーネントの中に組み込みましょう。Appコンポーネントに組み込めば画面に表示されるはずです。まず{/* ここに数字のボタンや「.」、「=」ボタン */}
の部分に追加してみます。
//App.jsxファイルの先頭
import Button from "./components/Button" ;
//.....
<Button text="0"/>
//.....
ボタンが表示されました!
あとは0,1,2,3,4,5,6,7,8,9のボタンを追加していきましょう。
//.....
<Button text="0"/>
<Button text="1"/>
<Button text="2"/>
<Button text="3"/>
<Button text="4"/>
<Button text="5"/>
<Button text="6"/>
<Button text="7"/>
<Button text="8"/>
<Button text="9"/>
//.....
※「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。
「ボタンが押されたら」を実装する
設計を振り返ってみると
数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。
となっているのでボタンが押されたときの処理を書いていきましょう。ボタンが押されたときの処理は**onClickプロップスに関数として渡します。**例えば0のボタンが押されたときの処理は、
<Button onClick={ ()=> {/*ここにやりたい事*/} } />
という風に記述します。
とはいっても0を押したときと1を押したときの動作の違いはほぼないので、keyPressedという関数を用意して、0のボタンが押されたときはkeyPressed(0)を、1のボタンが押されたときはkeyPressed(1)を...というように指定すると同じコードを何度も記述しなくて済みます。
function keyPressed(key){
//keyのボタンが押されたら...
}
//.....
<Button text="0" onClick={ ()=> {keyPressed(0)} } />
<Button text="1" onClick={ ()=> {keyPressed(1)} } />
<Button text="2" onClick={ ()=> {keyPressed(2)} } />
//以下同様
※繰り返しですが「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。
さてkeyPressed
関数の中では何をすべきでしょうか。設計を思い出してみましょう。
まず数字のボタンが押されたときは、leftまたはrightの値が変わってほしいです。leftかrightどちらの値を書き換えるべきかはopeによって変わります。
もしopeがnull(まだ入力されていない)なら
leftを更新
もしopeがnullじゃない(何か入力されている)なら
rightを更新
なのでkeyPressed
関数の中では条件によってleft,rightを更新する処理を書きます。
//.....
if(ope === null){
//leftを更新
}else{
//rightを更新
}
//.....
ではそれぞれどのような値に更新すべきでしょうか?まずleftから考えてみます。
これを考えるには①どのような状態の時に、②どのようなボタンが押されたら、③どのような値になってほしいのかの3つを考えると良いでしょう。例えば、
leftが
①「0」の時に
②「3」が押されたら
③leftは「3」になってほしい
ではこんなパターンではどうでしょうか?
leftが
①「3」の時に
②「4」が押されたら
③leftは「34」になってほしい
結論から言うと①と②と③の関係は次のようになっています。
③ = ①*10 + ②
---------------
3 = 0*10 + 3
34 = 3*10 + 4
このような思考は初心者にはすぐには理解できないかもしれませんので今は理解できなくてもいいでしょうが、いずれはできるようになれていくといいでしょう。
上の式をもとに考えるとleftの値は
更新前のleft * 10 + 入力された値
このような値に更新されるといいでしょう。
なのでifのYesの中には
//.....
function keyPresesd(key){
if(ope === null){
setLeft( left*10 + key);
}else{
//rightを更新
}
}
//.....
と書くと良いでしょう。また、rightも同様に考えれるので
//.....
function keyPresesd(key){
if(ope === null){
setLeft( left*10 + key);
}else{
setRight( right*10 + key);
}
}
//.....
とすればいいでしょう。ここまででいったんうまく動くかチェックしてみましょう。
まだopeを書き換える処理を実装していないので左側の数字の入力しかありませんが問題なさそうです。次は演算子を実装していきましょう。
演算子のボタンを実装する
演算子のボタンは{/* ここに「+」、「-」、「*」、「/」ボタン */}
のところに実装していきます。(数字のボタンの時とほぼ同じなので説明はほぼ割愛します。)以下のコードをここに書きましょう。
//.....
<Button text="+" onClick={ ()=> {/*後述*/} } />
<Button text="-" onClick={ ()=> {/*後述*/} } />
<Button text="*" onClick={ ()=> {/*後述*/} } />
<Button text="/" onClick={ ()=> {/*後述*/} } />
//.....
次にonClickプロップスに渡す関数を考えてみましょう。数字の時に作ったkeyPressed関数を使用したくなりますが、keyPressed関数を呼び出して実行されるのはあくまで数字が押されたときの処理です。演算子ボタンが押されたときの処理はそれとは少し違ったコードになるので、関数を分けるべきです。なので演算子ボタンがクリックされたとき用の関数opeKeyPressedを新たに定義してそれを実行するようにしましょう。
function opeKeyPressed(key){
//keyのボタンが押されたら...
}
//.....
<Button text="+" onClick={ ()=> {opeKeyPressed("+")} } />
<Button text="-" onClick={ ()=> {opeKeyPressed("-")} } />
<Button text="*" onClick={ ()=> {opeKeyPressed("*")} } />
<Button text="/" onClick={ ()=> {opeKeyPressed("/")} } />
//.....
次にopeKeyPressed(key)が実行されたときの処理を考えます。
速い話が、演算子ボタンが押されたらope
Stateを更新するべきです。それが更新されることで、**「opeやrightはopeがnullじゃない時だけ表示する」**の動きを実装できるからです。またどの演算子で更新するかはopeKeyPressed関数の引数として"+"や"-"などの演算子が与えられるのでそれをそのまま設定してしまえばOKです。
//.....
function opeKeyPressed(key){
setOpe(key);
}
//.....
ここまで実装できれば演算子ボタンを押すことで演算子が表示され、2つ目の数字が入力できるようになっているはずなので確認してみましょう。
ここまででApp.jsは以下のようになっているはずです。
import {useState} from "react" ;
import Button from "./components/Button" ;
function App() {
const [left, setLeft] = useState(0);
const [ope, setOpe] = useState(null);
const [right, setRight] = useState(0);
const [ans, setAns] = useState(null);
function keyPressed(key){
if(ope === null){
setLeft( left*10 + key);
}else{
setRight( right*10 + key);
}
}
function opeKeyPressed(key){
setOpe(key);
}
return (
<div className="calc">
<header>電卓</header>
<div className="display">
{left}
{ope === null ? "" : ope}
{ope === null ? "" : right}
</div>
<div className="input">
<div className="numbers">
<Button text="0" onClick={ ()=> {keyPressed(0)} } />
<Button text="1" onClick={ ()=> {keyPressed(1)} } />
<Button text="2" onClick={ ()=> {keyPressed(2)} } />
<Button text="3" onClick={ ()=> {keyPressed(3)} } />
<Button text="4" onClick={ ()=> {keyPressed(4)} } />
<Button text="5" onClick={ ()=> {keyPressed(5)} } />
<Button text="6" onClick={ ()=> {keyPressed(6)} } />
<Button text="7" onClick={ ()=> {keyPressed(7)} } />
<Button text="8" onClick={ ()=> {keyPressed(8)} } />
<Button text="9" onClick={ ()=> {keyPressed(9)} } />
</div>
<div className="operators">
<Button text="+" onClick={ ()=> {opeKeyPressed("+")} } />
<Button text="-" onClick={ ()=> {opeKeyPressed("-")} } />
<Button text="*" onClick={ ()=> {opeKeyPressed("*")} } />
<Button text="/" onClick={ ()=> {opeKeyPressed("/")} } />
</div>
</div>
</div>
);
}
export default App;
※Button.jsxは変更なし
「=」ボタンを押すと計算結果が表示される
さていよいよこの記事も大詰めです。最後に「=」を押すと計算結果を表示してくれるようにしましょう。まず=ボタンを作らないことには始まらないので、数字のボタンの最後にtext="="
であるボタンを追加しましょう。
//.....
<Button text="8" onClick={ ()=> {keyPressed(8)} } />
<Button text="9" onClick={ ()=> {keyPressed(9)} } />
<Button text="=" onClick={ ()=> {/*後述*/} } /> //←ここ
//.....
そしてまたまたonClickですが、=ボタンが押されたときの処理はこれまた他のボタンとは違った動きをします。なので新しくequalKeyPressed関数を用意してそれを実行するようにしましょう。ただし今回の=ボタンは1つしかないのでわざわざequalKeyPressed("=")としなくてもequalKeyPressed()だけで十分です。
function equalKeyPressed(){
//=ボタンが押されたとき
}
//.....
<Button text="=" onClick={ ()=> {equalKeyPressed()} } /> //←ここ
//.....
あとはequalKeyPressed()が実行されたときにおこる処理を考えるだけです。
ここで記事の初めの方で紹介した設計を思い出してください。
Stateleft ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果
数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。
「=」ボタンが押されたときは計算結果を表示しなければいけません。Stateの設計を見る限りその計算結果はansに入ってくると良さそうです。なのでansを更新しましょう。
function equalKeyPressed(){
setAns(/*後述*/);
}
ではsetAnsの中には何を書くべきでしょうか?
ここにはleft,ope,rightをもとに計算した結果を入れると良いです。
例えば、 leftに5,opeに"+",rightに7が入っていた場合,ansには12 を入れると良いです。
また、 leftに30,opeに"/",rightに5が入っていた場合,ansには6 を入れると良いです。
つまり、
もし ope が"+" なら
setAns(left + right)
もし ope が"-" なら
setAns(left - right)
//以下同様
としていけばいい訳です。これをコードにおこすと、
function equalKeyPressed(){
if(ope === "+"){
setAns(left+right);
}else if(ope === "-"){
setAns(left-right);
}else if(ope === "*"){
setAns(left*right);
}else if(ope === "/"){
setAns(left/right);
}
}
となります。
これでボタンを押したときの処理が実装できました。
ですがansはAppコンポーネントのreturn ( ... ) の中にはどこにも書かれていないため、 表示されません。なので right の後に表示するよう追加してみましょう。
<div className="display">
{left}
{ope === null ? "" : ope}
{ope === null ? "" : right}
答え:{ans} //ここを追加
</div>
ここまで来たら計算結果が表示されるはずです。チェックしてみましょう。
うーん、これじゃない感が否めませんが、とりあえず=を押したら計算結果の表示が出来ました!
これじゃない感を打破するために、「答え:」を=を押した後だけ表示するようにしてみましょう。これは三項演算子を使うことで手軽に実装できます。
{ ans === null ? "" : " 答え:"+ans }
三項演算子でansがnullじゃない(=ボタンが押された)ときだけ「 答え:〇〇」というように表示しています。
これで電卓の開発が出来ました!お疲れ様でした!
おまけ
-
ところどころに掲載したGithubのページのソースコードとこの記事で完成できるソースコードは多少異なっています。**これはあえてです。**何が違うのか、どちらの方が適切かぜひご自身で目を通して考えてみてください!(職務放棄)(ボソッ
-
この記事では実装していない機能(完成イメージとちょっと違うところ)がいくつかあるので紹介します。ぜひチャレンジしてみてください。(職務放棄)(ボソッ
-
計算結果をクリアしてもう一度計算しなおす機能
-
小数点が入力できる機能
-
様々な演算子(^, sin, cos, tanなどなど)や特別な数(πやeなど)
-
ボタンの並び方