2018年5月22日

プログラミング

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

目次

  1. はじめに
  2. コマンドデータをファイルにまとめる
  3. 発音ボタンを展開する
  4. まとめ

はじめに

Reactでスマートスピーカーの発音サービスを作るシリーズの第4弾です。
前回は発音をするボタンを作り、Propsを渡すことでAlexa、Google Home用のコマンドを実行してくれるように開発しました。

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

今回は、いくつかのコマンドをファイルにまとめ、そちらをループで発音ボタンとして展開する方法をご紹介します。

完成形はこのようなイメージです。
 2018-05-19 18.50.24.png (124.4 kB)

コマンドデータをファイルにまとめる

前回まではこのように、音楽をかけるコマンドを個別にsrc/components/speaker_button.jsに記載していました。

      <div className="col-sm-6 mb-2 mt-2">
        <button className="card" onClick={() => this._onClick()}>
          <div className="card-body">
            <h4 className="card-title text-left">
              <i className="fas fa-play"></i> 
              音楽かけて
            </h4>
            <p className="card-text text-left">
              ランダムで音楽をかけてくれるコマンドです。
            </p>
          </div>
        </button>
      </div>

ですが、例えば「音楽かけて」以外に10個とかコマンドを並べたいとき、10個speaker_button.js内にコマンドを記載していくのは、とても見づらくメンテナンスしづらくなってしまいます。
そこで、コマンド自体を別のファイルにまとめてしまい、そちらをspeaker_button.js内で展開していくことにしましょう。

今回は、src配下にconst(constant値、つまり定数として変わらない値)ディレクトリを作り、その中にalexa.jsgoogle.jsというファイルを作ります。

src/const/alexa.js

export const Alexa = {
  pre_commands: 'アレクサ、',
  commands: [
    {
      title: '音楽かけて',
      pronunciation: 'おんがくかけて',
      description: 'ランダムで音楽をかけてくれるコマンドです。',
    },
    {
      title: '音楽とめて',
      pronunciation: 'おんがくとめて',
      description: '再生中の音楽を停止するコマンドです。',
    },
    {
      title: '音量上げて',
      pronunciation: 'おんりょうあげて',
      description: '音量を上げてくれるコマンドです。',
    },
    {
      title: '音量下げて',
      pronunciation: 'おんりょうさげて',
      description: '音量を下げてくれるコマンドです。',
    },
    {
      title: '洋楽流して',
      pronunciation: '洋楽ながして',
      description: '洋楽をかけてくれるコマンドです。',
    },
    {
      title: 'ジャズ流して',
      pronunciation: 'ジャズながして',
      description: 'ジャズ音楽をかけてくれるコマンドです。',
    },
    {
      title: 'この曲名を教えて',
      pronunciation: 'この曲名を教えて',
      description: '現在流れている曲名を教えてくれるコマンドです。',
    },
    {
      title: 'この曲をリピートして',
      pronunciation: 'この曲をリピートして',
      description: '現在流れている曲をリピート再生してくれるコマンドです。',
    },
  ]
}

src/const/google.js

export const Google = {
  pre_commands: 'OK Google、',
  commands: [
    {
      title: '音楽かけて',
      pronunciation: 'おんがくかけて',
      description: 'ランダムで音楽をかけてくれるコマンドです。',
    },
    {
      title: '音楽とめて',
      pronunciation: 'おんがくとめて',
      description: '再生中の音楽を停止するコマンドです。',
    },
    {
      title: '音量上げて',
      pronunciation: 'おんりょうあげて',
      description: '音量を上げてくれるコマンドです。',
    },
    {
      title: '音量下げて',
      pronunciation: 'おんりょうさげて',
      description: '音量を下げてくれるコマンドです。',
    },
    {
      title: '洋楽流して',
      pronunciation: '洋楽ながして',
      description: '洋楽をかけてくれるコマンドです。',
    },
    {
      title: 'ジャズ流して',
      pronunciation: 'ジャズながして',
      description: 'ジャズ音楽をかけてくれるコマンドです。',
    },
    {
      title: 'この曲名を教えて',
      pronunciation: 'この曲名を教えて',
      description: '現在流れている曲名を教えてくれるコマンドです。',
    },
    {
      title: 'この曲をリピートして',
      pronunciation: 'この曲をリピートして',
      description: '現在流れている曲をリピート再生してくれるコマンドです。',
    },
  ]
}

AlexaとGoogle Homeではコマンドは異なるものもある為、現時点では全て一緒ですがファイルを分けることにします。
では中身を見ていきます。

