20210620のReactに関する記事は4件です。

AWS Amplify - 最速でログイン機能を実装してみた ① AWS Amplifyとは?

今回は、巷で話題になっているAWS製mBaaSを使って最速でログイン機能を実装してみました。 便利な世の中になったなぁというのが一番の感想ですが、とにかくアプリを最速でリリースしたい方にすごくおすすめのサービスだなと思いました。 チュートリアル動画はこちら 元記事はこちら AWS Amplifyとは? AWSを使用したスケーラブルなWeb・モバイルアプリ開発のためのmBaaS(Mobile backend as a Service)なフレームワークです。 Amplifyを用いることで、複雑なバックエンドの構築、アプリケーションとの統合を簡略化し、 バックエンドとインフラをAWSにサーバーレスで任せることができます。また、CI/CD環境の構築も自動で構築してくれまので、 インフラ構築からデプロイまでシームレスに任せることができます。 特に、React, Vue, AngularなどでのSPAや、Gatsby, NextJS、 NuxtJSなどの静的サイトジェネレーターを用いたWebアプリケーション開発の事例がたくさんあります。 AWS Amplifyの3つの構成要素 Amplify CLI Amplify Framework Amplify Console Amplify CLI Amplify CLI は、AWS CloudFormationやInfrastructure as Code の仕組みを利用することなく、コマンドラインから対話的に質問に回答するだけで、バックエンドを構築することができるインターフェースです。 また、Amplify CLIは、バックエンドとの連携に必要な設定ファイルやソースコードを自動で 生成します。 Amplify CLIによって、下記機能を構築することができます。 - 認証機能 - バックエンドAPI - Websocketを使ったリアルタイム通信 - ホスティング Amplify Framework Amplify Framreworkは、クライアントがAWSのバックエンドと連携するための処理を数行で実装できるようにするフレームワークです。 - ライブラリ、UIコンポーネント、CLIを含むOSSのクライアントフレームワーク - 数行でバックエンドと連携するための処理を実装 - iOS、Android、Web(React/Vue/Angular)、React Native - それぞれに最適化されたライブラリ、UIコンポーネントを提供 Amplify Console サーバーレスウェブアプリケーションのデプロイ・ホスティングをするための サービス - SPA (シングルページアプリケーション)や静的サイトのホスティング - CI/CD の運用を自動化 - GithubやAWS CodeCommitなどのソースリポジトリと連携 - ブランチごとにテスト環境を自動で構築 - 特定の環境へのBasic認証の付与 AWS Amplifyのメリット バックエンドをすばやく構成 最速でサービスをリリースできる 柔軟性が高い フロントエンドとのシームレスな接続 数クリックでデプロイ バックエンドをすばやく構成 直観的なワークフローで、認証、ストレージ、データを含む、スケーラブルな バックエンドを素早くセットアップできます。 最速でサービスをリリースできる 本質的な開発に注力することが可能 柔軟性が高い Amplify consoleを使うと、テスト環境を自動で構築したり、認証を付与するといった、柔軟な開発フローの設計ができます フロントエンドとのシームレスな接続 数行のコードで、アプリケーションを、新規もしくは既存のAWS リソースと接続可能 数クリックでデプロイ Amplify コンソールを使用すると、数クリックでレポジトリと接続しアプリケーションをホスティングできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript + Reactを勉強するために数独を解くアプリを作った話(画面作る編)

