- 投稿日:2020-02-06T17:23:07+09:00
Reactチュートリアルプログラム
目的
Reactの学習のため、下記のチュートリアルプログラムを作成してみる。
https://ja.reactjs.org/tutorial/tutorial.html環境
Windows10Home
node v10.16.0
npm 6.9.0記述
create-react-app
テキストエディタを使用するので、ベースとして
create-react-app
を使用。
下記を実行し土台を作成。npmnpx create-react-app test cd test npm startその後作成した
test\src
配下のファイルを全て削除し、index.css
,index.js
を作成。cssの記述
index.cssbody { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; }Reactの記述
index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0 }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); }); let status; if (winner) { status = "Winner: " + winner; } else { status = "Next player: " + (this.state.xIsNext ? "X" : "O"); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={i => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); } } // ======================================== ReactDOM.render(<Game />, document.getElementById("root")); function 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; }追加課題
1.履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
history
にマスの情報を追加し、値を呼び出せるように変更。index.js(handleClick)handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares, col: (i % 3) + 1, //追加 row: Math.floor(i / 3) + 1, //追加 } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); }遷移リストにマスの位置を表示するように
step.col
,step.row
追加index.js(render)render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move +'('+ step.col +','+ step.row +')' : //変更 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); });2.着手履歴のリスト中で現在選択されているアイテムをボールドにする。
Gameコンポーネント
にstepNumber
をステートとして保持しているので、これを利用してstepNumber
とhistory
のindexの役割をするmove
が一致した際にbold
というclassを付与index.js(render)render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move +'('+ step.col +','+ step.row +')' : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} className={this.state.stepNumber=== move ? 'bold':'' } >{desc}</button> </li> ); });
boldclass
付与時に太字が有効になるようにcss
に追加index.css.bold { font-weight: bold; }3.Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
key
を追加し、map関数
を二重で使用する。index.js(Board)class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} key={i} /> ); } render() { return ( <div> { Array(3).fill(0).map((row, i) => { return ( <div className="board-row" key={i}> { Array(3).fill(0).map((col, j) => { return( this.renderSquare(i * 3 + j) ) }) } </div> ) }) } </div> ); } }4.着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
Gameコンポーネントに昇順・降順を並べ替えるボタンを追加し、昇順か降順かどうかを保持するstateを新しく定義することで遷移リストをレンダリングする際にstateの状態によって昇順か降順を切り替えを行う。
index.js(Game)class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares, col: (i % 3) + 1, row: Math.floor(i / 3) + 1, } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, isAsc: true }); } toggleAsc() { this.setState({ asc: !this.state.asc, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move +'('+ step.col +','+ step.row +')' : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} className={this.state.stepNumber=== move ? 'bold':'' } >{desc}</button> </li> ); }); let status; if (winner) { status = "Winner: " + winner; } else { status = "Next player: " + (this.state.xIsNext ? "X" : "O"); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={i => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <div><button onClick={() => this.toggleAsc()}>{(this.state.asc ? "desc":"asc")}</button></div> <ol>{this.state.asc ? moves : moves.reverse()}</ol> </div> </div> ); } }5.どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
calculateWinner関数
に勝者の判定だけでなくどのマスの列が揃ったかの配列も返すように変更を加える。index.js(calculateWinner)function 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{ winner: squares[a], line: [a,b,c] }; } } return null; }
calculateWinner関数
の帰り値を変更したため、Gameコンポーネント
の修正とBoardコンポーネント
に揃った列のマスを渡すように変更。index.js(Game)class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares, col: (i % 3) + 1, row: Math.floor(i / 3) + 1, } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, isAsc: true }); } toggleAsc() { this.setState({ asc: !this.state.asc, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const settlement = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move +'('+ step.col +','+ step.row +')' : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} className={this.state.stepNumber=== move ? 'bold':'' } >{desc}</button> </li> ); }); let status; if (settlement) { status = "Winner: " + settlement.winner; } else { status = "Next player: " + (this.state.xIsNext ? "X" : "O"); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={i => this.handleClick(i)} highlightCells={settlement ? settlement.line : []} /> </div> <div className="game-info"> <div>{status}</div> <div><button onClick={() => this.toggleAsc()}>{(this.state.asc ? "desc":"asc")}</button></div> <ol>{this.state.asc ? moves : moves.reverse()}</ol> </div> </div> ); } }
Boardコンポーネント
に揃った列のマスの配列を受け取り、マスをレンダリング時に揃ったマスをハイライトするか否をisHighlight
で判定。index.js(Board)class Board extends React.Component { renderSquare(i, isHighlight = false) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} key={i} isHighlight={isHighlight} /> ); } render() { return ( <div> { Array(3).fill(0).map((row, i) => { return ( <div className="board-row" key={i}> { Array(3).fill(0).map((col, j) => { return( this.renderSquare(i * 3 + j, this.props.highlightCells.indexOf(i * 3 + j) !== -1) ) }) } </div> ) }) } </div> ); } }
Squareコンポーネント
をisHighlight
がtrue
のときのみhighlight
クラスを付与するように変更。index.js(Square)function Square(props) { return ( <button className={`square ${props.isHighlight ? 'highlight' : ''}`} onClick={() => props.onClick()}> {props.value} </button> ); }最後に
highlight
クラスがついたときに背景色が変わるようにcss
に追加。index.css.highlight { background-color: yellow; }6.どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
calculateWinner関数
に引き分けかどうかの情報も渡すように変更。index.js(calculateWinnerfunction 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{ winner: squares[a], line: [a,b,c], isDraw: false }; } } if(squares.filter((e) => !e).length === 0){ return { isDraw: true, winner: null, line: [] } } return null; }
Gameコンポーネント
のrenderメソッド
を書き換えていきます。index.js(Game)class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares, col: (i % 3) + 1, row: Math.floor(i / 3) + 1, } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, isAsc: true }); } toggleAsc() { this.setState({ asc: !this.state.asc, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const settlement = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move +'('+ step.col +','+ step.row +')' : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} className={this.state.stepNumber=== move ? 'bold':'' } >{desc}</button> </li> ); }); let status; if (settlement) { if (settlement.isDraw) { status = "Draw"; }else{ status = "Winner: " + settlement.winner; } } else { status = "Next player: " + (this.state.xIsNext ? "X" : "O"); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={i => this.handleClick(i)} highlightCells={settlement ? settlement.line : []} /> </div> <div className="game-info"> <div>{status}</div> <div><button onClick={() => this.toggleAsc()}>{(this.state.asc ? "desc":"asc")}</button></div> <ol>{this.state.asc ? moves : moves.reverse()}</ol> </div> </div> ); } }
- 投稿日:2020-02-06T13:07:10+09:00
Docker + Create React Appで環境変数を使いたかった(自戒)
失敗談と解決方法です
やらかしたメモ実現させたかったこと
Azure上のApp Serviceで設定した環境変数をデプロイしたDockerイメージで使用したかった。
CRA(Create React App)を使用しているため、.envファイルにREACT_APP_
と先頭とつけて書いておくと環境変数として読み込まれる。。
→ App Serviceから環境変数として渡されている値をもとに.env
を生成する。試した実装と望んでいた動作
DockerfileのCMDを使用して
Docker run
のタイミングで.env
を生成するシェルスクリプトを実行する。
↓↓↓CMD [ "/bin/bash", "-c", "./env.sh" ]env.sh#!/bin/bash rm -rf .env touch .env while read -r line || [[ -n "$line" ]]; do if printf '%s\n' "$line" | grep -q -e '='; then varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') fi value=$(printf '%s\n' "${!varname}") [[ -z $value ]] && value=${varvalue} echo "$varname = \"$value\"" >> .env done < .env.key.env.keyREACT_APP_ID = REACT_APP_PW =
.env.key
にある変数名をもとに.env
を生成して、下の要領でアプリケーション内で読み込みたかった。id = process.env.REACT_APP_ID; pw = process.env.REACT_APP_PW;原因
.env
の読み込みタイミングはDockerfile内のyarn build
が実行されたとき。
yarn build
が実行されているのはdocker build
をした際のみで、.env
が生成される前。
docker run
が実行されるのもApp Serviceから環境変数が渡されるのもdocker build
が終了し、App Serviceにデプロイされたタイミング。
→.env
の生成自体は成功していたが読み込まれていなかった。ただのミスだった…
解決策
下記のようにenvファイルを変更して、環境変数を保持するjsファイルを生成する。
index.html
のhead
でスクリプトとして読み込ませることで、window._env_.***
として呼び出すことができる。呼び出しid = window._env_.REACT_APP_ID; pw = window._env_.REACT_APP_PW;env.sh#!/bin/bash rm -rf ./env-config.js touch ./env-config.js echo "window._env_ = {" >> ./env-config.js while read -r line || [[ -n "$line" ]]; do if printf '%s\n' "$line" | grep -q -e '='; then varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') fi value=$(printf '%s\n' "${!varname}") [[ -z $value ]] && value=${varvalue} echo " $varname: \"$value\"," >> ./env-config.js done < .env echo "}" >> ./env-config.jsindex.html<head> ... <script src="%PUBLIC_URL%/env-config.js"></script> ... </head>参考
- 投稿日:2020-02-06T02:04:42+09:00
highlight.js で一部の言語のみに対応して webpack のビルドサイズを減らす方法
こんにちは、フロントエンジニアの ku6ryo です。
コード文字列をハイライトしてくれる highlight.js を使う機会があったのですが、highlight.js のライブラリ全体のファイルサイズが大きくて webpack ビルドのサイズを大きくしていたので必要な言語のみ対応して、ビルドサイズを大幅に削減する方法を紹介します。まず、highlight.js の大きさは webpack で build した場合、Gzip しないとサイズが 746.69 KB、Gzip すると 246.18 KB のサイズがあります。
いずれにしても、とても大きなサイズなことは代わりありませんね(汗)
すべての対応言語を読み込んでしまうと、このようなサイズになります。
必要な言語をのみを読み込むには、以下のように必要な言語のファイルのみを読み込む設定ファイルを作ります。例えば、Javascript, CSS, JSON のみに対応するには、以下のようなファイルを作ります。// my-highlight.js import hljs from 'highlight.js/lib/highlight' hljs.registerLanguage('css', require('highlight.js/lib/languages/css')) hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript')) hljs.registerLanguage('json', require('highlight.js/lib/languages/json')) export default hljsそして、このファイルを highlight.js を呼ぶときと同じように使えば OK です。
// 通常 import hljs from 'highlihgt.js' // 必要なものだけ読み込む import hljs from './my-highlight.js'上記の言語セットの場合、Gzip 前 19KB、Gzip 後 7.14 KB になります。Gzip 前 700 KB 以上、Gzip 後 200 KB 以上のサイズ削減になります。これはだいぶ大きですよね! highlight.js 使っている方はぜひやってみてくださいね〜