export const Alexa = {
export const Google = {
ここではconst変数としてexportしています。

pre_commands はコマンド実行前の呼びかけの文言ですね。

その下にcommandで実行するコマンドを配列で格納しています。
項目を見ていきます。
title: はリストを表示するときの見出し(タイトル)部分です。
pronunciationはJavaScriptで発音させる項目です。音読み訓読み等、正しく発音してくれない可能性を考えて、私たちが見るようの見出しとJavaScriptで発音させる項目を分けています。
descriptionは説明部分です。

発音ボタンを展開する

では、これらのconstファイルをspeaker_button.js内で呼び出していきましょう。
まずimportしていきます。

import {Alexa} from '../const/alexa';
import {Google} from '../const/google';

では、次にconst値を展開していきます。
コマンドを格納していたのはcommandsという配列でしたね。
配列を展開する為には、mapを仕様します。

  Alexa.commands.map((command) => {
    return (
      <div className="col-sm-6 mb-2 mt-2">
        <button className="card" onClick={() => this._onClick(command.pronunciation)}>
          <div className="card-body">
            <h4 className="card-title text-left">
              <i className="fas fa-play"></i> 
              {command.title}
            </h4>
            <p className="card-text text-left">
              {command.description}
            </p>
          </div>
        </button>
      </div>
    );
  })
);

最初のAlexa.commands.map((command) => でAlexaのcommandsの中身を展開してcommandに格納しています。
これでcommandのキーであるtitleやdescription、pronunciationを使用し、値を取得することができます。

<button className="card" onClick={() => this._onClick(command.pronunciation)}>
前回までのコードでは発音ボタンでどの発音をするか、特に指定が必要ありませんでした。
ですが、今回はどの発音をするか情報が必要なので、_onClickの引数としてcommandのpronounciationを指定しています。

{command.title}
{command.description}
これらは問題ないですね。commandのtitle、descriptionの値を取り出しています。

さて、今回Alexa.commandsとAlexaを指定しましたが、Googleの場合はGoogle.commandsとする必要がありそうです。
この為にもう一度Google用の処理を書くのは二度手間になりそうです。

なので、今回はこのコンポーネントの初期化処理時に呼び出し元の引数を使用し、Alexa、Googleいずれかの値を格納し、そこからcommandsを展開する形が良さそうです。

constructor

constructorはオブジェクト指向プログラミングをしている方は馴染み深いと思います。
オブジェクト生成時に初期化などを行うメソッドです。
今回の場合、Reactのコンポーネント生成時に呼ばれる初期化用メソッドと考えていただくのが良いです。

早速AlexaとGoogle用のconstructorを書いていきます。

  constructor(props) {
    super(props);
    switch (this.props.speaker_type) {
      case 'alexa': {
        this.speaker_type = Alexa;
        break;
      }
      case 'google': {
        this.speaker_type = Google;
        break;
      }
      default:
      break;
    }
  }

constructor(props)
constructorは引数としてpropsを取ります。

super(props);
superキーワードは親クラス、ここではComponentクラスのコンストラクタを表しています。
Componentクラスを継承するためには、コンストラクタ内で必ず呼び出す必要があります。

POINT!!

今までconstructorやsuper(props)といったものは書かなくても、this.props.hoge とthis.propsを使用できていました。これはなぜでしょうか?

実はconstructorの中身がsuper(props)の場合にはconstructorを省略することができるのです。

さて、super(props) によってthis.props.speaker_type でspeaker_typeを取得できるようになりました。
これでcase文を作り、this.speaker_type にAlexaかGoogleを格納しましょう。

   switch (this.props.speaker_type) {
      case 'alexa': {
        this.speaker_type = Alexa;
        break;
      }
      case 'google': {
        this.speaker_type = Google;
        break;
      }
      default:
      break;
    }

明示的にbreakを入れるのを忘れないようにしましょう!

これでthis.speaker_typeにAlexaかGoogleかが入るようになりました。
これを使用し、先程のmap部分で使用していきます。
this.speaker_type.commands.map((command)

では、最後に_onClick部分を修正していきます。
先程、このようにpronunciation を引数として渡していました。
<button className="card" onClick={() => this._onClick(command.pronunciation)}>

このpronunciation(発音内容)を使用し、発音させていきましょう。

  _onClick(pronunciation) {
    let ssu = new SpeechSynthesisUtterance();
    ssu.text = this.speaker_type.pre_commands + pronunciation;
    ssu.lang = 'ja-JP';
    speechSynthesis.speak(ssu);
  }

特に難しい点はありませんね!
では、speaker_button.jsを通しで見ていきます。

import React, { Component } from 'react';
import {Alexa} from '../const/alexa';
import {Google} from '../const/google';

export default class SpeakerButton extends Component {
  constructor(props) {
    super(props);
    switch (this.props.speaker_type) {
      case 'alexa': {
        this.speaker_type = Alexa;
        break;
      }
      case 'google': {
        this.speaker_type = Google;
        break;
      }
      default:
      break;
    }
  }

  _onClick(pronunciation) {
    let ssu = new SpeechSynthesisUtterance();
    ssu.text = this.speaker_type.pre_commands + pronunciation;
    ssu.lang = 'ja-JP';
    speechSynthesis.speak(ssu);
  }

  render() {
    return (
      Alexa.commands.map((command) => {
        return (
          <div className="col-sm-6 mb-2 mt-2">
            <button className="card" onClick={() => this._onClick(command.pronunciation)}>
              <div className="card-body">
                <h4 className="card-title text-left">
                  <i className="fas fa-play"></i> 
                  {command.title}
                </h4>
                <p className="card-text text-left">
                  {command.description}
                </p>
              </div>
            </button>
          </div>
        );
      })
    );
  }
}

これで、今回のイメージは完成ですね!

まとめ

今回はデータをconstファイルに格納し、そちらをmapで展開しました。
またconstructor内で初期化処理としてスピーカーのタイプを格納し、使用する方法も解説しました。

次回はよりUIをリッチにし、stateを使ったレンダリングの使い方を見ていきます。

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