TypeScriptとReactを勉強するための題材として、 解けなくてイラついていた 数独の問題を解かせてみようという試みのアウトプットです。 この記事では画面を作るところまで書いています。 解くアルゴリズムとしてバックトラック法や総当りがありますが、 TypeScriptをできるだけ書きたいので、自分の思考をトレースする様なプログラムでいきたいなぁと思っています。 プロジェクト作成 npx create-react-app --template typescript sudoku Reactの型定義をインストール npm install --save @types/react これをやっておかないと、後でxlsxのインストールをしたときに 「import React from ‘react’ モジュール ‘react’ の宣言ファイルが見つかりませんでした。」と言われます。 画面部品を作る 数独は9つのグループがあり、1つのグループには9つのマスという構成です。 また、各マスで取りうる可能性が消えていくのを見てニヤつきたいので、 1マス内の9か所に残った可能性を表示できるようにします。 テンキーの様に9か所に配置できる部品として共通化したら楽と思ったので作ります。 nineTable.tsx import "./nine-table.css" /** propsの型定義 */ type NineTableProps = { /** 並べたい部品 */ components: JSX.Element[]; }; /** * 9か所に部品を配置する部品 * @param props 配置する9つのコンポーネント * @returns */ export default function NineTable(props: NineTableProps) { const components = props.components; return ( <div className="nine-table"> <div className="nine-table-row"> <div className="one nine-table-item"> {components[0]} </div> <div className="two nine-table-item"> {components[1]} </div> <div className="three nine-table-item"> {components[2]} </div> </div> <div className="nine-table-row"> <div className="four nine-table-item"> {components[3]} </div> <div className="five nine-table-item"> {components[4]} </div> <div className="six nine-table-item"> {components[5]} </div> </div> <div className="nine-table-row"> <div className="seven nine-table-item"> {components[6]} </div> <div className="eight nine-table-item"> {components[7]} </div> <div className="nine nine-table-item"> {components[8]} </div> </div> </div> ); } nine-table.css .nine-table { display: table; } .nine-table-row { display: table-row; } .nine-table-item { display: table-cell; } 定数クラス 1~9の整数を持つ配列をよく使うので、名前空間に定数クラスっぽいものを作っておきます const.tsx export namespace Const { export const NINE_VALUES:number[] = [1,2,3,4,5,6,7,8,9]; } 1マスを作る 消えた可能性や確定した値といった状態を持つCellDataクラスを受け取り、 CellDataクラスの中身を描画するCellクラスを作成します ViewModeプロパティで、残りの可能性を表示するモードと、確定した値を表示するモードを切り替えれる様にします cell.tsx import React from "react"; import CellData from "../models/cellData"; import ViewMode from "../models/viewMode"; import NineTable from "./nineTable" import './cell.css' import { Const } from "../models/const"; /** Cellのprops型定義 */ type CellProps = { /** Cellに表示する1マス内の状態 */ cellData: CellData; }; /** * 1マスを表示する部品 */ export default class Cell extends React.Component<CellProps>{ /** * CellDataの内容を表示 * ViewModeによって表示を切り替え */ render() { const cellData: CellData = this.props.cellData; return ( <div className="cell-content"> { cellData.viewMode === ViewMode.Value ? this.showValue() : this.showPossibles()} </div> ); } /** 確定値を表示 */ private showValue = () => { const value = this.props.cellData.value ? this.props.cellData.value : 0; return ( <div className={`cell-value ${value ? null : "hidden"}`}> { value } </div> ); } /** まだ残っている可能性の表示 */ private showPossibles = () => { const components = Const.NINE_VALUES.map( n => { const value = this.props.cellData.possibles.includes(n) ? n : 0 ; return <span className={`possible-value ${value ? null : "hidden"}`}>{value}</span> } ); return( <NineTable components={components} ></NineTable> ); } } cell.css .possible-value { font-size: 20px; } .cell-content > .nine-table , .cell-value { width: 85px; height: 85px; text-align: center; vertical-align: middle; } .cell-value { display: table-cell; font-size: 40px; } .cell-content{ border: 1px solid gray; } .hidden { color: white; } cellData.tsx import ViewMode from "./viewMode"; /** * 1マスの状態を管理する */ export default class CellData { /** 1マスを特定するためのid */ id: number; /** 所属するグループのID */ groupId:number; /** 行位置を示す(1~9) */ rowId:number; /** 列位置を示す(1~9) */ columnId:number; /** 1マスの値/可能性 表示モード */ viewMode: ViewMode = ViewMode.Value; /** 確定した値(0は未確定) */ value: number = 0; /** 取りうる可能性 */ possibles: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; constructor(id: number, groupId:number, rowId:number, columnId:number){ this.id = id; this.groupId = groupId; this.rowId = rowId; this.columnId = columnId; } /** 可能性を消す */ erasePossible(n: number){ this.possibles = this.possibles.filter( p => p !== n); } } viewMode.tsx /** 1マスの表示モードを列挙する */ enum ViewMode { /** 値表示 */ Value, /** 可能性表示 */ Possibles }; export default ViewMode; export default enum とは書けないらしい また、使われていないenumが存在する時、トランスパイラが不要と判断できず削除軽量化してくれないので、 enumは使わない方が良いとのこと。使えばよかろうなのだ 1グループ作る 9つのCellDataを受け取り、 先に作ったNinePanelを利用して1グループを表示する部品を作ります。 group.tsx import CellData from "../models/cellData"; import Cell from "./cell"; import NineTable from "./nineTable"; import "./group.css"; /** Groupのprops型定義 */ type GroupProps = { /** 表示する各マスの情報 */ cellDatas: CellData[]; } /** * 1グループを表示する部品 * @param props * @returns */ export default function Group(props: GroupProps) { /** 9マスを生成 */ const cells = props.cellDatas.map(c => ( <div className="cell"> <Cell cellData={c}></Cell> </div> )); return ( <div className="group"> <NineTable components={cells}></NineTable> </div> ); } group.css .group{ border: 2px solid black; } メイン画面を作る プロジェクト作成時に作られたApp.tsxを改造してメイン画面を作ります 初期画面は、適当にCellDataを81個生成してGroupに渡します App.tsx import './App.css'; import NineTable from './components/nineTable'; import CellData from './models/cellData'; import Group from './components/group'; import React from 'react'; import ViewMode from './models/viewMode'; import {Const} from './models/const'; export default class App extends React.Component < {}, {cellDatas: CellData[]}> { constructor(props: any){ super(props); //作成したダミーのCellDataを保持 this.state = { cellDatas: this.createDummyCellDatas() } } render() { //groupId毎にGroupを生成 let groups = Const.NINE_VALUES.map( n => this.state.cellDatas.filter(c=> c.groupId === n)) .map( cells => <Group cellDatas={cells} />); return ( <div className="App"> <NineTable components={groups} /> <div> <input type="file" onChange={this.handleChange} accept=".xlsx" /> </div> <div className="controller"> <button onClick={this.changeViewMode} className="control-button">Change</button> <button onClick={this.executeNext} className="control-button">Execute</button> </div> </div> ); } /** * 1マスの表示切替 */ private changeViewMode = () => { let cellDatas: CellData[] = this.state.cellDatas; cellDatas.forEach( c => c.viewMode = c.viewMode === ViewMode.Value ? ViewMode.Possibles : ViewMode.Value); this.setState({cellDatas: cellDatas}); } /** 適当にダミーのCellDataを81個作る */ private createDummyCellDatas = (): CellData[] => { let value = 1; return Array.from(Array(81), (_,k) => k).map( i => { let groupId = Math.floor(i/9) + 1; let cellData = new CellData(i+1, groupId, 0, 0); cellData.value = value; cellData.erasePossible(value); // 表示のテスト用に一つだけ可能性を消している value = value === 9 ? 1 : value+1; return cellData; }); } /** ファイル選択時イベント 後で実装 */ private handleChange = (e: any) => {} /** * 確定値の探索 * データ処理編で実装する */ private executeNext = () : void => {} } App.css .App { margin: 10px 0px 0px 10px; width: 800px; } .controller { text-align: right; padding-top: 20px; } .control-button{ width: 70px; height: 70px; } .controller :first-child{ margin-right: 5px; } 実行 npm start 初期表示 Changeで切り替え ファイル読み込み サボっていたファイル読み込みを実装します データの作成はExcelで作るのが楽と思ったので、Excelファイル(xlsx)を読み込む前提です xlsxのインストール npm install xlsx App.tsx import XLSX , {utils} from 'xlsx'; //追加 export default class App extends React.Component < {}, {cellDatas: CellData[]}> { private groupIds = [1,4,7,28,31,34,55,58,61].map( x => [x,x+1,x+2,x+9,x+10,x+11,x+18,x+19,x+20] ); //追加 constructor(props: any){ ... //追加 this.handleChange = this.handleChange.bind(this); this.readXlsxFile = this.readXlsxFile.bind(this); } /** * ファイル選択時イベント * @param e * @returns */ private handleChange(e: any) { if(!e || !e.target){ return; } const files = e.target.files; if(files && files[0]) this.readXlsxFile(files[0]); }; /** XLSXファイルを読み込み、CellDataを生成してstateに格納する */ private readXlsxFile= (file: File) => { const reader = new FileReader(); reader.onload = (e) => { if(!e || !e.target){ return; } const bstr = e.target.result; const wb = XLSX.read(bstr, {type: 'array'}); const ws = wb.Sheets[wb.SheetNames[0]]; //1シート目を読み込む let cellId = 1; let cells: CellData[] = []; Const.NINE_VALUES.forEach( rowId => Const.NINE_VALUES.forEach( columnId => { //xlsxは0ベース const adress = utils.encode_cell({ c: columnId - 1, r: rowId -1 }); const targetCell = ws[adress]; let value: number = 0; if(targetCell){ value = targetCell.v; }; let groupId = this.getGroupId(cellId); let cell = new CellData(cellId, groupId, rowId, columnId); cell.value = value; if(value){ cell.possibles = [value]; } cells.push(cell); cellId ++; }) ); this.setState({ cellDatas: cells }); } reader.readAsArrayBuffer(file); } /** * セルIDからGroup IDを取得する * @param cellId セルID * @returns */ private getGroupId(cellId: number): number { for(let i=0;i<9;i++){ if(this.groupIds[i].includes(cellId)){ return i + 1; } } return -1; } ファイル読み込み実行 以下の様なファイルを選択すると このような表示になります 以上で画面作成が完了です。stateにあるCellData[]を更新すれば画面に反映されます。 データ処理編に続きます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

関数コンポーネントで学ぶReact公式チュートリアル

関数コンポーネント版React公式チュートリアル Reactの公式チュートリアルではクラスコンポーネントが使われていますが、最近は関数コンポーネントを利用することがほとんどだと思います。折角初心者の方に教えるのであれば、使えるものを教えたいと思ったので、公式チュートリアルを一通り関数コンポーネントを使った形に書き換えてみました。 以下にはコードと関数コンポーネントで書く際の注意点のみ記述します。 コードの詳しい解説については、公式の方を確認してください。 スターターコード サンプルコードに関してはCodeSandboxに用意しました。以降で操作するファイルは、src/index.jsになります。 CodeSandbox 描画したいJSXを、クラスコンポーネントではrenderメソッドに記述しましたが、関数コンポーネントでは返り値とするのでreturn文のなかに記述します。 また、以下のコードでは関数の定義は、基本的にアロー関数を変数に代入する形でおこないます。 const コンポーネント名=(props)=>{ /* 処理 */ return <Component value={props.value}/> } この場合、定義順などに気をつける必要がありますが、同名コンポーネントの定義を防止できるなど利点もあるので、この形を利用していきます。 JavaScriptの関数定義に興味のある方は、@raccy様の記事を参考にしてみてください。 ゲームを完成させる データをProps経由で渡す BoardのrenderSqueareを以下のように書きえましょう。 const renderSquare = (i) => <Square value={i} />; こうすることでSquareの中で、引数iの値をprops.valueとして扱うことができます。実際にSquareを書き換えてprops.valueの値を反映させてみましょう。 const Square = (props) => { return <button className="square">{props.value}</button>; }; 各マスに数字が表示されるようになったと思います。 インタラクティブなコンポーネントを作る クラスコンポーネントではコンストラクタのthis.stateでまとめてステートを管理しますが、関数コンポーネントではHook(今回は'useState'関数)と言われるものを利用して一つ一つステートを定義します。 const [state, setState]=useState(初期値); 変数名(state)は何でもいいですが、setStateの部分は必ずset+変数名(キャメルケース)にしてください)。 ステートの値が使いたい場合は、普通の変数と同じようにstateを使いましょう。 しかし、ステートの値を変更する際は直接代入するのではなく、各ステートに対応した関数を使う点には注意してください。上記のコードを用いると以下のようになります。 //Bad state=1; //これだとDOMが更新されない //そもそもconstで定義しているので代入はできませんが・・・ //Good setState(1); //ちゃんとDOMが更新されます Squareを書き換えると以下のようになります。 const Square = (props) => { const [value, setValue] = useState(""); return ( <button className="square" onClick={() => setValue("X")}> {value} </button> ); }; State のリフトアップ これについては、ステートの定義方法が違うだけでクラスコンポーネントと同じ手法を用います。以下に変更結果を示します。 完成品 const Square = (props) => { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }; const Board = (props) => { const [squares, setSquares] = useState(Array(9).fill(null)); const handleClick = (i) => { const newSquares = squares.slice(); newSquares[i] = "X"; setSquares(newSquares); }; const renderSquare = (i) => ( <Square value={squares[i]} onClick={() => { handleClick(i); }} /> ); const status = "Next player: X"; return ( <div> <div className="status">{status}</div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; 手番の処理 一部公式とは違う手法でやっていますが、基本同じです。 (異なる点:テンプレート文字列) const Board = (props) => { const [squares, setSquares] = useState(Array(9).fill(null)); const [xIsNext, setXIsNext] = useState(true); const handleClick = (i) => { const newSquares = squares.slice(); newSquares[i] = xIsNext ? "X" : "O"; setSquares(newSquares); setXIsNext(!xIsNext); }; const renderSquare = (i) => ( <Square value={squares[i]} onClick={() => { handleClick(i); }} /> ); const status = `Next player: ${xIsNext ? "X" : "O"}`; //以下変更なし ゲーム勝者の判定 Board内で関数を定義する、引き分けを追加するなど変更点はありますが大体同じです。 const Board = (props) => { const [squares, setSquares] = useState(Array(9).fill(null)); const [xIsNext, setXIsNext] = useState(true); const handleClick = (i) => { const newSquares = squares.slice(); if (calculateWinner(newSquares) || newSquares[i]) { return; } newSquares[i] = xIsNext ? "X" : "O"; setSquares(newSquares); setXIsNext(!xIsNext); }; const renderSquare = (i) => ( <Square value={squares[i]} onClick={() => { handleClick(i); }} /> ); const calculateWinner = (squares) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if ( squares[a] && squares[a] === squares[b] && squares[a] === squares[c] ) { return squares[a]; } } return null; }; const status = () => { const winner = calculateWinner(squares); if (winner) { return "Winner: " + winner; } else if (squares.reduce((x, y) => x && y)) { return "Draw"; } else { return `Next player: ${xIsNext ? "X" : "O"}`; } }; return ( <div> <div className="status">{status()}</div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; タイムトラベル機能の追加 State のリフトアップ、再び historyステートはオブジェクトのリストではなく、2次元リストに変更しました。 その他定義順や記法が一部異なります。 const Board = (props) => { const renderSquare = (i) => ( <Square value={props.squares[i]} onClick={() => { props.onClick(i); }} /> ); return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; const Game = (props) => { const initSquares = Array(9).fill(null); const [history, setHistory] = useState([initSquares]); const [xIsNext, setXIsNext] = useState(true); const calculateWinner = (squares) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if ( squares[a] && squares[a] === squares[b] && squares[a] === squares[c] ) { return squares[a]; } } return null; }; const status = (squares) => { const winner = calculateWinner(squares); if (winner) { return "Winner: " + winner; } else if (squares.reduce((x, y) => x && y)) { return "Draw"; } else { return `Next player: ${xIsNext ? "X" : "O"}`; } }; const current = history.slice(-1)[0]; const winner = calculateWinner(current); const handleClick = (i) => { const squares = current.slice(); if (winner || squares[i]) return; squares[i] = xIsNext ? "X" : "O"; setHistory([...history, squares]); setXIsNext(!xIsNext); }; return ( <div className="game"> <div className="game-board"> <Board squares={current} onClick={handleClick} /> </div> <div className="game-info"> <div>{status(current)}</div> <ol>{/* TODO */}</ol> </div> </div> ); }; 過去の着手の表示&Key を選ぶ 関数の定義順や場所に注意したこと以外に変更はありません。 これがチュートリアルの最後であるため、全文載せておきます。 import React, { useState } from "react"; import ReactDOM from "react-dom"; import "./index.css"; const Square = (props) => { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }; const Board = (props) => { const renderSquare = (i) => ( <Square value={props.squares[i]} onClick={() => { props.onClick(i); }} /> ); return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; const Game = (props) => { const initSquares = Array(9).fill(null); const [history, setHistory] = useState([initSquares]); const [xIsNext, setXIsNext] = useState(true); const [stepNumber, setStepNumber] = useState(0); const calculateWinner = (squares) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if ( squares[a] && squares[a] === squares[b] && squares[a] === squares[c] ) { return squares[a]; } } return null; }; const status = (squares) => { const winner = calculateWinner(squares); if (winner) { return "Winner: " + winner; } else if (squares.reduce((x, y) => x && y)) { return "Draw"; } else { return `Next player: ${xIsNext ? "X" : "O"}`; } }; const current = history[stepNumber]; const winner = calculateWinner(current); const handleClick = (i) => { const slicedHistory = history.slice(0, stepNumber + 1); const squares = current.slice(); if (winner || squares[i]) return; squares[i] = xIsNext ? "X" : "O"; setHistory([...slicedHistory, squares]); setXIsNext(!xIsNext); setStepNumber(slicedHistory.length); }; const jumpTo = (step) => { setStepNumber(step); setXIsNext(step % 2 === 0); }; const moves = history.map((step, move) => { const desc = move ? "Go to move #" + move : "Go to game start"; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board squares={current} onClick={handleClick} /> </div> <div className="game-info"> <div>{status(current)}</div> <ol>{moves}</ol> </div> </div> ); }; // ======================================== ReactDOM.render(<Game />, document.getElementById("root")); まとめ 以上が関数コンポーネント化させたReact公式チュートリアルになります。関数内で関数を定義したり、クロージャーを用いていたりと見慣れない書き方かもあるかもしれませんが、Reactや関数型プログラミングではよく見る光景なので慣れておくといいと思います。 一部筆者の好みで改変したため、こうした方が公式に近い、より良い書き方があるという方は、コメントなどでアドバイスをいただけると幸いです。 Reactは非常に便利なライブラリですので、これを活用してどんどんアプリケーションを作っていきましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのOnClick時に音が鳴るようにした時の記録

経緯 Reactの練習として簡単な貯金箱アプリを作成しました。 イメージとしてはこのような感じ↓ 作成している途中に、「これ+と-ボタンをクリックした時に"チャリン♪"って音鳴らしたいなー」と思ったので実装方法と、その際に困ったことを記録として残しておきます! 実際のコード 実際のボタン部分とオーディオを作成するためのコードは以下の通りです。(該当部分のみ載せています) ボタン部分↓ <button className="btn" onClick={() => { audioPlay(true); setCount(count + 1); props.setSum(props.unit + props.sum); }}> + </button> <button className="btn" onClick={() => { audioPlay(false); setCount(count - 1); props.setSum(-props.unit + props.sum); }}> − </button> ↓オーディオ作成、再生部分 const checkUnit = (isIncrement: boolean) => { if (isIncrement) { if (props.unit < 1000) { return "../audio/inCoin.mp3" } else { return "../audio/inBill.mp3" } } else { if (props.unit < 1000) { return "../audio/outCoin.mp3" } else { return "../audio/outBill.mp3" } } } const audioPlay = (is_increment: boolean) => { const audio = new Audio(checkUnit(is_increment)) audio.play().then(() => { console.log("Audio started!") }) .catch(error => console.warn(error)) } 内容としてはボタンをクリックすると、カウント数と、親コンポーネントで管理している合計金額が変化するというとてもシンプルな内容になっています。 オーディオ作成・再生部分では、audioPlay関数内の const audio = new Audio(checkUnit(is_increment)) の部分でオーディオ要素を作成しており、引数として音源のURLを指定する必要があるのですが、今回お札と小銭、+と-でそれぞれ使用する音源を分けたかったので、checkUnit関数で場合分けを行いました。 const checkUnit = (isIncrement: boolean) => { if (isIncrement) { if (props.unit < 1000) { return "../audio/inCoin.mp3" } else { return "../audio/inBill.mp3" } } else { if (props.unit < 1000) { return "../audio/outCoin.mp3" } else { return "../audio/outBill.mp3" } } } お札と小銭で音源を変えることはさほど難しくはなかったのですが、プラスとマイナスはどのように分けようか...と考えて、今回はis_incrementをtrue/falseで渡すことでプラスをクリックしているかマイナスをクリックしているかを分けるようにしました。 よしできた!と思い、ボタンをクリックしてみたのですが、クリックしてもなかなか音源が再生されず・・・。 調べてみたところ、new Audioの引数に指定するURLはindex.htmlからのパスを指定する必要があるみたいです。 index.htmlからのパスにすることと、条件分岐をすることで4つのパターンの音源を使い分けて実装を行うことができました! 以上、私が実際に実装をする上で困ったことでした!! 最後に 今回の内容としてはすごく基礎的な部分だと思います。 少しずつですが、実装を通して自分のできることを増やしていければと思います
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む