20200711のReactに関する記事は10件です。

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-allyeslint-plugin-jsx-a11y
でした。。

動画教材だとコマンドとかをコピペできないので、こう言うことが起こりがち・・。

以下コマンドを実行で無事ダウンロードできました。
npm install --save--dev eslint-plugin-jsx-a11y

しょーもないミスに時間取られないようにちゃんとしよ・・。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで外部リンクする方法(ダウンロードリンク作る方法)

<a href='/somefile.txt' download>Click to download</a>

これで行けたわ

https://stackoverflow.com/questions/50694881/how-to-download-file-in-react-js

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reactで内部リンク

import { Link } from 'react-router-dom';


<Link to="/Path" > クリック </Link> 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reactで内部リンク『同じドメイン化しかむり』

import { Link } from 'react-router-dom';


<Link to="/Path" > クリック </Link> 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Routerを使ってWebサイトを作ってみる

先日React Routerを学習したので、そのアウトプットとして何か作ってみたいと思い、自分のアメリカ留学をテーマにした簡単なサイトを作成してみました!

以下、サイトのURLとGitHubリポジトリです。(サイトはGitHub Pagesにデプロイしています。)
サイトURL:https://taikiyamano.github.io/MyLifeInTheUS/#/
Githubリポジトリ:https://github.com/TaikiYamano/MyLifeInTheUS

また、今回のWebサイトを作成するにあたり、こちらの記事を参考にさせて頂きました:pray:

ディレクトリ構造

今回作成したWebサイトのディレクトリ構造は以下のようになります。(create-react-appコマンドでデフォルトで作成されるいくつかのファイルは削除しています。)
スクリーンショット 2020-07-11 13.46.56.png

componentsディレクトリの中に各ルーティングに対するページのディレクトリ(DanceやStudyなど)を作り、その中に各フォルダ名に対応するJSXファイルを作成しています。今回はディレクトリの中にJSXファイルしかないので、コンポーネント毎にディレクトリを作成する必要もなかったのですが、今後コンポーネント毎に別のCSSを当てるということが起きた先に柔軟に対応できるよう、componentsディレクトリの中に各コンポーネント用のディレクトリを作成しました。

imagesディレクトリの中には各ページで表示する為の画像を入れたディレクトリがあり、Page.jsxにはページ全体のヘッダーとヘッダーメニュー、ルーティングを記述しています。Page.cssはページ全体のCSSを記述していて、App.jsでPageコンポーネント(Page.jsxの内容)とPage.cssを読み込んでいます。

App.js

App.js
import 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.jsx
import 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ページが返ってきます:sweat_smile: 原因としては、リロードするとサーバーまでリクエストが走ってしまい、「そのパスにはページが存在していないよ!」と返されるかららしいです。一方、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.jsx
import 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へのデプロイはこちらの記事を参考にさせて頂きました!:pray:
ただ、僕の場合、サイトを作成し始める段階で、すでにコードを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ページが表示されました:sweat_smile:
設定項目は以下のようになります。
スクリーンショット 2020-07-11 17.47.36.png

最後に

今回、React-Routerのアウトプットとして1つ簡単なWebサイトを作成してみましたが、やはり何かを作ってみると知識の定着が早いなと個人的に感じました!

また、サイトのCSSがしょぼい割にCSSでのサイトデザインや、GitHub Pagesへのデプロイに時間が掛かってしまったのが、少し悔しかったところです:sweat_smile:
デザインもダサいし、写真の読み込みが遅いなど改善点もあるので、今後少しずつ改善していけたらと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React のチュートリアルを Typescript でやってみた3(タイムトラベルの実装)【完】

目次

概要

チュートリアル通りにやりますが、端折る箇所もあると思います。

各々保管をお願いいたします。

公式のチュートリアルページ

今回の実施チュートリアル内容

タイムトラベル機能の追加

  1. 着手の履歴の保存
  2. State のリフトアップ、再び
  3. 過去の着手の表示
  4. key を選ぶ
  5. タイムトラベルの実装

1.着手の履歴の保存

特にソースの変更なし
どこにhistoryを作成するべきか考える。

2.State のリフトアップ、再び

