2019年12月22日
プログラミング
ペースの計算を行う【Reactで作るマラソンペースメーカー】


はじめに
新しい技術にチャレンジし続けるpalanのアドベントカレンダーDay22です!
昨日は『サーバーレス時代のデータベース FaunaDB で GraphQL を試してみた』についての記事でした。
このシリーズでは、Reactを使用しマラソンペース計算システムを作っています。
前回はReact Hooksを使う、という記事でReactの新機能であるhookを使用し、関数型コンポーネントの解説をしていきました。
今回は、シリーズの続きとしてReactのhookも使いながら、実際にマラソンのペースを計算する機能を作っていきます。
ヘッダーをつけてみる
まず、今回作っていく完成形のUIはこちらです。
距離とタイム(時間・分)を設定することで、1kmあたりのペースが表示されるシステムです。
またせっかくMaterial-UIを使っているので、ヘッダーについてもMaterial-UIのコンポーネントを使っていきましょう。
Material-UIの公式サイトから、こちらのApp Barコンポーネントを見てみましょう。
こちらのSimple App Barが良さそうですね!
App.jsに(次回以降でコンポーネントをちゃんとファイル分けしていきます)、このようにインポートしていきます。
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
その後はこのようにUIを作っていきます。
<AppBar position="static">
<Toolbar>
<IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
<Typography variant="h5" className={classes.title}>
Marathon Pace Maker
</Typography>
</Toolbar>
</AppBar>
入力フォームを作る
さて、次は入力フォームを作っていきます。
どれくらいの距離を走るのか、どれくらいの時間で走りたいのか(時間と分) を元に、実際に1kmあたり何分何秒で走ればよいのかを計算し出力していきます。
まず、どれくらいの距離を走るのか、については主要なフルマラソン(42.195km)、ハーフマラソン(21.0975km)、10km、5kmの4つから選択するようにしてみます。
選択式のフォームですが、TextField
コンポーネントを使用していきます。
また実際に選択する選択肢部分はMenuItem
を使用していきます。
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
````
そして選択肢部分のラベルと値を配列に格納していきます。
```jsx
const distances = [
{
value: 42.195,
label: 'フルマラソン(42.195km)',
},
{
value: 30,
label: '30km',
},
{
value: 21.0975,
label: 'ハーフマラソン(21.0975km)',
},
{
value: 10,
label: '10km',
},
{
value: 5,
label: '5km',
},
];
次にstateの初期値を設定していきます。
後々に目標時間なども設定するのでvaluesに格納していきます。
const [values] = React.useState({
distance: 42.195,
});
次に選択肢が選択された際に、値をvaluesに設定する関数を書いていきます。
値を数値型として設定する為に、Numberで数値型にします。
const handleChange = prop => event => {
values[prop] = Number(event.target.value);
});
};
そして肝心のフォーム部分のコードです。
<TextField
id="distance"
select
label="距離"
className={classes.textField}
onChange={handleChange('distance')}
value={values.distance}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
helperText="走るレースの距離を選択してください"
margin="normal"
>
{distances.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
主要なところを解説します。
select
こちらがあることで、選択型のSelect APIを使用することができます。
こちらで先程のhandleChangeを呼び出しています。
valuesのキーであるdistanceを引数にしています。
onChange={handleChange('distance')}
こちらはTextFieldの値となります。
value={values.distance}
こちらで選択肢部分のメニューに対し、Propsを設定することができます。
SelectProps={{
MenuProps: {
className: classes.menu,
},
次に、入れ子の形で選択肢部分の値を設定していきます。
{distances.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
配列distancesにvalueとlabelを設定しましたが、そちらを使用して選択肢を作っています。
mapで展開し、MenuItemのkey、valueに設定し、また表示上のラベルも設定しています。
これで距離の選択フォームができました。
次に目標時間、分のフォームを作っていきます。
<TextField
id="standard-number"
label="目標タイム(時間)"
type="number"
className={classes.numberField}
onChange={handleChange('hour')}
value={values.hour}
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputProps: { min: 0, max: 10 },
endAdornment: <InputAdornment position="end">h</InputAdornment>,
}}
margin="normal"
/>
<TextField
id="standard-number"
label="目標タイム(分)"
type="number"
className={classes.numberField}
value={values.minute}
onChange={handleChange('minute')}
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputProps: { min: 0, max: 59 },
endAdornment: <InputAdornment position="end">m</InputAdornment>,
}}
margin="normal"
/>
先程のフォームと異なる点は、select型でないくらいで、あとはほぼ一緒です。
InputProps
というPropsを使うことで、入力に関するPropsを設定できます。
最小、最大値の設定はInputProps
の更にその中に値を設定する必要があるので注意です。
また、この最後にhやmといった単位のUIを作る為にInputAdornment
コンポーネントを使用しています。
ペース計算処理を作る
次に、ペースの計算処理を作っていきます。
hookのuseStateを使用し、paceMinuteとpaceSecondの初期値を設定しています。
const [paces, setPaces] = React.useState({
paceMinute: 5,
paceSecond: 41,
});
次に先程フォームの入力時につくった、handleChange関数に計算処理を入れていきます。
フォームの値を入力したら、ペース部分にペースが表示される仕様を実現する為のものです。
const handleChange = prop => event => {
values[prop] = Number(event.target.value);
// 分数を計算
const minute = (values.hour * 60) + values.minute;
// 分数を距離で割り、1キロあたりの分数を計算
const paceMinute = (minute) / values.distance;
// 分数の少数以下に60をかけ秒数を計算
const paceSecond = (paceMinute - Math.floor(paceMinute)) * 60
setPaces({
...paces,
paceMinute: Math.floor(paceMinute),
paceSecond: Math.floor(paceSecond),
});
};
コメントに書いたように、まずvaluesのminiteとhourから全体の分数を計算し、そこを距離であるdistanceで割ることで目標のペースが計算できます。
あとは小数点以下を丸めたり処理し、setPacesでpaceMinuteとpaceSecondのstateに値を設定していきます。
最後にUI部分のコードです。
<p>
目標をクリアする為には、1kmあたり <strong>
{paces.paceMinute}分{paces.paceSecond}秒</strong>
で走る必要があります。
</p>
これで先程のpaceMinuteとpaceSecondに設定されたら、正しくもく目標の値が計算されて表示されていきますね!
こんな形で、それぞれの値を変更しても連動しって正しく目標ペースが表示されるようになりました!
まとめ
前回のReact Hooksを実際に使いながら、今回は計算処理を作っていきました。
まだコンポーネント分割できていなかったり、また動的にペースを変更したい(〜キロまではこれくらいのペース、そこから先はこれくらいのペースといった具合)など、まだ改善していく必要があります。
次回以降に対応していきますので、お楽しみに!
ソースコードや実際の公開されているURLはこちらです。
https://github.com/palan-inc/marathon-pace-maker
https://palan-inc.github.io/marathon-pace-maker/
Reactのお仕事に関するご相談
Bageleeの運営会社、palanではReactに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。
この記事は
参考になりましたか?
0
0
関連記事

2022年5月13日
Reactでオセロゲームを作る

2021年4月9日
Slack OAuth x React で Slack ユーザー認証を作ってみた!

2021年3月1日
Reactで使えるバリデーションライブラリを紹介!

2021年2月22日
Hasura Cloud × Auth0 × React でお手軽にTodoアプリを作ってみた!

2020年12月12日
Blitz.jsとTailwind CSSでメモ帳アプリの作成【第2弾】

2020年12月8日
コンポーネントを実装するときに意識すること
簡単に自分で作れるWebAR
「palanAR」はオンラインで簡単に作れるWebAR作成ツールです。WebARとはアプリを使用せずに、Webサイト上でARを体験できる新しい技術です。
palanARへ
palanでは一緒に働く仲間を募集しています
正社員や業務委託、アルバイトやインターンなど雇用形態にこだわらず、
ベテランの方から業界未経験の方まで様々なかたのお力をお借りしたいと考えております。
運営メンバー

Eishi Saito 総務
SIerやスタートアップ、フリーランスを経て2016年11月にpalan(旧eishis)を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。
sasakki デザイナー
アメリカの大学を卒業後、日本、シンガポールでデザイナーとして活動。

やまかわたかし デザイナー
フロントエンドデザイナー。デザインからHTML / CSS、JSの実装を担当しています。最近はReactやReact Nativeをよく触っています。
Sayaka Osanai デザイナー
Sketchだいすきプロダクトデザイナー。シンプルだけどちょっとかわいいデザインが得意。 好きな食べものは生ハムとお寿司とカレーです。

はらた エンジニア
サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

こぼり ともろう エンジニア
サーバーサイドエンジニア。SIerを経て2019年7月に入社。日々学習しながらRuby on Railsを使った開発を行っています。

ささい エンジニア
フロントエンドエンジニア WebGLとReactが強みと言えるように頑張ってます。

Damien
WebAR/VRの企画・開発をやっています。森に住んでいます。

ゲスト bagelee

かっきー

まりな

suzuki
miyagi

ogawa
雑食デザイナー。UI/UXデザインやコーディング、時々フロントエンドやってます。最近はARも。

いわもと
デザイナーをしています。 好きな食べ物はラーメンです。

taishi kobari
フロントエンドの開発を主に担当してます。Blitz.js好きです。

kubota shogo
サーバーサイドエンジニア。Ruby on Railsを使った開発を行いつつ月500kmほど走っています!
nishi tomoya

aihara
グラフィックデザイナーから、フロントエンドエンジニアになりました。最近はWebAR/VRの開発や、Blender、Unityを触っています。モノづくりとワンコが好きです。
nagao
SIerを経てアプリのエンジニアに。xR業界に興味があり、unityを使って開発をしたりしています。

Kainuma
サーバーサイドエンジニア Ruby on Railsを使った開発を行なっています

sugimoto
asama
ando
iwasawa ayane

oshimo
異業界からやってきたデザイナー。 palanARのUIをメインに担当してます。 これからたくさん吸収していきます!