React.js

初心者向けのReact.jsで作る超簡単なストップウォッチ

初心者向けのReact.jsで作る超簡単なストップウォッチ

みなさんこんにちは、からんです。

今回はReact.jsで簡単にできるストップウォッチの作り方の解説です。

仕様

最初が下記で「Stop、Reset」はボタンが薄くなっていて押すことができない感じになっています。(実際は押せます)

「Start」、「Stop」、「Reset」を押した時で押すことができるボタンを切り替えます。

例えば下記は「Start」ボタンを押した時です。

デモ

ここから見ることができます。

それでは解説しますがReactをインストールしたのを前提として解説します。

CSSを記述する所

cssはindex.cssに記述します。

index.cssのコードは下記にします。

/*----------------ここからリセットcss------------------------------------------*/
/*
html5doctor.com Reset Stylesheet
v1.6.1
Last Updated: 2010-09-17
Author: Richard Clark - http://richclarkdesign.com
Twitter: @rich_clark
*/

html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
    margin:0;
    padding:0;
    border:0;
    outline:0;
    font-size:100%;
    vertical-align:baseline;
    background:transparent;
}

body {
    line-height:1;
}

article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
    display:block;
}

nav ul {
    list-style:none;
}

blockquote, q {
    quotes:none;
}

blockquote:before, blockquote:after,
q:before, q:after {
    content:'';
    content:none;
}

a {
    margin:0;
    padding:0;
    font-size:100%;
    vertical-align:baseline;
    background:transparent;
}

/* change colours to suit your needs */
ins {
    background-color:#ff9;
    color:#000;
    text-decoration:none;
}

/* change colours to suit your needs */
mark {
    background-color:#ff9;
    color:#000;
    font-style:italic;
    font-weight:bold;
}

del {
    text-decoration: line-through;
}

abbr[title], dfn[title] {
    border-bottom:1px dotted;
    cursor:help;
}

table {
    border-collapse:collapse;
    border-spacing:0;
}

/* change border colour to suit your needs */
hr {
    display:block;
    height:1px;
    border:0;  
    border-top:1px solid #cccccc;
    margin:1em 0;
    padding:0;
}

input, select {
    vertical-align:middle;
}
p,.nav{
    margin:0;
}
/*----------------ここまでリセットcss------------------------------------------*/


.App{
  width: 50vw;
  margin:50px auto 0;
  text-align: center;
}
.title{
  font-size: 50px;
}
p{
  font-size: 30px;
}
.time,.button{
  margin-top: 50px;
}
.time{
  font-size: 40px;
}
.box{
    margin-left: 10px;
    display: inline-block;
    padding: 10px;
    font-size: 15px;
}
.able{
    padding: 10px;
    font-size: 15px;
    display: inline-block;
    margin-left: 10px;
}
.disable{
    opacity:0.4;
    padding: 10px;
    font-size: 15px;
    display: inline-block;
    margin-left: 10px;
}

コンポーネントの切り分け

ストップウォッチのコンポーネントの切り分けはこうします。

全体をAPP.jsに記述してボタンの部分をButton.jsxに記述をします。

HOOKSを使うときは一番上のコンポーネントで使わないといけないので最初に考えていたコンポーネントの切り分け方を変えて今回解説しているコンポーネントの切り分けにしています。

Button.jsxはまだ作ってないのでsrcディレクトリの下にcomponentsディレクトリを作ってその下に置きます。

App.jsとButton.jsx

下記はApp.jsです。

import React from 'react';
import Button from './components/Button';

function App() {
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">00:00.000</div>
      <Button/>
    </div>
  );
}

export default App;

下記はButton.jsxです。

import React from 'react';

    const Button = () => {
      
        return (
            <div className="button">
                <button onClick={onStart}>Start</button>
                <button>Stop</button>
                <button>Reset</button>
            </div>
        );
    };

export default Button;

「00:00.000」を変える事ができるようにする

まずは「00:00.000」と書いてある部分が変化できるようにしますがuseStateで行い初期値を「’00:00.000’」にします。

App.jsのコードを下記にします。

import React,{useState}  from 'react';        //この行を編集
import Button from './components/Button';


function App() {
  
  const [time,timeChange] = useState('00:00.000');          //この行を追加
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>  <!--この行を編集-->
      <Button />
    </div>
  );
}

export default App;

時間の取得をして「00:00.000」の部分を変化させますがまずは「Start」ボタンを押した時です。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;            //この行を追加

