2018年6月6日
Reactでスマートスピーカーのコマンド発音サービスを作ろう(7)【作りながら覚えるReact】


はじめに
スマートスピーカーの発音サービスを作りながらReactを学んでいくシリーズ、第7弾です。
前回、フリーワード発音部分を実装していきました。
サービスはこちらです。
今回はカードのコマンド発音部分を実装していきます。
スピーカーの種類を切り替える
前回、こちらの図のheader.js部分を実装していきました。

今回はmain.js部分を実装していきます。
さて、main.jsですが、コンポーネントの中に更にコンポーネントを分割し(子コンポーネント化)、1つ1つのカードをspeaker_button.jsにしていきます。

スピーカー選択タブ
では、main.jsのスピーカー選択タブ部分を実装していきます。
ここは前回の記事と同じくラジオボタンを使っていきます。
以下のような流れでラジオボタンを作りましたね。
- constructorでStateにデフォルト値(alexa)を設定する
- render内のJSXでラジオボタンを作る。
 1で設定したStateの値を使用し、alexaの場合にはalexaのラジオボタンのcheckedにtrueが入るように、googleの場合はgoogleのラジオボタンのcheckedにtrueが入るようにする
- ラジオボタンが押されたらonChangeで設定したイベントが発動する
- イベント内でStateの更新が行われ、ラジオボタン部分が再レンダリングされる
- Stateの値が変わることでcheckedの判定が変わり、選択した部分がcheckedと判定されるようになる
では、実際にコードを見ていきましょう。
import React, { Component } from 'react';
export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      speaker_type: "alexa",
    };
    this._toggleButton = this._toggleButton.bind(this);
  }
  _toggleButton(e) {
    this.setState({speaker_type: e.target.value});
  }
  render() {
    return (
      <div className="main">
        <div className="container">
          <div className="tabs">
            <input
              type="radio"
              value="alexa"
              name="tab_item"
              id="main_alexa"
              checked={this.state.speaker_type === "alexa"}
              onChange={this._toggleButton}
            />
            <label className="tab_item" htmlFor="main_alexa">
              <i className="fab fa-amazon"></i> Alexa
            </label>
            <input
              type="radio"
              value="google"
              name="tab_item"
              id="main_google"
              checked={this.state.speaker_type === "google"}
              onChange={this._toggleButton}
            />
            <label className="tab_item" htmlFor="main_google">
              <i className="fab fa-google"></i> Google Home
            </label>
          </div>
        </div>
      </div>
    );
  }
}
先程解説した流れの通りになっている為、難しい点はないと思います。
注意点としては、idをmain_alexa、main_googleとしている点です。
header.js内でラジオボタンにalexaとgoogleを使用している為、同じIDを使用すると重複してしまい上手く動かなくなります。
カードのコマンドを発音する
次にカード部分のspeaker_button.jsを実装していきます。
今回は1つ1つのカードをspeaker_button.jsの子コンポーネントに分ける為、以前実装したconstのファイルをmapしていく処理はmain.js側に書いていきます。
main.jsでカードを展開する
main.js
import SpeakerButton from './speaker_button';
import {Alexa} from '../const/alexa';
import {Google} from '../const/google';
まずSpeakerButton、AlexaとGoogleのデータをインポートしていきます。
<div className="tab_content" id="alexa_content">
  <div className="row">
    {Alexa.commands.map((command, i) => {
      return (
        <SpeakerButton
          key={i}
          title={command.title}
          description={command.description}
          pronunciation={command.pronunciation}
          pre_commands={Alexa.pre_commands}
        />
        );
      })
    }
  </div>
</div>
<div className="tab_content" id="google_content">
  <div className="row">
    {Google.commands.map((command, i) => {
      return (
        <SpeakerButton
          key={i}
          title={command.title}
          description={command.description}
          pronunciation={command.pronunciation}
          pre_commands={Google.pre_commands}
        />
        );
      })
    }
  </div>
