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へのデプロイやサービスを公開する為の細かい修正などを行っていきます。

Reactのお仕事に関するご相談

Bageleeの運営会社、palanではReactに関するお仕事のご相談を無料で承っております。
zoomなどのオンラインミーティング、お電話、貴社への訪問、いずれも可能です。
ぜひお気軽にご相談ください。

無料相談フォームへ

0

0

AUTHOR

eishis

Eishi Saito 総務

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

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

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

簡単に自分で作れるWebAR

「palanAR」はオンラインで簡単に作れるWebAR作成ツールです。WebARとはアプリを使用せずに、Webサイト上でARを体験できる新しい技術です。

palanARへ
palanar

palanはWebARの開発を
行っています

弊社では企画からサービスの公開終了まで一緒に関わらせていただきます。 企画からシステム開発、3DCG、デザインまで一貫して承ります。

webar_waterpark

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

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

話を聞いてみたい

運営メンバー

eishis

Eishi Saito 総務

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

sasakki デザイナー

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

yamakawa

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

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

Sayaka Osanai デザイナー

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

はらた

はらた エンジニア

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

kobori

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

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

sasai

ささい エンジニア

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

damien

Damien

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

ゲスト bagelee

ゲスト bagelee

かっきー

かっきー

まりな

まりな

suzuki

suzuki

miyagi

ogawa

ogawa

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

いわもと

いわもと

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

kobari

taishi kobari

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

shogokubota

kubota shogo

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

nishi tomoya

aihara

aihara

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

nagao

SIerを経てアプリのエンジニアに。xR業界に興味があり、unityを使って開発をしたりしています。

kainuma

Kainuma

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

sugimoto

sugimoto

asama

ando

iwasawa ayane

oshimo

oshimo

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

CONTACT PAGE TOP