- 投稿日:2020-07-11T19:37:44+09:00
eslint-plugin-jsx-ally@6.0.3 postinstall: `node ./postinstall.js`エラー解決方法
めちゃくそしょうもないエラーに久しぶりに引っかかったので、戒めのために記事を残しておく
エラー内容
ReactNative + ExpoのUdemy講座を進めている時にnpm installでライブラリをインストールする場面があり、
講座では
eslint-plugin-jsx-ally
をダウンロードしろとのことだったので、以下コマンドを実行
npm install --save--dev eslint-plugin-jsx-ally
すると以下のエラーに遭遇
npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! eslint-plugin-jsx-ally@6.0.3 postinstall: `node ./postinstall.js` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the eslint-plugin-jsx-ally@6.0.3 postinstall script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /Users/UserName/.npm/_logs/2020-07-11T10_28_15_941Z-debug.log講座を何度も見直しても原因がわからない。
解決方法
eslint-plugin-jsx-ally
→eslint-plugin-jsx-a11y
でした。。動画教材だとコマンドとかをコピペできないので、こう言うことが起こりがち・・。
以下コマンドを実行で無事ダウンロードできました。
npm install --save--dev eslint-plugin-jsx-a11y
しょーもないミスに時間取られないようにちゃんとしよ・・。
- 投稿日:2020-07-11T19:20:19+09:00
Reactのthis.props.fooなんて知らないぞ!
結論
コンポーネントに渡された入力データを this.props で参照し、render() の中で使用する。
内容
完全React初心者の自分が、練習がてらコンポーネントを自作する際に、メソッドの中でthis.props.hogeが使用されているのを見かけ、「なんだこれは、こんなの定義してないのぞ。。。。。」と思ってみたので調べてまとめてみた。
まずは以下のコードを見て欲しい。
class HelloMessage extends React.Component { render() { return ( <div> Hello {this.props.name} </div> ); } } ReactDOM.render( <HelloMessage name="Taylor" />, document.getElementById('hello-example') );これはとても初歩的な自作Reactコンポーネント。ちなみにこちらは、参考資料に掲載している本家Reactのホームページのチュートリアルから抜粋してきたものである。
<HelloMessage name="Taylor" />コンポーネントを使用する際、name="Taylor"をコンポーネントに渡している。this.props.nameはこのnameを参照して、コンポーネントの中で Hello {this.props.name}を表示する。
参考資料
React tutorial
- 投稿日:2020-07-11T19:18:09+09:00
Reactで外部リンクする方法(ダウンロードリンク作る方法)
<a href='/somefile.txt' download>Click to download</a>これで行けたわ
https://stackoverflow.com/questions/50694881/how-to-download-file-in-react-js
- 投稿日:2020-07-11T19:07:49+09:00
reactで内部リンク
import { Link } from 'react-router-dom'; <Link to="/Path" > クリック </Link>
- 投稿日:2020-07-11T19:07:49+09:00
reactで内部リンク『同じドメイン化しかむり』
import { Link } from 'react-router-dom'; <Link to="/Path" > クリック </Link>
- 投稿日:2020-07-11T18:33:06+09:00
React Routerを使ってWebサイトを作ってみる
先日React Routerを学習したので、そのアウトプットとして何か作ってみたいと思い、自分のアメリカ留学をテーマにした簡単なサイトを作成してみました!
以下、サイトのURLとGitHubリポジトリです。(サイトはGitHub Pagesにデプロイしています。)
サイトURL:https://taikiyamano.github.io/MyLifeInTheUS/#/
Githubリポジトリ:https://github.com/TaikiYamano/MyLifeInTheUSまた、今回のWebサイトを作成するにあたり、こちらの記事を参考にさせて頂きました
ディレクトリ構造
今回作成したWebサイトのディレクトリ構造は以下のようになります。(create-react-appコマンドでデフォルトで作成されるいくつかのファイルは削除しています。)
componentsディレクトリの中に各ルーティングに対するページのディレクトリ(DanceやStudyなど)を作り、その中に各フォルダ名に対応するJSXファイルを作成しています。今回はディレクトリの中にJSXファイルしかないので、コンポーネント毎にディレクトリを作成する必要もなかったのですが、今後コンポーネント毎に別のCSSを当てるということが起きた先に柔軟に対応できるよう、componentsディレクトリの中に各コンポーネント用のディレクトリを作成しました。
imagesディレクトリの中には各ページで表示する為の画像を入れたディレクトリがあり、Page.jsxにはページ全体のヘッダーとヘッダーメニュー、ルーティングを記述しています。Page.cssはページ全体のCSSを記述していて、App.jsでPageコンポーネント(Page.jsxの内容)とPage.cssを読み込んでいます。
App.js
App.jsimport React from 'react'; import './Page.css'; import Page from './Page'; function App() { return ( <Page/> ); } export default App;App.jsの中身はこんな感じです。上記のようにPage.cssとPageコンポーネントをインポートし、AppコンポーネントでPageコンポーネントをreturnしているだけになります。
Page.jsx
Page.jsximport React from 'react'; //1 import { HashRouter, Route, Link } from 'react-router-dom'; //2 import Top from './components/Top/Top'; import Study from './components/Study/Study'; import Dance from './components/Dance/Dance'; import Trip from './components/Trip/Trip'; import '../src/Page.css'; function Page(){ return( <header className="header_wrapper"> //3 <HashRouter> <nav> <ul className="header_menu"> //4 <li><Link to="/">Top</Link></li> <li><Link to="/study">Study</Link></li> <li><Link to="/dance">Dance</Link></li> <li><Link to="/trip">Trip</Link></li> </ul> </nav> <div className="header_title_img"> <h1 className="header_title">My Life In The U.S.</h1> </div> //5 <Route path="/" exact component={Top} /> <Route path="/study" exact component={Study} /> <Route path="/dance" exact component={Dance} /> <Route path="/trip" exact component={Trip} /> </HashRouter> </header> ) } export default Page;Page.jsxの中身はこんな感じです。
1、こちらではreact-router-domからHashRouter Router、Routeをインポートしました。(詳細は後ほど説明します。)2、こちらでは、各ページのコンポーネントをインポートしています。
3、今回、GitHub Pagesに静的ファイルをデプロイしたかったので、1でBrouserRouterではなくHashRouterをインポートし、使用しました。(因みにGitHubPagesにデプロイする際、BrouserRouterを使っていると、デプロイしたサイトをリロードした時に404ページが返ってきます 原因としては、リロードするとサーバーまでリクエストが走ってしまい、「そのパスにはページが存在していないよ!」と返されるかららしいです。一方、HashRuterを使用すると、URLの後ろに/#/が付き、その場でリロードしてもページがそのまま読み込まれる形となり、404は出なくなります。URLの見栄えは少し悪くなりますが...)
4、ヘッダーメニュー部分に1でインポートしたLinkを使用して、toの部分に各表示部分のパスを設定しています。
5、Routeではpathの部分で指定したパスにアクセスをした際に、どのコンポーネントを表示させるかをcomponent部分で指定しています。今回の場合、
アクセスするパス 表示するコンポーネント / Topコンポーネント /study Studyコンポーネント /dance Danceコンポーネント /trip Tripコンポーネント となります。
また、Route内にexactと記述していますが、この記述がないとパスが部分的に一致した場合でも、そのコンポーネントを表示してしまいます。つまり今回の場合では、どのパスにも「/」が入っている為、exactがないと、「/」が部分的に一致し、どのパスにアクセスしても、常にTopコンポーネントが表示された状態になります。exactを記述すると、パスが完全に一致した時にだけ、対応するコンポーネントを表示するようにするので、Topコンポーネントが常に表示されるという事態を防ぐことができます。Trip.jsx
今回は各ページのコンポーネントファイルの中身がほとんど同じなので、それぞれのファイルの中身を一つずつ説明はしないのですが、Tripコンポーネントのみ、ImageGalleryというコンポーネントをインポートしてカルーセルのイメージギャラリーを表示させるようにしたので、それも踏まえてTripコンポーネントを、全てのページコンポーネントファイルを代表して中身を説明していきます。
Trip.jsximport React from 'react'; //1 import '../../Page.css'; //2 import NiagaraFalls from '../../images/Trip/niagarafalls.JPG'; import TimesSquare from '../../images/Trip/timessquare.JPG'; import Trip_img1 from '../../images/Trip/trip_img1.JPG'; import Trip_img2 from '../../images/Trip/trip_img2.JPG'; import Trip_img3 from '../../images/Trip/trip_img3.JPG'; import Trip_img4 from '../../images/Trip/trip_img4.JPG'; import Trip_img5 from '../../images/Trip/trip_img5.JPG'; import Trip_img6 from '../../images/Trip/trip_img6.JPG'; import Trip_img7 from '../../images/Trip/trip_img7.JPG'; import Trip_img8 from '../../images/Trip/trip_img8.JPG'; import Trip_img9 from '../../images/Trip/trip_img9.JPG'; import Trip_img10 from '../../images/Trip/trip_img10.JPG'; //3 import ImageGallery from 'react-image-gallery'; import "react-image-gallery/styles/css/image-gallery.css"; function Trip(){ //4 const images = [ { original: `${Trip_img1}`,★ thumbnail: `${Trip_img1}`, description: "Grand Central Terminal", }, { original: `${Trip_img2}`, thumbnail: `${Trip_img2}`, description: "The Statue of Liberty", }, { original: `${Trip_img3}`, thumbnail: `${Trip_img3}`, description: "Landscape of NYC", }, { original: `${Trip_img4}`, thumbnail: `${Trip_img4}`, description: "Me in front of Niagara Falls' billboard", }, { original: `${Trip_img5}`, thumbnail: `${Trip_img5}`, description: "A church in New York (Maybe St. Patrick's Cathedral)", }, { original: `${Trip_img6}`, thumbnail: `${Trip_img6}`, description: "Trump Tower", }, { original: `${Trip_img7}`, thumbnail: `${Trip_img7}`, description: "Night Brooklyn bridge", }, { original: `${Trip_img8}`, thumbnail: `${Trip_img8}`, description: "Night view from Brooklyn bridge", }, { original: `${Trip_img9}`, thumbnail: `${Trip_img9}`, description: "New York Public Library", }, { original: `${Trip_img10}`, thumbnail: `${Trip_img10}`, description: "Niagara Falls from the ship" }, ]; //5 return( <div className="whole_wrapper"> <div className="inner"> <h1 className="page_title">Trip</h1> <p className="trip_top_text"> I couldn't visit a lot of places while studying abroad becase I was so busy with studying. But I visited NewYork City twice, and Niagara Falls twice. </p> <div className="content"> <div className="img_space img_right"> <img src={NiagaraFalls} alt="NiagaraFalls"/> <span className="picture_description">Picture: NiagaraFalls</span> </div> <p className="text_space text_left"> Niagara Falls was very amazing and powerful place! I had only seen it in books and on videos,<br/>so I was very glad to see the real one! In Niagara Falls, I got on a ship and could see Niagara Falls very close up! <br/> It was so dynamic and beautiful! If you like nature, I recomend to visit here!<br/> </p> </div> <div className="content"> <div className="img_space img_left"> <img src={TimesSquare} alt="NiagaraFalls"/> <span className="picture_description">Picture: the countdown at Times Square in New York on New Year’s Eve</span> </div> <p className="text_space text_right"> Visiting New York City is one of the best memories in my life! <br/> The first time, I went to the countdown at Times Square in New York on New Year’s Eve with my friends! <br/> It was so crowded at Times Square and my friends and me waited until the countdown was started<br/> (we were wating for the countdown around 12 hours...), so we were so tired.<br/> But I think I had a valuable experience I had never experienced.<br/> On the following day, my friends and me visit Grand Central Station and Liberty Island<br/> that is famous for the Statue of Liberty. The second time, <br/>I visted NYC to went sightseeing and enjoy some popular food in NYC. <br/> If I have a chance, I would like to visit America and NYC again! </p> </div> <div className="pictures_gallery"> <h1>Pictures gallery</h1> <p>I'm going to share some of my pictures about my trips in America here!</p> //6 <ImageGallery items={images} /> </div> </div> </div> ) } export default Trip;Trip.jsxの中身はこんな感じです。
1、こちらではページ全体のCSSをインポートしています。
2、こちらではページ内で使う画像、イメージギャラリーで使う画像をインポートしています。
3、こちらではreact-image-galleryからImageGalleryコンポーネント、そしてそのCSSをインポートしています。
4、ImageGalleryコンポーネントのitemsの部分に指定する配列を定義しています。配列の中はオブジェクトで、プロパティはreact-image-gallery側で指定されています。(種類が多いのですが、色々選べてとても便利です!詳しくはこちらのドキュメントをご参照ください。)
今回はoriginal(イメージギャラリーで大きく表示される画像)、thumbnail(サムネイル)、description(画像の説明)の3つのプロパティを指定しました。(因みにインポートした画像をプロパティの値として使用する場合、画像指定部分を`${Trip_img1}`という風に記述しないとうまく表示されません。)
5、こちらでは、ページの表示部分をreturnしています。
6、ImageGalleryコンポーネントを表示させます。またこの時、items部分には4で定義した配列を指定してあげることで、イメージギャラリーを表示させることができます。GitHub Pagesへのデプロイ
GitHub Pagesへのデプロイはこちらの記事を参考にさせて頂きました!
ただ、僕の場合、サイトを作成し始める段階で、すでにコードをGitHubのリポジトリで管理していて、部分的に違うところもあったので、僕が行った手順を説明させて頂ければと思います。1、gh-pagesをインストール
作業中のディレクトリ(React-RouterでWebサイトを作っているディレクトリ)に移動し、以下のコマンドを実行しました。
$ npm install gh-pages --save-dev
2、package.jsonに設定を追記
package.json内に
"homepage"
、"scripts"の中に"predeploy"
と"deploy"
の3つを追記しました。"homepage"の中は
"http://[自分のGitHubのユーザー名].github.io/[公開したいアプリ、サイトのリポジトリ名"
を記述しました。
以下が実際の追記内容です。package.json{ "homepage": "http://TaikiYamano.github.io/MyLifeInTheUS", //省略 }, "scripts": { //省略 "predeploy": "npm run build", "deploy": "gh-pages -d build" },3、GitHub Pagesにデプロイ
1と同じディレクトリで、以下のコマンドを実行し、サイトをGitHub Pagesにデプロイしました。
$ npm run deploy
4、GitHub Page側で表示の確認
ブラウザでGitHubを開き、今回のサイトを管理しているリポジトリを選択します。そうすると、「Code」や「Issues」などのタブが表示されるので、そこの一番右、「Settings」を選択します。
Settingsページに移動し、下の方にスクロールして行くと、「GitHub Pages」という項目があり、そこに
「Your site is published at [サイトのURL]」とあります。(表示されているサイトのURLはpackage.jsonの"homepage"に記述したURLと同じはずです。)そこをクリックすると、今回作成したサイトが表示されました。また、GitHub Pagesの設定項目の「Source」の部分ではgh-pages branchとmaster branchを選択できるのですが、gh-pages branchを選択しました。(master branchを選択すると、公開されたサイトを別タブで開き、ページを何回かリロードするとなぜか404ページが表示されました)
設定項目は以下のようになります。
最後に
今回、React-Routerのアウトプットとして1つ簡単なWebサイトを作成してみましたが、やはり何かを作ってみると知識の定着が早いなと個人的に感じました!
また、サイトのCSSがしょぼい割にCSSでのサイトデザインや、GitHub Pagesへのデプロイに時間が掛かってしまったのが、少し悔しかったところです
デザインもダサいし、写真の読み込みが遅いなど改善点もあるので、今後少しずつ改善していけたらと思います!
- 投稿日:2020-07-11T18:30:41+09:00
React のチュートリアルを Typescript でやってみた3(タイムトラベルの実装)【完】
目次
- React のチュートリアルを Typescript でやってみた【環境構築編】
- React のチュートリアルを Typescript でやってみた【環境構築編2】
- React のチュートリアルを Typescript でやってみた【ソース準備編】
- React のチュートリアルを Typescript でやってみた1
- React のチュートリアルを Typescript でやってみた2
- React のチュートリアルを Typescript でやってみた3 ←ここ
概要
チュートリアル通りにやりますが、端折る箇所もあると思います。
各々保管をお願いいたします。公式のチュートリアルページ
今回の実施チュートリアル内容
タイムトラベル機能の追加
- 着手の履歴の保存
- State のリフトアップ、再び
- 過去の着手の表示
- key を選ぶ
- タイムトラベルの実装
1.着手の履歴の保存
特にソースの変更なし
どこにhistoryを作成するべきか考える。2.State のリフトアップ、再び
game.tsxに作成する必要があるためStateとPropsを作成。
historyに関しては、1つの要素に1ステージのデータが入っているとしたものを作成しました。
こうしないと後々エラーになりました(´・ω・`)game.tsxtype stage = { squares: Array<String>; } type State = { history: Array<stage>; xIsNext: boolean; } type Props = {}その後コンストラクタを追加
game.tsxconstructor(props: Props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; }board.tsxの書き換え
game.tsxにStateを持つことになりこれまでデータを保持していたboard.tsxを大まかに変更する必要があります。変更1:SteteからPropsに変更する。
Steteを削除し以下のPropsを作成board.tsxtype Porps = { squares: Array<String>; onClick: (i: number) => void; }変更2:classの引数とコンストラクタの変更
board.tsxclass Board extends React.Component<Porps> { constructor(props: Porps) { super(props) }変更3:renderSquareの内容の変更
board.tsxrenderSquare(i: number) { return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />; }変更4:handleClick及びcalculateWinnerのfunctionをgame.tsxに移動
大まかにはこのくらいまだまだ変更する必要がありますがとりあえずはって感じです。
game.tsxのrenderを書き換えていきます。
以下のような感じになりました。game.tsxrender() { const history = this.state.history; const current = history[history.length - 1]; const winner = this.calculateWinner(current.squares); let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○'); } 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>{/* TODO */}</ol> </div> </div> ); } }game.tsxを書き換えた事によってboard.tsxを書き換えます。
renderの部分のstatusについて削除しました。board.tsxrender() { 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> ); }game.tsxに移動したhandleClickを書き換えます。
以下のような感じになりました。
ここのhistory: history.concat({ squares: squares }),
でかなり躓きました・・・
このようにするためにgame.tsxのStateがあのような形になった感じです。
もっといい方法があればオシエテクダサイ・・・game.tsxhandleClick(i: number) { const history = this.state.history; const current = history[history.length - 1]; const squares = current.squares.slice(); if (this.calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : '○'; this.setState({ history: history.concat({ squares: squares }), xIsNext: !this.state.xIsNext, }); }以上でゲームができるような形になったと思います。
3.過去の着手の表示
game.tsxのrenderに以下のものを追加します。
game.tsxrender() { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[this.state.stepNumber]; const winner = this.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; ︙ <div className="game-info"> <div>{status}</div> + <ol>{moves}</ol> </div> ︙この時点ではWARNINGが表示されますが以降のものを実装することによってなくなるので放置します。
4.key を選ぶ
特にソースの変更なし
概念的には理解する必要あり5.タイムトラベルの実装
movesの中身を変更します。
以下の変更でWARNINGが消えると思います。game.tsxreturn ( - <li> + <li key={move}> <button /* onClick={() => this.jumpTo(move)} */>{desc}</button> </li> );historyの機能のために
stepNumberを追加します。game.tsxtype State = { history: Array<stage>; xIsNext: boolean; + stepNumber: number; } ︙ constructor(props: Props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, + stepNumber: 0, }; }jumpToの実装
game.tsxjumpTo(step: number) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); } + <button onClick={() => this.jumpTo(move)} >{desc}</button>stepNumberの機能をhandleClickに反映させます。
チュートリアルのソースのままです。game.tsxhandleClick(i: number) { + const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (this.calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : '○'; this.setState({ history: history.concat({ squares: squares }), + stepNumber: history.length, xIsNext: !this.state.xIsNext, }); }最後にrenderする際の対象となるゲームのステージを設定します。
game.tsxconst history = this.state.history; + const current = history[this.state.stepNumber]; const winner = this.calculateWinner(current.squares);まとめ
駆け足になりましたが、以上になります。
どのくらい吸収できたのかわかりませんがとりあえずチュートリアルは以上です。
何かしら自分で作ったほうがいいかもしれませんね。ソースは以下
- チュートリアル完成
- 投稿日:2020-07-11T17:37:55+09:00
React 開発時にやること
はじめに
Reactの開発を始める時にやることをメモしておきます。
プロジェクト作成
npx create-react-app project_name --typescript
eslint設定ファイル作成
cd project_name ./node_modules/.bin/eslint --initこの時、プラグインはインストールしない
Prettier設定
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
eslintの設定ファイルに追記
module.exports = { "env": { "browser": true, "es6": true }, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:prettier/recommended" //これを追加 ], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 2018, "sourceType": "module" }, "plugins": [ "react", "@typescript-eslint" ], "rules": { } };.prettierrcを作成
touch .prettierrc
react-router-dom
インストール
yarn add -D react-router-dom @types/react-router-dom
雛形作成
src/App.tsximport React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import TopPage from "./TopPage/TopPage"; import HelloPage from "./HelloPage/HelloPage"; function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">TopPage</Link> </li> <li> <Link to="/hello">HelloPage</Link> </li> </ul> </nav> <Switch> <Route path="/hello"> <HelloPage /> </Route> <Route path="/"> <TopPage /> </Route> </Switch> </div> </Router> ); } export default App;/HelloPage/HelloPage.tsximport React from "react"; interface Props {} const HelloPage: React.FC<Props> = (props: Props) => { return <div className="HelloPage">hello</div>; }; export default HelloPage;src/TopPage/TopPage.tsximport React from "react"; interface Props {} const TopPage: React.FC<Props> = (props: Props) => { return <div className="TopPage">Top</div>; }; export default TopPage;(Optional) VSCodeを使用する場合
ESLint
Prettier
のプラグインをインストールFormat on Save
を有効化(Optional) Electron を使用する場合
いい感じに環境を構築してくれている人が既にいるので、参考にし構築。
create-react-appとelectron-builderでTypeScriptとHot Reloadに完全対応したElectronアプリ開発環境を作成するおわりに
以上のことをやれば簡単に開発に取り掛かることができると思います。
- 投稿日:2020-07-11T16:43:59+09:00
React のチュートリアルを Typescript でやってみた2(ゲーム完成まで)
目次
- React のチュートリアルを Typescript でやってみた【環境構築編】
- React のチュートリアルを Typescript でやってみた【環境構築編2】
- React のチュートリアルを Typescript でやってみた【ソース準備編】
- React のチュートリアルを Typescript でやってみた1
- React のチュートリアルを Typescript でやってみた2 ←ここ
- React のチュートリアルを Typescript でやってみた3
概要
チュートリアル通りにやりますが、端折る箇所もあると思います。
各々保管をお願いいたします。公式のチュートリアルページ
今回の実施チュートリアル内容
ゲームを完成させる
- Stateのリストアップ
- イミュータビリティは何故重要なのか
- 関数コンポーネント
- 手番の処理
- ゲーム勝者の判定
1.Stateのリストアップ
Squareに値を当てられるようにする
チュートリアルページの様にBoardでSquareの値を管理するように書いてみます。
まずは、
square.tsx
の修正
Propsでデータを受け取れるようPropsを定義します。
このとき使用しなくなったStateは削除しておきます。また、valueの方はString型を定義します。
今回扱う文字がX
とO
のためになりますが、チュートリアルでは他にもnullを扱っています。
Typescriptの型の都合上今回は空文字にします。
しっかりとチュートリアルどおりにやりたい場合は、Stringとnullを同時に扱える定数を提示しそちらを使うようにしましょう。square.tsx+ type Props = { + value: String + } - type State = { - value: String - }Stringとnullを定義したもの
type SquareVaule = String | null; type Props = { value: SquareVaule; }次に、
board.tsx
の改修を進めていきます。
boardのコンポーネントでsquareのコンポーネントのデータを扱うためStateを定義します。board.tsxtype State = { squares: Array<String>; }次にコンポーネントの引数の部分について修正
以下のようにしました。
{}
の部分は、Propsに当たります。今回は使用していないため空のものを設定
わかりにくい場合は、Propsの空の定義を作成し入れてあげるといいでしょう。
どちらがいいのかわからないです。その時時によると思います。board.tsxclass Board extends React.Component<{}, State> {以下は丁寧にした感じのソースです。
type Props = {} class Board extends React.Component<Props, State> {次にコンストラクタを定義します。
以下の用な感じにこちらはほぼチュートリアルのソース通りです。board.tsxconstructor(props: {}) { super(props) this.state = { squares: Array<String>(9).fill(""), } }あとは、Squareのコンポーネントにstateのデータを指定するだけです。
board.tsxrenderSquare(i: number) { return <Square value={this.state.squares[i]} />; }以上で、Squareに文字列を設定することができるようになります。
マス目がクリックされたときの動作を作成する
マス目がクリックされたときの動作を作成します。
まずは、square.tsx
のPropsを修正します。
boardから渡ってくる動作を受け取れるように以下の様にonClickを設定します。square.tsxtype Props = { value: String + onClick: () => void; }Boardで追加したonClickの部分について作成していきます。
まずは、board.tsx
でSquareにonClickを渡す部分を追加します。board.tsxrenderSquare(i: number) { return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />; }次にhandleClickについて作成します。
board.tsxhandleClick(i: number) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }最後に渡したonClickの動作を動作するようにします。
square.tsxrender() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); }2.イミュータビリティは何故重要なのか
特にソースを変更する必要なし。
3.関数コンポーネント
Squareクラスを関数コンポーネントに書き換えます。
引数の部分に型を入れるだけになった。square.tsxfunction Square(props: Props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }4.手番の処理
とりあえず、Stateの中にユーザ判定用のフラグを用意しました。
board.tsxtype State = { squares: Array<String>; + xIsNext: boolean; }コンストラクタ内で初期値の設定を追加
board.tsxconstructor(props: {}) { super(props) this.state = { squares: Array<String>(9).fill(""), + xIsNext: true, } }マス目がクリックされた際の動作を追加します。
追加した動作
1. ○と×の入力切替
2. プレイヤーのフラグの変更board.tsxhandleClick(i: number) { const squares = this.state.squares.slice(); + squares[i] = this.state.xIsNext ? 'X' : '○'; this.setState({ squares: squares, + xIsNext: !this.state.xIsNext, }); }表示プレイヤーの切り替え
board.tsxconst status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○');5.ゲーム勝者の判定
勝敗判定のfunctionを追加します。
引数はStringの配列に設定board.tsxcalculateWinner(squares: Array<String>) { 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; }勝敗のfunctionを使用しプレイヤーが勝利した場合プレイヤーの表示方法を変更する。
board.tsxrender() { - const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○'); + const winner = this.calculateWinner(this.state.squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○'); + }勝敗がついた場合やマス目がすべて入力された場合に何も動作しないようにする。
board.tsxhandleClick(i: number) { const squares = this.state.squares.slice(); + if (this.calculateWinner(squares) || squares[i]) { + return; + } squares[i] = this.state.xIsNext ? 'X' : '○'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); }まとめ
とりあえずゲーム完成までやってみました。
公式通りにやったためそこまで詰まるところはなかった。
型をちゃんと意識すれば良さそうですね。ここまでのソースは以下になります
- ゲーム完成までのブランチ
- 投稿日:2020-07-11T16:30:50+09:00
ReduxのReducerでのState操作の基本
目次
概要
この記事では、Reduxを使用したときのStoreにあるStateをReducerで操作する際の基本として、主にCRUDアプリケーションをはじめとした非常に多くの場面で利用される「追加、更新、削除」についてまとめています。
ReducerでのState操作の方法が分からなくなった時にこの記事を参考にしていただけるようであれば幸いです。
なおこの記事では、Reactを使用していることを前提としています。前提知識
- Reduxの基本的な知識を身につけている(Reduxの基本についてはこちらの記事にまとめています)
- JavaScript(ES2015以降)で使用できる構文をある程度知っている
環境
導入ライブラリ version React 16.13.1 react-redux 7.2.0 redux 4.0.5 lodash 4.17.15 ReducerでState操作
State操作の掟
Reduxで扱うStateはすべて、Storeというところに格納されています。
そしてそのStoreに格納されているStateは直接変更してはいけません。(イミュータブル)
ではStateを変更したい場合にはどのようにStateを変更すればいいのか以下に操作例をまとめます。Stateの操作例
配列のState操作
悪い例
// Reducer export default (state=[], action) => { switch(action.type){ // 追加する場合 case 'ADD': return state.push('hi'); // 更新する場合 case 'REPLACE': return state[0] = 'bye'; // 削除する場合 case 'REMOVE': return state.pop(); default: return state; }; };この場合、エラーとなるかあるいは正しくStateが変更されません。
正しい例
// Reducer export default (state=[], action) => { switch(action.type){ // 追加する場合 case 'ADD': return [...state, 'hi']; // 更新する場合 case 'REPLACE': return state.map(element => element === 'hi' ? 'bye' : element); // 削除する場合 case 'REMOVE': return state.filter(element => element !== 'hi'); default: return state; }; };このように書くことで配列のStateを正しく変更することができます。
オブジェクトのState操作
悪い例
// Reducer export default (state={}, action) => { switch(action.type){ // 追加する場合 case 'ADD': return state.age = '30'; // 更新する場合 case 'UPDATE': return state.name = 'Sum'; // 削除する場合 case 'REMOVE': return delete state.name; default: return state; }; };この場合、配列のときと同様にエラーとなるか正しくStateが変更されません。
正しい例
// Reducer import _ from 'lodash'; export default (state={}, action) => { switch(action.type){ // 追加する場合 case 'ADD': return {...state, age: 30}; // 更新する場合 case 'UPDATE': return {...state, name: 'Sum'}; // 削除する場合 case 'REMOVE': return {...state, age: undefined}; // または return _.omit(state, 'age'); // lodashを使用 default: return state; }; };オブジェクト構造のStateの操作はこのように記述します。
配列やオブジェクトの操作で
...state
といったようにスプレッド構文を使用していることに注目しましょう。これは現在あるStateを変更するのではなく、新たに現在のStateをコピーを生成して、その中で変更を行いそれを新たにStateとしてStoreに格納していることになります。ReduxにおけるStateのようにオブジェクトや配列などをイミュータブルに扱いたい場合にスプレッド構文が活躍します。
オブジェクトのキー補間構文
一部ではKey interpellation syntaxと呼ばれていました。
これは一体なんなのかというと、コードを見ていただいた方が早いと思うので以下で紹介します。const countryCapital = { japan: 'Tokyo', france: 'Paris' }; const country = 'china'; const capital = 'Beijing'; {...countryCapital, [country]:capital} // -> { japan: 'Tokyo', france: 'Paris', china: 'Beijing' }ここで注目してもらいたいのが、
{...countryCapital, [country]:capital}
です。{...countryCapital}
は先程説明したスプレッド構文ですが、その後に続く[country]:capital
の部分が見慣れない構文なのではないかと思います。この[country]
は配列ではありません。今回の場合ですと、オブジェクトのキーとしてcountry
を渡していることになります。すなわち[country]:capital === china:'Beijing'
ということになります。
この構文を知っていれば、ReducerのState操作を便利にしてくれるはずなので、覚えておきましょう。まとめ
今回はReduxでのState操作の方法についてまとめました。
操作方法の例を紹介しましたが、前提として最も重要なことはStateは直接操作せずイミュータブルに扱うということです。
そのための手段としてスプレッド構文やlodashライブラリ、キー補間構文などがあるということを抑えておきましょう。参考資料