</div>
{Alexa.commands.map((command, i) => {
こちらは以前の記事でもあった、コマンドの展開をmapメソッドで行っています。
command は展開されたデータですが、その後ろのiはインデックス(番号)が入ります。
  return (
    <SpeakerButton
      key={i}
      title={command.title}
      description={command.description}
      pronunciation={command.pronunciation}
      pre_commands={Alexa.pre_commands}
    />
    );
こちらではSpeakerButtonコンポーネントを呼び出しています。
Propsとしてkey、title、description、pronunciation、pre_commandsを渡しています。
POINT!!
mapで展開したインデックスをkeyとしてコンポーネントになぜ渡しているのでしょうか。
ReactではVirtualDOMという仮想のDOMを保持し、実際に更新の対象となる部分(差分)だけDOM更新を行ってくれると以前の記事で解説しました。
この差分を検知する為、どのアイテムが差分が出たか特定する為にkeyが必要なのです。
keyが無いとどのアイテムか特定できず、展開したものを全てDOM更新してしまう為に効率が悪くなってしまいます。
ちなみにkeyを設定していなかった場合はブラウザのconsole上にワーニングが出てきます。
SpeakerButtonコンポーネント
では、speaker_button.jsを見ていきましょう
import React, { Component } from 'react';
export default class SpeakerButtons extends Component {
  _onClick(pronunciation) {
    let ssu = new SpeechSynthesisUtterance();
    ssu.text = this.props.pre_commands + pronunciation;
    ssu.lang = 'ja-JP';
    speechSynthesis.speak(ssu);
  }
  render() {
    return (
      <div className="col-sm-6 mb-2 mt-2">
        <button className="card" onClick={() => this._onClick(this.props.pronunciation)}>
          <div className="card-body">
            <h4 className="card-title text-left">
              <i className="fas fa-play"></i> 
              {this.props.title}
            </h4>
            <p className="card-text text-left">
              {this.props.description}
            </p>
          </div>
        </button>
      </div>
    );
  }
}
ここも今までのシリーズを見ていただければ、そんなに難しい点はないかと思います。
渡ってきたPropsの値を持ってカードを展開し、押された際に_onClickを呼び出し、そこで発音をしてくれるという流れです。
main.js
改めて今回修正したmain.jsはこちらです。
import React, { Component } from 'react';
import SpeakerButton from './speaker_button';
import {Alexa} from '../const/alexa';
import {Google} from '../const/google';
export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      speaker_type: "alexa",
    };
    this._toggleButton = this._toggleButton.bind(this);
  }
  _toggleButton(e) {
    this.setState({speaker_type: e.target.value});
  }
  render() {
    return (
      <div className="main">
        <div className="container">
          <div className="tabs">
            <input
              type="radio"
              value="alexa"
              name="tab_item"
              id="main_alexa"
              checked={this.state.speaker_type === "alexa"}
              onChange={this._toggleButton}
            />
            <label className="tab_item" htmlFor="main_alexa">
              <i className="fab fa-amazon"></i> Alexa
            </label>
            <input
              type="radio"
              value="google"
              name="tab_item"
              id="main_google"
              checked={this.state.speaker_type === "google"}
              onChange={this._toggleButton}
            />
            <label className="tab_item" htmlFor="main_google">
              <i className="fab fa-google"></i> Google Home
            </label>
            <div className="tab_content" id="alexa_content">
              <div className="row">
                {Alexa.commands.map((command, i) => {
                  return (
                    <SpeakerButton
                      title={command.title}
                      description={command.description}
                      pronunciation={command.pronunciation}
                      pre_commands={Alexa.pre_commands}
                    />
                    );
                  })
                }
              </div>
            </div>
            <div className="tab_content" id="google_content">
              <div className="row">
                {Google.commands.map((command, i) => {
                  return (
                    <SpeakerButton
                      key={i}
                      title={command.title}
                      description={command.description}
                      pronunciation={command.pronunciation}
                      pre_commands={Google.pre_commands}
                    />
                    );
                  })
                }
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
まとめ
今回は子コンポーネント(カード部分)にmapで展開した値をPropsで渡していきました。
次回はGitHub Pagesへのデプロイやサービスを公開する為の細かい修正などを行っていきます。
のお仕事に関するご相談
Bageleeの運営会社、palanではに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。
この記事は
参考になりましたか?
0
0
関連記事

2022年6月14日
Figmotionの使い方

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

2022年3月25日
【Spoke】アプリ不要!WebだけでVR空間を作ろう!

2022年2月25日
palanXRサイトリニューアルの流れをご紹介します!

2021年12月25日
vanilla-extractで型安全なCSSを書こう!

2021年12月24日
Miroを使ったプロジェクトの振り返り
簡単に自分で作れる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をメインに担当してます。 これからたくさん吸収していきます!









 
 
 
 