game.tsxに作成する必要があるためStateとPropsを作成。
historyに関しては、1つの要素に1ステージのデータが入っているとしたものを作成しました。
こうしないと後々エラーになりました(´・ω・`)

game.tsx
type stage = { squares: Array<String>; }

type State = {
  history: Array<stage>;
  xIsNext: boolean;
}

type Props = {}

その後コンストラクタを追加

game.tsx
  constructor(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.tsx
type Porps = {
  squares: Array<String>;
  onClick: (i: number) => void;
}

変更2:classの引数とコンストラクタの変更

board.tsx
class Board extends React.Component<Porps> {

  constructor(props: Porps) {
    super(props)
  }

変更3:renderSquareの内容の変更

board.tsx
  renderSquare(i: number) {
    return <Square
      value={this.props.squares[i]}
      onClick={() => this.props.onClick(i)}
    />;
  }

変更4:handleClick及びcalculateWinnerのfunctionをgame.tsxに移動

大まかにはこのくらいまだまだ変更する必要がありますがとりあえずはって感じです。

game.tsxのrenderを書き換えていきます。
以下のような感じになりました。

game.tsx
  render() {
    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.tsx
  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>
    );
  }

game.tsxに移動したhandleClickを書き換えます。
以下のような感じになりました。
ここのhistory: history.concat({ squares: squares }),でかなり躓きました・・・
このようにするためにgame.tsxのStateがあのような形になった感じです。
もっといい方法があればオシエテクダサイ・・・

game.tsx
  handleClick(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.tsx
  render() {
    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.tsx
      return (
-        <li>
+        <li key={move}>
          <button /* onClick={() => this.jumpTo(move)} */>{desc}</button>
        </li>
      );

historyの機能のために
stepNumberを追加します。

game.tsx
type 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.tsx
  jumpTo(step: number) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }

+          <button onClick={() => this.jumpTo(move)} >{desc}</button>

stepNumberの機能をhandleClickに反映させます。
チュートリアルのソースのままです。

game.tsx
  handleClick(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.tsx
    const history = this.state.history;
+    const current = history[this.state.stepNumber];
    const winner = this.calculateWinner(current.squares);

まとめ

駆け足になりましたが、以上になります。
どのくらい吸収できたのかわかりませんがとりあえずチュートリアルは以上です。
何かしら自分で作ったほうがいいかもしれませんね。

ソースは以下
- チュートリアル完成

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.tsx
import 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.tsx
import React from "react";

interface Props {}

const HelloPage: React.FC<Props> = (props: Props) => {
  return <div className="HelloPage">hello</div>;
};

export default HelloPage;
src/TopPage/TopPage.tsx
import 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アプリ開発環境を作成する

おわりに

以上のことをやれば簡単に開発に取り掛かることができると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React のチュートリアルを Typescript でやってみた2(ゲーム完成まで)

目次

概要

チュートリアル通りにやりますが、端折る箇所もあると思います。

各々保管をお願いいたします。

公式のチュートリアルページ

今回の実施チュートリアル内容

ゲームを完成させる

  1. Stateのリストアップ
  2. イミュータビリティは何故重要なのか
  3. 関数コンポーネント
  4. 手番の処理
  5. ゲーム勝者の判定

1.Stateのリストアップ

Squareに値を当てられるようにする

チュートリアルページの様にBoardでSquareの値を管理するように書いてみます。

まずは、square.tsxの修正
Propsでデータを受け取れるようPropsを定義します。
このとき使用しなくなったStateは削除しておきます。

また、valueの方はString型を定義します。
今回扱う文字がXOのためになりますが、チュートリアルでは他にも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.tsx
type State = {
  squares: Array<String>;
}

次にコンポーネントの引数の部分について修正
以下のようにしました。
{}の部分は、Propsに当たります。今回は使用していないため空のものを設定
わかりにくい場合は、Propsの空の定義を作成し入れてあげるといいでしょう。
どちらがいいのかわからないです。その時時によると思います。

board.tsx
class Board extends React.Component<{}, State> {

以下は丁寧にした感じのソースです。

type Props = {}

class Board extends React.Component<Props, State> {

次にコンストラクタを定義します。
以下の用な感じにこちらはほぼチュートリアルのソース通りです。

board.tsx
  constructor(props: {}) {
    super(props)
    this.state = {
      squares: Array<String>(9).fill(""),
    }
  }

あとは、Squareのコンポーネントにstateのデータを指定するだけです。

board.tsx
  renderSquare(i: number) {
    return <Square value={this.state.squares[i]} />;
  }

以上で、Squareに文字列を設定することができるようになります。

マス目がクリックされたときの動作を作成する

マス目がクリックされたときの動作を作成します。
まずは、square.tsxのPropsを修正します。
boardから渡ってくる動作を受け取れるように以下の様にonClickを設定します。

square.tsx
type Props = {
  value: String
+  onClick: () => void;
}

Boardで追加したonClickの部分について作成していきます。
まずは、board.tsxでSquareにonClickを渡す部分を追加します。

board.tsx
  renderSquare(i: number) {
    return <Square
      value={this.state.squares[i]}
      onClick={() => this.handleClick(i)}
    />;
  }

次にhandleClickについて作成します。

board.tsx
  handleClick(i: number) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
  }

最後に渡したonClickの動作を動作するようにします。

square.tsx
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }

2.イミュータビリティは何故重要なのか

特にソースを変更する必要なし。

3.関数コンポーネント

Squareクラスを関数コンポーネントに書き換えます。
引数の部分に型を入れるだけになった。

square.tsx
function Square(props: Props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

4.手番の処理

とりあえず、Stateの中にユーザ判定用のフラグを用意しました。

board.tsx
type State = {
  squares: Array<String>;
+  xIsNext: boolean;
}

コンストラクタ内で初期値の設定を追加

board.tsx
  constructor(props: {}) {
    super(props)
    this.state = {
      squares: Array<String>(9).fill(""),
+      xIsNext: true,
    }
  }

マス目がクリックされた際の動作を追加します。
追加した動作
1. ○と×の入力切替
2. プレイヤーのフラグの変更

board.tsx
  handleClick(i: number) {
    const squares = this.state.squares.slice();
+    squares[i] = this.state.xIsNext ? 'X' : '';
    this.setState({
      squares: squares,
+      xIsNext: !this.state.xIsNext,
    });
  }

表示プレイヤーの切り替え

board.tsx
  const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '');

5.ゲーム勝者の判定

勝敗判定のfunctionを追加します。
引数はStringの配列に設定

board.tsx
  calculateWinner(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.tsx
  render() {
-    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.tsx
  handleClick(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,
    });
  }

まとめ

とりあえずゲーム完成までやってみました。
公式通りにやったためそこまで詰まるところはなかった。
型をちゃんと意識すれば良さそうですね。

ここまでのソースは以下になります
- ゲーム完成までのブランチ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ライブラリ、キー補間構文などがあるということを抑えておきましょう。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む