function App() {
  
  const [time,timeChange] = useState('00:00.000');

  //ここから追加
  const countUp = () => {
    
    const d = new Date(Date.now() - startTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);
  };
  //ここまで追加
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button />
    </div>
  );
}

export default App;

13行目の意味が分からなかったらおまじないみたいな感じで覚えていいと思います。

14行目が分の取得で15行目が秒の取得で16行目がミリ秒の取得で18行目で取得した値を表示しています。

そしてcountUp関数が一定時間ごとに行うことができるようにsetTimeoutを使います。

App.jsのコードを下記にします。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;             //この行を追加

function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    //ここから追加
    timeoutId = setTimeout(()=>{
      countUp();
    },10);
    //ここまで追加
  };
  
  //ここから追加
  const start = () => {

    startTime = Date.now();

    countUp();
  };
  //ここまで追加
  };
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button />
    </div>
  );
}

export default App;

22行目を「timeoutId」と置いていますがあとで使うために置いてます。

それでは「Start」ボタンを押すとストップウォッチが動くようにします。

ストップウォッチが動かす

App.jsを下記にします。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;  

function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    timeoutId = setTimeout(()=>{
      countUp();
    },10);
  };
  
  const start = () => {

    startTime = Date.now();

    countUp();
  };
  };
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button 
        onStart={start}      //この行を追加
      />
    </div>
  );
} 

export default App;

Button.jsxを下記にします。

import React from 'react';

    const Button = ({onStart}) => {
      
        return (
            <div className="button">
                <button onClick={onStart}>Start</button>
                <button>Stop</button>
                <button>Reset</button>
            </div>
        );
    };

export default Button;

これで「Start」ボタンを押すとストップウォッチが動きます。

次は「Stop」ボタンと「Reset」ボタンを押すと作動するようにします。

「Stop」ボタンと「Reset」ボタンを動かす

StopですがtimeoutIdを消さないといけないのでclearTimeout()を使います。

Resetは00:00.000が変化しているのを最初の状態に戻します。

App.jsを下記にします。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;

function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime+elapsedTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    timeoutId = setTimeout(()=>{
      countUp();
    },10);
  };

  const start = () => {

    startTime = Date.now();

    countUp();

    statusChange(GameStatus.start);
  };

  //ここから追加
  const stop = () => {
    clearTimeout(timeoutId);

  }
  
  const reset = () => {
    timeChange('00:00.000');

  };
  //ここまで追加
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button 
        onStart={start}
        onStop={stop}            //この行を編集
        onReset={reset}         //この行を編集
      />
    </div>
  );
}

export default App;

Button.jsxを下記にします。

import React from 'react';

    const Button = ({onStart,onStop,onReset,status}) => {             //この行を編集
      
        return (
            <div className="button">
                <button onClick={onStart}>Start</button>
                <button onClick={onStop}>Stop</button>            //この行を編集
                <button onClick={onReset}>Reset</button>         //この行を編集
            </div>
        );
    };

export default Button;

これで「Start」ボタン、「Stop」ボタン、「Reset」ボタンは実装できましたがStartボタンを押してからStopボタンを押して再びStartボタンを押すとStartボタンを押してからの時間がリセットされるので00:00.000になります。

Stopボタンを押すまでの時間を保存しておかないといけないのでApp.jsのコードを下記にします。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;

let elapsedTime = 0;  <!--この行を追加-->

function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime+elapsedTime);  <!--この行を編集-->
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    timeoutId = setTimeout(()=>{
      countUp();
    },10);
  };

  const start = () => {

    startTime = Date.now();

    countUp();

  };

  const stop = () => {
    clearTimeout(timeoutId);
    elapsedTime += Date.now() - startTime;  <!--この行を追加-->

  }
  
  const reset = () => {
    timeChange('00:00.000');  
    elapsedTime = 0;  <!--この行を追加-->

  };
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button 
        onStart={start}
        onStop={stop}
        onReset={reset}
        status={status}
      />
    </div>
  );
}

export default App;

これで一連の動作はできるようになりましたがStartボタンを押した時にStartボタンやResetボタンを押せたりStopボタンを押した時にStopボタンを押せたりResetボタンを押した時にResetボタンを押せるのはストップウォッチとしては不自然ですよね。

だから↓みたいにしてボタンを押せない感じをだします。(実際は押すことができます)

ボタンを押せない感じにする

まずは状態ですが↓にします。

