2018年6月6日

プログラミング

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

目次

  1. はじめに
  2. スピーカーの種類を切り替える
  3. カードのコマンドを発音する
  4. まとめ

はじめに

スマートスピーカーの発音サービスを作りながらReactを学んでいくシリーズ、第7弾です。
前回、フリーワード発音部分を実装していきました。

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

サービスはこちらです。

今回はカードのコマンド発音部分を実装していきます。

スピーカーの種類を切り替える

前回、こちらの図のheader.js部分を実装していきました。
2018-05-29-14.49.43.png (430.6 kB)

今回はmain.js部分を実装していきます。

さて、main.jsですが、コンポーネントの中に更にコンポーネントを分割し(子コンポーネント化)、1つ1つのカードをspeaker_button.jsにしていきます。
 2018-06-05 23.47.12.png (107.0 kB)

スピーカー選択タブ

では、main.jsのスピーカー選択タブ部分を実装していきます。
ここは前回の記事と同じくラジオボタンを使っていきます。
以下のような流れでラジオボタンを作りましたね。

  1. constructorでStateにデフォルト値(alexa)を設定する
  2. render内のJSXでラジオボタンを作る。
    1で設定したStateの値を使用し、alexaの場合にはalexaのラジオボタンのcheckedにtrueが入るように、googleの場合はgoogleのラジオボタンのcheckedにtrueが入るようにする
  3. ラジオボタンが押されたらonChangeで設定したイベントが発動する
  4. イベント内でStateの更新が行われ、ラジオボタン部分が再レンダリングされる
  5. 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_alexamain_googleとしている点です。
header.js内でラジオボタンにalexagoogleを使用している為、同じ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へのデプロイやサービスを公開する為の細かい修正などを行っていきます。

0

0

AUTHOR

eishis

Eishi Saito 総務

SIerやスタートアップ、フリーランスを経て2016年11月にeishis, Inc.を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。

アプリでもっと便利に!気になる記事をチェック!

記事のお気に入り登録やランキングが表示される昨日に対応!毎日の情報収集や調べ物にもっと身近なメディアになりました。

palanでは一緒に働く仲間を募集しています

正社員や業務委託、アルバイトやインターンなど雇用形態にこだわらず、
ベテランの方から業界未経験の方まで様々なかたのお力をお借りしたいと考えております。

話を聞いてみたい

運営メンバー

eishis

Eishi Saito 総務

SIerやスタートアップ、フリーランスを経て2016年11月にeishis, Inc.を設立。 マーケター・ディレクター・エンジニアなど何でも屋。 COBOLからReactまで色んなことやります。

sasakki デザイナー

アメリカの大学を卒業後、日本、シンガポールでデザイナーとして活動。

しまだ

しまだ デザイナー

WebAR/VRのデザインと3DCG制作がメインです。 肩書きは「アニメ案件に関わりたいデザイナー」。

Miu マーケター

ドイツでWEBマーケティングしています。

しんのき エンジニア

主に React Native を使ったアプリ開発と AWS や Firebase を使ったサーバーレスアーキテクチャを担当しています。元々はインフラとかPHPをやっていました。

yamakawa

やまかわたかし デザイナー

フロントエンドデザイナー。デザインからHTML / CSS、JSの実装を担当しています。最近はReact NativeやReact360をよく触っています。

furuya エンジニア

サーバーサイド、フロントエンド、Unityと色々手を出してる雑食系エンジニア。ReactNativeが最近のマイブーム。

Sayaka Osanai デザイナー

Sketchだいすきプロダクトデザイナー。シンプルだけどちょっとかわいいデザインが得意。 好きな食べものは生ハムとお寿司とカレーです。

はらた

はらた エンジニア

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

うえまつゆい エンジニア

サーバーサイドエンジニアからフロントエンドエンジニアになりました。主にReact Nativeでのアプリ開発をしています。

kobori

こぼり ともろう エンジニア

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

sasai

ささい エンジニア

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

damien

Damien

WebAR/VRを中心に企画やディレクションやエンジニアもちょっとやっています。森に住んでいます。

デザイナーゲスト

ゲスト デザイナー

CONTACT PAGE TOP