- 投稿日:2020-04-05T21:23:33+09:00
複数の GitHub リポジトリの情報を簡単に比較できるサービス「Repositories Comparison」を公開しました
こちらもご一緒にどうぞ
プログラミング言語別の求人数を一覧で見ることができるサービス「Nojov」を公開しました - Qiitaアプリケーション
作ったきっかけ
最近、業務で Golang を利用した新規開発のプロジェクトで利用するフレームワークの検討を行う機会がありました。
新規開発で利用するライブラリを選別する際に、 GitHub リポジトリの Star 数や最終コミットの日時等を比較することはよくあると思います。
Golang のフレームワークといえば Gin, Beego, echo, その他色々ありますが、それらのリポジトリを行ったり来たりして情報を見比べる作業がとても面倒でした。
そこで、複数のリポジトリの情報を簡単に見比べることができるサービスがあったら便利かもしれないと思い、作ってみました。GitHub
使い方
アクセストークンを設定する
Repositories Comparison では、リポジトリ情報の取得に GitHub API を利用しています。
アクセストークンを設定しなかった場合、 GitHub API は 1 時間あたり 60 件しかリクエストを送信できませんが、アクセストークンを設定することで 1 時間あたり 5000 件までリクエストを送信することができます。
Repositories Comparison を快適に利用するために、アクセストークンを設定することを推奨します。
GitHub アクセストークンはこちらから生成・取得してください。
アクセストークンに付与する必要がある最低限のスコープはpublic_repo
です(プライベートリポジトリを指定する場合はrepo
が必要です)。アクセストークンは localStorage に保存され、 GitHub API を利用したリポジトリ情報の取得以外の用途に使われることは一切ありません。
比較したいリポジトリを追加する
入力フォームから比較したいリポジトリを追加していきます。
フォーマット:<owner>/<repo>
比較する
後は
COMPARE
ボタンを押すだけです。
以下のようにリポジトリの情報を表形式で比較することができます。また、表形式だけでなくチャート形式で比較することもできます。
利用技術
- React
- TypeScript
- Docker
- Terraform
- Serverless Framework
- AWS
- Route 53
- Certificate Manager
- S3
- CloudFront
- 投稿日:2020-04-05T18:59:01+09:00
【React + Material-UI】数字当てゲームを作ろう!【2020年4月版】
はじめに
この記事は「Reactのチュートリアルを終えて、何かを作ってみたい」という読者を想定しています。
分からない部分が出た場合はReactの公式ドキュメントに立ち返りましょう。material-uiの公式ドキュメントも要チェック
前にReact+Material-UIで作ったもの
今回作ったもの
React Number Guess
ランダムに生成された1~100の数字を推測するゲームです。「60より上ですか?」などの質問をしていき、数字が絞れたら「〇〇ですか?」で正解を出します。右側に予想履歴が表示されます。
戦略
ReactコンポーネントにはMaterial-UIを使い、保持するstateをReactのuseStateで実装します
また、今回はuseEffectを使い、読み込み時に1~100のランダムな数字を生成します。component, containerの洗い出し
component
- 数字ボタン: 0~9の数字を入力できるボタンコンポーネント。色はピンクで固定。
- 大きいボタン: 「より下ですか?」「クリア」「予想する」ボタンなど、数字ボタン以外に使うコンポーネント。色をpropsで指定できる。
- ディスプレイ: 現在の質問を表示するコンポーネント
- 予想履歴: いままでの予想の一覧を表示するコンポーネント
- 正解モーダル: 正解したときに表示されるモーダルコンポーネント
その他、タイトルと説明はコンポーネントにせず、containerにそのまま書きます。
container
- NumberGuess: すべてのコンポーネントを集めて表示するコンテナ
stateの洗い出し
- inputNumber: 予想に使う数字(int)
- questionContent: 質問の内容。"より下ですか?"、"より下ですか?"、"ですか?"の3種類(string)
- history: 質問の履歴が格納された配列(array)。
[[34,"より下ですか?", "はい"],[17,"より下ですか?", "はい"],[7,"より上ですか?", "はい"]]
のような2次元配列- answer: 答えの数字。はじめにuseEffectでランダムに設定される(int)
- open: 正解したときにmodalを開くために必要なboolean
- guessTimes: 質問した回数(int)
インストール
create react app
npx create-react-app react-calculator cd react-calculator@material-ui/core
// npm を使う場合 npm install @material-ui/core // yarn を使う場合 yarn add @material-ui/coreバージョン情報
- react: 16.13.1
- @material-ui/core: 4.9.9
ファイル構成
コンポーネントの実装
Display
Display.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; const useStyles = makeStyles((theme) => ({ box: { width: "100%", margin: "10px auto", }, })); export default function Display(props) { const classes = useStyles(); return ( <div> <Box className={classes.box}> <Typography variant="h3"> {props.children} </Typography> </Box> </div> ); }NumberButton
NumberButton.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; export default function NumberButton(props) { const useStyles = makeStyles((theme) => ({ margin: { margin: theme.spacing(1), backgroundColor: "#f48fb1", }, })); const classes = useStyles(); return ( <div> <Button className={classes.margin} onClick={props.onClick}> {props.children} </Button> </div> ); }ポイント
- propsでonClickイベントを受け取って、ButtonのonClickに接続しています
LongButton
LongButton.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; export default function LongButton(props) { const useStyles = makeStyles((theme) => ({ margin: { margin: theme.spacing(2), padding: theme.spacing(1), backgroundColor: props.backgroundColor, }, })); const classes = useStyles(); return ( <div> <Button className={classes.margin} onClick={props.onClick}> {props.children} </Button> </div> ); }ポイント
- 数字ボタンと他のボタンでスタイルを変えようと思いましたが、同じような見た目になってしまいました。こちらは背景色をpropsに渡すことができます。
GuessHistory
GuessHistory.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import Box from '@material-ui/core/Box'; const useStyles = makeStyles((theme) => ({ box: { textAlign: "left", margin:"20px 0", padding:"0 20px" }, })); export default function GuessHistory(props) { const classes = useStyles(); const history = props.history; return ( <div> <Typography>予想履歴</Typography> <Box className={classes.box}> { history.map((guess, index) => <Box key={index} marginBottom="10px"> <Typography variant="h5"> {index+1}. {guess[0]}{guess[1]}: {guess[2]} </Typography> </Box> ) } </Box> </div> ); }ポイント
history
をprops
で受け渡しています。history.map
で単一のguess
を受け取り、Box
内で表示しています。map
を使うときはコンポーネントにkey
をつけるのを忘れずに。正解モーダル
SuccessModal.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Modal from '@material-ui/core/Modal'; import Backdrop from '@material-ui/core/Backdrop'; import Fade from '@material-ui/core/Fade'; import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; const useStyles = makeStyles((theme) => ({ modal: { display: 'flex', alignItems: 'center', justifyContent: 'center', }, paper: { backgroundColor: theme.palette.background.paper, border: '2px solid #000', boxShadow: theme.shadows[5], padding: theme.spacing(2, 4, 3), }, })); export default function TransitionsModal(props) { const classes = useStyles(); const guessTimes = props.guessTimes; const answer = props.answer; const open = props.open return ( <div> <Modal aria-labelledby="transition-modal-title" aria-describedby="transition-modal-description" className={classes.modal} open={open} closeAfterTransition BackdropComponent={Backdrop} BackdropProps={{ timeout: 500, }} > <Fade in={open}> <div className={classes.paper} > <Box marginBottom="30px" id="transition-modal-title"> <Typography variant="h4">正解です!</Typography> </Box> <Box id="transition-modal-description"> <Typography variant="h5">正解の数字: {answer}</Typography> <Typography variant="h5">正解までの質問数: {guessTimes}</Typography> </Box> </div> </Fade> </Modal> </div> ); }ポイント
- 元となるコンポーネントはmaterial-uiの公式ドキュメント、モーダルから引っ張ってきました
props
でopen
を受け取ることで、コンテナのopen
stateがtrue
になったときに子のopenも更新されます。コンテナの実装
NumberGuess.jsimport React, {useState, useEffect} from 'react'; //components import NumberButton from '../components/NumberButton'; import LongButton from '../components/LongButton'; import Display from '../components/Display'; import GuessHistory from '../components/GuessHistory'; import SuccessModal from '../components/SuccessModal'; //material-ui import Box from '@material-ui/core/Box'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; export default function NumberGuess() { //states const [inputNumber, setInputNumber] = useState(0); const [questionContent, setQuestionContent] = useState(""); const [history, setHistory] = useState([]); const [answer,setAnswer] = useState(0); const [open, setOpen] = useState(false); const [guessTimes, setGuessTimes] = useState(0); //ページが読み込まれたときに一度だけ実行させる useEffect(() => { setAnswer(Math.floor( Math.random() * (100) ) + 1); }, []) //数字ボタンをクリックしたとき const handleNumberClick = (num) => { let newInputNumber = inputNumber*10 + num; if(newInputNumber >= 1 && newInputNumber <= 100){ setInputNumber(newInputNumber); } } //予想の内容ボタンをクリックしたとき const handlequiestionContentClick = (content) => { if(content === "biggerThan"){ setQuestionContent("より上ですか?"); } else if(content === "smallerThan"){ setQuestionContent("より下ですか?"); } else if(content === "exact"){ setQuestionContent("ですか?"); } } //クリアボタンをクリックしたとき const handleClearClick = () => { setInputNumber(0); setQuestionContent(""); } //予想ボタンをクリックしたとき const handleSubmitClick = () => { //予想の内容が入力されていない場合はすぐに返す if(questionContent === "") return; //予想した回数を1増やす setGuessTimes(guessTimes + 1); if(questionContent === "より上ですか?"){ if(inputNumber < answer){ setHistory([ ...history, [inputNumber, "より上ですか?", "はい"] ]); } else { setHistory([ ...history, [inputNumber, "より上ですか?", "いいえ"] ]); } } else if(questionContent === "より下ですか?"){ if(inputNumber > answer){ setHistory([ ...history, [inputNumber, "より下ですか?", "はい"] ]); } else { setHistory([ ...history, [inputNumber, "より下ですか?", "いいえ"] ]); } } else if(questionContent === "ですか?"){ if(inputNumber === answer){ setHistory([ ...history, [inputNumber, "ですか?", "はい"] ]); //正解の場合にモーダルを開く setOpen(true); } else { setHistory([ ...history, [inputNumber, "ですか?", "いいえ"] ]); } } setInputNumber(0); setQuestionContent(""); } return ( <div> <Box marginBottom="20px"> <Typography variant="h4">React Number Guess</Typography> <Typography variant="h6">1~100の数字を予想しよう</Typography> </Box> <Grid container spacing={1}> <Grid item sm={6} xs={12}> <Display>{inputNumber} {questionContent}</Display> <Box display="flex" flexDirection="column"> <Box display="flex" justifyContent="center"> <NumberButton onClick={() => handleNumberClick(0)}>0</NumberButton> <NumberButton onClick={() => handleNumberClick(1)}>1</NumberButton> <NumberButton onClick={() => handleNumberClick(2)}>2</NumberButton> <NumberButton onClick={() => handleNumberClick(3)}>3</NumberButton> <NumberButton onClick={() => handleNumberClick(4)}>4</NumberButton> </Box> <Box display="flex" justifyContent="center"> <NumberButton onClick={() => handleNumberClick(5)}>5</NumberButton> <NumberButton onClick={() => handleNumberClick(6)}>6</NumberButton> <NumberButton onClick={() => handleNumberClick(7)}>7</NumberButton> <NumberButton onClick={() => handleNumberClick(8)}>8</NumberButton> <NumberButton onClick={() => handleNumberClick(9)}>9</NumberButton> </Box> <Box display="flex" justifyContent="center"> <LongButton onClick={() => handlequiestionContentClick("biggerThan")} backgroundColor="#f48fb1">より上ですか?</LongButton> <LongButton onClick={() => handlequiestionContentClick("smallerThan")} backgroundColor="#f48fb1">より下ですか?</LongButton> <LongButton onClick={() => handlequiestionContentClick("exact")} backgroundColor="#f48fb1">ですか?</LongButton> </Box> <Box display="flex" justifyContent="center"> <LongButton onClick={handleClearClick} backgroundColor="#bdbdbd">クリア</LongButton> <LongButton onClick={handleSubmitClick} backgroundColor="#b2ff59">予想する</LongButton> </Box> </Box> </Grid> <Grid item sm={6} xs={12}> <GuessHistory history={history}/> </Grid> </Grid> <SuccessModal open={open} guessTimes={guessTimes} answer={answer}/> </div> ); }ポイント
- useEffectを使って、マウント時に一度だけanswerを設定するようにしました。単純に
const answer = ...
としていると、ボタンを押すごとに更新されてしまいます。handleSubmitClick
のsetHistory
内では元のhistory
を展開して、最後に新たな要素を追加しています。App.js
App.jsimport React from 'react'; import './App.css'; import NumberGuess from './containers/NumberGuess' function App() { return ( <div className="App"> <div className="container"> <NumberGuess /> </div> </div> ); } export default App;App.css
App.css.App { text-align: center; } .container{ margin: 80px auto 80px auto; max-width: 900px; background-color: #80deea; }以上で完成です!
おまけ
こちらの記事を参考に、gh-pagesでサイトを公開しました。
https://shintaro-hirose.github.io/react-number-guess/今後もReactで簡単なアプリを作っていきます!
- 投稿日:2020-04-05T12:46:02+09:00
【React Hooks】stateの使用からみたクラスコンポーネントと関数コンポーネントのコード比較
これまでは、stateはクラスコンポーネントのみが持ち、関数コンポーネントの中では使用することができませんでした。
しかし、hookを導入することで関数コンポーネントの中でもstateが使えるようになりました。(hookはReact16.8で追加された機能です。)
今回は、stateを使用するクリックカウントコンポーネントを作成する場合の、クラスコンポーネントと関数コンポーネントのコードを比較をしていきたいと思います。全体のコード
クラスコンポーネントの場合
Class.jsimport React from 'react'; class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }関数コンポーネントの場合
Function.jsimport React, { useState } from 'react'; function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }関数コンポーネントの方がコード量が少なく、見やすい印象を受けます。
それでは2つの違いを詳しく見ていきましょう。1.stateの設定方法
クラスコンポーネントでは、コンストラクタ内でthis.stateにstate(count)を設定しています。
Class.jsclass Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; }関数コンポーネントでは、useStateフックを使用して、state(count)と、stateを更新する関数(setCount)を分割代入で受け取っています。
useStateの引数には初期値を渡します。Function.jsimport React, { useState } from 'react'; function Example() { const [count, setCount] = useState(0);2.stateの読み出し
クラスコンポーネントで現在のcountの値を表示する場合、this.state.countとします。
Class.js<p>You clicked {this.state.count} times</p>関数コンポーネントでは、countとして直接呼び出せます。
Function.js<p>You clicked {count} times</p>3.stateの更新
クラスコンポーネントでは、this.setState()を使用します。
Class.js<button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button>関数コンポーネントでは、useStateの分割代入で受け取ったsetCountを使用します。
Function.js<button onClick={() => setCount(count + 1)}> Click me </button>まとめ
以上、クラスコンポーネントと関数コンポーネントをstateの使用から見て比較をしてみました。
関数コンポーネントの方が、stateの読み出しも更新も、「this」を介さずそのまま使用できるのですっきり見えます。
最後まで読んでいただきありがとうございました。
- 投稿日:2020-04-05T11:49:27+09:00
FigmaのプラグインのUIをReact+TSで作る
TL;DR
git clone https://github.com/kazuyaseki/react-figma-plugin-boilerplate.git <your project name> yarn or npm install yarn webpack:watch or npm run webpack:watch
Figmaのプラグイン、テンションが上がるものなのですが、プラグイン自体がUIを必要とする & ある程度 Stateful なものな場合、命令的に作るのは耐えられない!という気持ちになりました。
なのでReactで開発できる boilerplate を作ってみたので、使い方をご紹介していこうと思います。
boilerplate の中身はこちらです。cloneしていい感じに使ってみてください。
https://github.com/kazuyaseki/react-figma-plugin-boilerplateどうやって実現するか
特に難しいことは何もないんですが、Figmaのプラグインでは、プラグインが起動されたときのUIは
manifest.json
のui
のところに指定した ui.html を読み込むので、そこにエントリーポイントを用意してあげてReactDOM.render
するだけです。普通のReactアプリケーションですね。ui.html<div id="app"></div>ui.tsximport * as React from 'react'; import * as ReactDom from 'react-dom'; // App の中で普通にReact開発していく import { App } from './App'; ReactDom.render(<App />, document.getElementById('app'));開発中のプラグインをFigmaから使えるようにする
manifest.json へパスを通す必要があります。
Plugins > Development > New Plugin... をクリックすると次のようなWindowが出現するので、そこから指定してあげてください。
サンプル
以上であとは boilerplate 試してみてくれ、というくらい語ることがないのですが、サンプルとして次のツイートのようなものを作ったのでコードを貼っておきます。
めっちゃ雑だけどReactでプロジェクト内のコンポーネントをインクリメンタルサーチしてコンポーネント作るfigma plugin作れた。keyboard shortcut機能が追加されたら普通に便利になれそう pic.twitter.com/Zx52Mz5tQX
— seya (@sekikazu01) April 4, 2020プラグイン作る上での注意点は、figma のデータを参照できるスレッドとプラグインのUI部分のスレッドは別物だというところです。詳しくはFigmaのドキュメントHow Plugins Runを参照ください。
なので message オブジェクトを使ってなんとかするのですが、下記サンプルにて main thread -> ui thread と ui thread -> main thread の両方向の例があるので参考にしてみてください。code.tsximport { subscribeOnMessages } from 'react-figma'; figma.showUI(__html__); const componentNodes = figma.root.findAll((node) => node.type === 'COMPONENT'); // uiスレッドにそのままNode渡すとなぜかプロパティが undefined になるので成形してから渡す const conmponentsData = componentNodes.map((node) => ({ id: node.id, name: node.name, })); figma.ui.postMessage(conmponentsData); figma.ui.onmessage = (message) => { subscribeOnMessages(message); if (message.type === 'create-instance') { const component = figma.root.findOne( (node) => node.id === message.id ) as ComponentNode; component.createInstance(); } };App.tsximport * as React from 'react'; type ComponentItemType = { id: string; name: string; }; export const App = () => { const [query, setQuery] = React.useState(''); const [components, setComponents] = React.useState<ComponentItemType[]>([]); React.useEffect(() => { onmessage = (event) => { setComponents(event.data.pluginMessage as ComponentItemType[]); }; }, []); const create = (id: string) => { parent.postMessage({ pluginMessage: { type: 'create-instance', id } }, '*'); }; return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <div style={{ display: 'flex', flexDirection: 'column' }}> {components .filter((component) => { if (query.length === 0) { return true; } return component.name.includes(query); }) .map((component) => ( <button onClick={() => create(component.id)}> {component.name} </button> ))} </div> </div> ); };
- 投稿日:2020-04-05T00:26:35+09:00
【React + Material-UI】電卓アプリを作ろう!【2020年4月版】
はじめに
この記事は「Reactのチュートリアルを終えて、何かを作ってみたい」という読者を想定しています。
分からない部分が出た場合はReactの公式ドキュメントに立ち返りましょう。material-uiの公式ドキュメントも要チェック!
作ったもの
iPhoneに入っている電卓アプリのようなものを作りました。
Githubにソースコードを公開しています。
戦略
ReactコンポーネントにはMaterial-UIを使い、保持するstateをReactのuseStateで実装します。
component、containerの洗い出し
component
container
- calculator: ボタンとディスプレイを集結させたもの。今回はこれ自体がアプリとなる。
stateの洗い出し
iPhoneの電卓アプリをいじってみて、どんなstateが必要かを考えます。はじめにざっくりと書き出して、実際には実装しながら追加していくカタチになります。
- value1: 演算対象の数字の文字列
- value2: value1に変化を与える数字の文字列
operator: add(加算), subtract(減算), divide(除算), multiply(乗算)。operatorが指定されていないときはfalseにする。
isAllClear: clearボタンがAC / Cどちらかを判別するboolean。
isDicimalInput: .ボタンが押されて小数点以下入力になっているか判別するboolean。
isAnswerDisplay: =ボタンが押された直後には特殊な挙動をするので、判別するbooleanを用意しておく。
たとえば、 5 * 7 =
と入力した場合、value1は5, value2は7, operatorは"multiply"である。インストール
create react app
npx create-react-app react-calculator cd react-calculator@material-ui/core
// npm を使う場合 npm install @material-ui/core // yarn を使う場合 yarn add @material-ui/coreバージョン情報
- react: 16.13.1
- @material-ui/core: 4.9.8
ファイル構成
コンポーネントの実装
Display
Display.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; const useStyles = makeStyles((theme) => ({ box: { fontSize: "60px", width: "100%", margin: "0 auto", }, })); export default function Display(props) { const classes = useStyles(); return ( <div> <Box className={classes.box}> {props.children} </Box> </div> ); }ポイント
props.children
とすることで、<Display></Display>
ではさんだものを表示できます。MyButton
MyButton.jsimport React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; export default function MyButton(props) { const useStyles = makeStyles((theme) => ({ margin: { margin: theme.spacing(1), backgroundColor: props.backgroundColor }, })); const classes = useStyles(); return ( <div> <Button className={classes.margin} onClick={props.onClick}> {props.children} </Button> </div> ); }ポイント
ボタンコンポーネントは親から1. 背景色, 2. クリックしたときに実行する関数, 3. ボタンの文字を受け取ります。クリックしたときに実行する関数について、親コンポーネントで
<MyButton onClick={somthing}>
としても機能しないので、子のButtonのonClickに接続する必要があるんですね。コンテナの実装
長いコードなのでコメントアウトで説明していきます。
Calculator.jsimport React, {useState} from 'react'; //components import MyButton from '../components/MyButton'; import Display from '../components/Display'; //material-ui import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; function Calculator(){ //保持したいstates const [value1, setValue1] = useState("0"); const [value2, setValue2] = useState("0"); const [operator, setOperator] = useState(false); const [isAllClear, setIsAllClear] = useState(true); const [isDicimalInput, setIsDicimalInput] = useState(false); const [isAnswerDisplay, setIsAnswerDisplay] = useState(false); //数字ボタンを押したとき const handleNumberClick = (num) => { //=ボタンを押した直後に数字を押した場合 if(isAnswerDisplay){ setValue1(num); setValue2("0"); setOperator(false); setIsAnswerDisplay(false); setIsDicimalInput(false); } else { //演算子が押されていないときはvalue1を更新する if(!operator){ if(isDicimalInput){ setValue1(String(value1) + String(num)) } else { setValue1(String(Number(value1)*10+num)); } //演算子が押されているときはvalue2を更新する } else { if(isDicimalInput){ setValue2(String(value2) + String(num)) } else { setValue2(String(Number(value2)*10+num)); } } } //0以外が押されたときはACをCに変える if(num !== 0){ setIsAllClear(false); } } //00ボタンを押したとき const handleDoubleZeroClick = () => { if(!operator){ if(isDicimalInput){ setValue1(String(value1) + "00") } else { setValue1(String(Number(value1)*100)); } } else { if(isDicimalInput){ setValue2(String(value2) + "00") } else { setValue2(String(Number(value2)*100)); } } } //AC/Cボタンを押したとき const handleClearClick = () => { setIsDicimalInput(false); //ACのときはすべてリセット if(isAllClear){ setValue1("0"); setOperator(false); setIsAnswerDisplay(false) //Cのときはvalue2を"0"にする } else { setIsAllClear(true); if(isAnswerDisplay){ setValue1("0") } else{ if(!operator){ setValue1("0"); } else { setValue2("0"); } } } } // -/+ボタンを押したとき const handleMinusPlusClick = () => { if(isAnswerDisplay){ setValue1(-1 * Number(value1)); } else { if(!operator){ setValue1(-1 * Number(value1)); } else { setValue2(-1 * Number(value2)); } } } // %ボタンを押したとき const handlePercentageClick = () => { if(isAnswerDisplay){ setValue1(Number(value1) * 0.01); } else { if(!operator){ setValue1(Number(value1) * 0.01); } else { setValue2(Number(value2) * 0.01); } } } //演算子を押したとき //引数はadd subtract divide multiplyのどれか const handleOperatorClick = (a) => { if(isAnswerDisplay){ setIsAnswerDisplay(false); } else { if(value2 !== "0"){ if(operator === "add"){ setValue1(Number(value1) + Number(value2)) } else if(operator === "subtract"){ setValue1(Number(value1) - Number(value2)) } else if(operator === "divide"){ setValue1(Number(value1) / Number(value2)) } else if(operator === "multiply"){ setValue1(Number(value1) * Number(value2)) } } } setIsDicimalInput(false); setOperator(a); setValue2("0") } // .ボタンを押したとき const handleDicimalPointClick = () => { if(isDicimalInput) return; setIsDicimalInput(true); if(!operator){ setValue1(String(value1) + '.') } else { setValue2(String(value2) + '.') } } // =ボタンを押したとき const handleAnswerClick = () => { setIsDicimalInput(false); setIsAnswerDisplay(true); if(operator === "add") { if(value2 === "0"){ //演算子が指定されたあと、value2が入力されずに=が押された場合 //例えば、5 + = と入力した場合、10と表示する。 setValue2(value1) setValue1(Number(value1) + Number(value1)) } else { setValue1(Number(value1) + Number(value2)) } } else if(operator === "subtract") { if(value2 === "0"){ setValue2(Number(value1)) setValue1(Number(value1) - Number(value1)) } else { setValue1(Number(value1) - Number(value2)) } } else if(operator === "divide") { if(value2 === "0"){ setValue2(Number(value1)) setValue1(Number(value1) / Number(value1)) } else { setValue1(Number(value1) / Number(value2)) } } else if(operator === "multiply"){ if(value2 === "0"){ setValue2(Number(value1)) setValue1(Number(value1) * Number(value1)) } else { setValue1(Number(value1) * Number(value2)) } } } //ディスプレイに表示する内容 const displayMarkup = isAnswerDisplay ? ( value1 ) : ( operator ? ( value2 ) : ( value1 ) ); // AC/Cボタンの表示 const clearButtonMarkup = isAllClear ? ( "AC" ) : ( "C" ) return( <div> <Typography variant="h5"> React Calculator </Typography> <Display> {displayMarkup} </Display> //material-uiのBoxはflexを使って好きなように並べられる。公式ドキュメント参照。 <Box display="flex" flexDirection="column"> <Box display="flex" justifyContent="center"> //ボタンの中に表示したいものをMyButtonではさむ //MyButtonのpropsに背景色、onClick関数を渡す <MyButton backgroundColor="gray" onClick={handleClearClick}> <Typography variant="h5">{clearButtonMarkup}</Typography> </MyButton> <MyButton backgroundColor="gray" onClick={handleMinusPlusClick}> <Typography variant="h5">-/+</Typography> </MyButton> <MyButton backgroundColor="gray" onClick={handlePercentageClick}> <Typography variant="h5">%</Typography> </MyButton> //onClickに引数を渡したい場合はこのように記述する <MyButton backgroundColor="orange" onClick={() => handleOperatorClick("divide")}> <Typography variant="h5" >÷</Typography> </MyButton> </Box> <Box display="flex" justifyContent="center" > <MyButton onClick={() => handleNumberClick(7)} backgroundColor="#e0e0e0"> <Typography variant="h5">7</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(8)} backgroundColor="#e0e0e0"> <Typography variant="h5">8</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(9)} backgroundColor="#e0e0e0"> <Typography variant="h5">9</Typography> </MyButton> <MyButton backgroundColor="orange" onClick={() => handleOperatorClick("multiply")}> <Typography variant="h5" >×</Typography> </MyButton> </Box> <Box display="flex" justifyContent="center"> <MyButton onClick={() => handleNumberClick(4)} backgroundColor="#e0e0e0"> <Typography variant="h5">4</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(5)} backgroundColor="#e0e0e0"> <Typography variant="h5">5</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(6)} backgroundColor="#e0e0e0"> <Typography variant="h5">6</Typography> </MyButton> <MyButton backgroundColor="orange" onClick={() => handleOperatorClick("subtract")}> <Typography variant="h5" >-</Typography> </MyButton> </Box> <Box display="flex" justifyContent="center"> <MyButton onClick={() => handleNumberClick(1)} backgroundColor="#e0e0e0"> <Typography variant="h5">1</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(2)} backgroundColor="#e0e0e0"> <Typography variant="h5">2</Typography> </MyButton> <MyButton onClick={() => handleNumberClick(3)} backgroundColor="#e0e0e0"> <Typography variant="h5">3</Typography> </MyButton> <MyButton backgroundColor="orange" onClick={() => handleOperatorClick("add")}> <Typography variant="h5" >+</Typography> </MyButton> </Box> <Box display="flex" justifyContent="center"> <MyButton onClick={() => handleNumberClick(0)} backgroundColor="#e0e0e0"> <Typography variant="h5">0</Typography> </MyButton> <MyButton onClick={handleDoubleZeroClick} backgroundColor="#e0e0e0"> <Typography variant="h5">00</Typography> </MyButton> <MyButton onClick={handleDicimalPointClick} backgroundColor="#e0e0e0"> <Typography variant="h5" >.</Typography> </MyButton> <MyButton backgroundColor="orange" onClick={handleAnswerClick}> <Typography variant="h5" >=</Typography> </MyButton> </Box> </Box> </div> ) }; export default Calculator;ポイント
handleNumberClick
などの引数がある関数は、onClick={() => somthing(引数)}
のように記述します- stateはひとつにまとめても良いが、どこで何を使うかが決まってないうちは独立したstateにしておいて後からまとめよう。
App.js
App.jsimport React from 'react'; import './App.css'; import Calculator from './containers/Calculator' function App() { return ( <div className="App"> <div className="container"> <Calculator /> </div> </div> ); } export default App;App.css
App.css.App { text-align: center; } .container { margin: 80px auto 80px auto; padding: 20px; max-width: 300px; background-color: #80deea; }以上で完成です!
おまけ
こちらの記事を参考に、gh-pagesでサイトを公開しました。
https://shintaro-hirose.github.io/react-calculator/今後もReactで簡単なアプリを作っていきます!