const GameStatus = Object.freeze({
init:'init',                  //最初の状態
  start:'start',          //Startボタンを押した状態
  reset:'reset',         //Resetボタンを押した状態
  stop:'stop'           //Stopボタンを押した状態
});

そして状態に応じてボタンのclassNameを切り替えます。

Reactはclassnamesライブラリを使うのを推奨しているみたいですが私は使い方が分からなかったので 記述でやっています。

App.jsのコードを下記にします。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;

let elapsedTime = 0;

//ここから追加
const GameStatus = Object.freeze({
  init:'init',
  start:'start',
  reset:'reset',
  stop:'stop'
});
//ここまで追加


function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const [status,statusChange] = useState(GameStatus.init);  <!--この行を追加-->

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime+elapsedTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    timeoutId = setTimeout(()=>{
      countUp();
    },10);
  };

  const start = () => {

    startTime = Date.now();

    countUp();

    statusChange(GameStatus.start);  <!--この行を追加-->
  };

  const stop = () => {
    clearTimeout(timeoutId);
    elapsedTime += Date.now() - startTime;
    statusChange(GameStatus.stop);  <!--この行を追加-->
  }
  
  const reset = () => {
    timeChange('00:00.000');
    elapsedTime = 0;
    statusChange(GameStatus.init);  <!--この行を追加-->
  };
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button 
        onStart={start}
        onStop={stop}
        onReset={reset}
        status={status}
      />
    </div>
  );
}

export default App;

Button.jsxのコードを下記にします。

import React from 'react';

    const Button = ({onStart,onStop,onReset,status}) => {
      
        return (
            <div className="button">
                <button className={status === 'start' ? status === 'reset' ? 'disable' : 'disable' : 'able'} onClick={onStart}>Start</button>      //この行を編集
                <button className={status === 'start' ? status === 'stop' ? 'disable' : 'able' : 'disable'} onClick={onStop}>Stop</button>  //この行を編集
                <button className={status === 'stop' ? status === 'reset' ? 'disable' : 'able' : 'disable'} onClick={onReset}>Reset</button>     //この行を編集
            </div>
        );
    };

export default Button;

Button.jsxの7行目、8行目、9行目に「?」や「:」がありますが「三項演算子」と言います。

これで完成です。

コードの完成系

最後に全てのコードを載せます。(index.cssは変更していないので載せません)

下記はApp.jsです。

import React,{useState}  from 'react';
import Button from './components/Button';

let startTime;

let timeoutId;

let elapsedTime = 0;

const GameStatus = Object.freeze({
  init:'init',
  start:'start',
  reset:'reset',
  stop:'stop'
});


function App() {
  
  const [time,timeChange] = useState('00:00.000');

  const [status,statusChange] = useState(GameStatus.init);

  const countUp = () => {
    
    const d = new Date(Date.now() - startTime+elapsedTime);
    const m = String(d.getMinutes()).padStart(2,'0');
    const s = String(d.getSeconds()).padStart(2,'0');
    const ms = String(d.getMilliseconds()).padStart(3,'0');

    timeChange(`${m}:${s}.${ms}`);

    timeoutId = setTimeout(()=>{
      countUp();
    },10);
  };

  const start = () => {

    startTime = Date.now();

    countUp();

    statusChange(GameStatus.start);
  };

  const stop = () => {
    clearTimeout(timeoutId);
    elapsedTime += Date.now() - startTime;
    statusChange(GameStatus.stop);
  }
  
  const reset = () => {
    timeChange('00:00.000');
    elapsedTime = 0;
    statusChange(GameStatus.init);
  };
  
  return (
    <div className="App">
      <h1 className="title">ストップウォッチ</h1>
    <div className="time">{time}</div>
      <Button 
        onStart={start}
        onStop={stop}
        onReset={reset}
        status={status}
      />
    </div>
  );
}

export default App;

下記はButton.jsxです。

import React from 'react';

    const Button = ({onStart,onStop,onReset,status}) => {
      
        return (
            <div className="button">
                <button className={status === 'start' ? status === 'reset' ? 'disable' : 'disable' : 'able'} onClick={onStart}>Start</button>
                <button className={status === 'start' ? status === 'stop' ? 'disable' : 'able' : 'disable'} onClick={onStop}>Stop</button>
                <button className={status === 'stop' ? status === 'reset' ? 'disable' : 'able' : 'disable'} onClick={onReset}>Reset</button>
            </div>
        );
    };

export default Button;