20200601のReactに関する記事は5件です。

reactでシューティングゲーム

canvasの勉強をするために本に載ってたシューティングゲームの写経をした。
ただ、写経するだけではつまらないので、以下のように変更しながら学習をした。

技術スタック
フレームワークなし、jsReact + Redux Toolkit + Redux Saga + TypeScript

設計
継承のみ活用継承、DIを活用

https://github.com/pokotyan/shooting-game-react
スクリーンショット 2020-06-01 22.34.10.png
ここで実際に遊べる。(PCのみ)

コード書いてて感じたのはcanvasでゲームを作るときに、reactやreduxとかのフレームワークはいらないなと感じた。ただ複雑になってるだけのように感じる。reactとcanvasは相性悪いみたいな記事も作ってる最中に見かけたりした。

あと、Redux Toolkit が結構いい感じだった。createSlice でreducerとactionの両方が自動で作られるのは楽。ただ、ミドルウェアはthunkしか対応してなくて、sagaを使うためにはちょっとガチャガチャしないといけないのがめんどかった。

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

React[基礎知識編]

Reactとは

世界的に有名なJavaScriptのライブラリです。
Reactはサイトの見た目の部分を作ることができ、
Facebook、Airbnb、Dropboxなどの有名企業も多く使用しています。

雛形

// Reactをimport
import React from 'react';

// React.Compornentを継承するクラスの定義
class アプリ名 extends React.Compornent {
 constructor(props) {
  super(props);
  this.state = {変数: }
 }

 イベント名() {

 }

// JSXを戻り値とするrenderメソッドを定義
  render() {
  return (
     この中でJSXを記述する。
   )
  }
}

// クラスをexport
export default アプリ名;

JSXとは

Reactで使うHTMLのことを言います。ほぼHTMLと同じですが呼び方が違います。

複数の要素がある場合には < div >タグを使う。

render() {
 return (
  <h1>見出し1</h1>
  <h2>見出し2</h2>
  // <img>タグには閉じタグ(/)が必要
  <img src = '----'/>
 )
}

イベントの書き方

イベント名 = {() => { 処理 }} とすることでイベントを設定できます。

<button onClick = {() => { 処理 }}

stateとは

ユーザーの動きに合わせて変わる値のことをいいます。
stateは、constructorの中で、オブジェクトとして定義します。
ここで定義したオブジェクトがstateの初期値となります。
その他の部分の、定型文として覚えておけば大丈夫です。

constructor(props) {
  super(props);
  this.state = {変数: }
}

stateの表示

this.state.プロパティ名とすることで、指定したstateのプロパティ名に対応する値を取得できます。

constructor(props) {
  super(props);
  this.state = {name: '田中'}
 }
  render() {
  return (
     <h1>こんにちは、{this.state.name}さん!</h1>
   )
  }

stateの変更

this.setState({プロパティ名: 変更する値})とすることで、指定されたプロパティに対応するstateの値が変更される。
Reactでは、「stateの値に直接代入することで値を変更してはいけない」という決まりがあります。
値を変更したい場合は、setStateを使いましょう。

<button onClick = {() => {this.setState({name: '佐藤'})}}>佐藤</button>

コンポーネントとは

コンポーネントは「部品」や「パーツ」という意味です。
Reactでは、見た目を機能ごとにコンポーネント化して、コンポーネントを組み合わせることでWebサイトの見た目を作ります。
クラス名がコンポーネント名となり、下記の場合はLanguageがコンポーネント名となる。

Language.js
import React from 'react';

class Language extends React.Component {
  render() {
   return(
     JSXの記述
   );
}

コンポーネントの表示

1.作成したコンポーネントをexportする

Language.js
export default Language;

2.App.jsで、コンポーネントをインポートし、JSX内に記述する。

App.js
import React from 'react';

// コンポーネントをインポート
import Language from './Language.js';

class Language extends React.Component {
  render() {
   return(
     // JSX内に<コンポーネント名 />を記述する
     <Language />
   );
}

propsとは

propsは、「props名=値」という形で、コンポーネントを呼び出す箇所で渡します。
タグの中身は、改行してあげることで見やすくなります。
渡されたpropsは、this.propsで取得できます。
this.propsは{ props名: 値}というオブジェクトになります。

<Language
  prop = 
/>

propsの取得

this.propsと書くことで{props名: 値}というオブジェクトを取得できるので、「this.props.props名」とすることでpropsの値を取得できます。

      <div className='language-item'>
        <div className='language-name'>{this.props.name}</div>
        <img 
          className='language-image' 
          src={this.props.image} 
        />
      </div>

mapメソッドを利用する

mapメソッドで配列fruitListの各要素に対して順に処理を行い、各要素を < p >タグで囲んで表示しています。mapメソッドの戻り値はJSXなので、引数であるfruitItemは中括弧{}で囲むことに注意しましょう。

{languageList.map((languageItem) => {
  return (
    <Language
      name = {languageItem.name}
      image = {languageItem.image}
    />
  )
})}

コンポーネントが表示される流れ

コンポーネント

↓jsx

App.js

↓jsx

index.js

↓html

index.html

index.jsの中身は以下の記述を記載する。
index.js
import App from './compornents/App';
ReactDOM.render(<App />,document.getElementById('root');
index.htmlの中身は以下の記述を記載する。
<div id = 'root'>
 // 指定したid名の要素の中に挿入される
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ROSトピックをインターネット経由で送受信できるWebフロントエンドの作成チュートリアル(React + ROS + Rowma)

このチュートリアルではReactを使ってROSのトピックをインターネット越しにPublish/SubscribeできるWebフロントエンドを、Rowmaというシステムを使って実装していきます。

以下のキャプチャは完成図です。左がReactで作ったWebフロントエンドで右がROS側のターミナルです。
Peek 2020-05-08 22-50.gif
まず/chatterトピックをSubscribeしてWebブラウザでトピックを受け取ります。そして/chatterトピックをWebブラウザからロボットに対してPublishします。ローカルホストでやり取りしているように見えますがちゃんとインターネット経由でデータを送受信しています。

このチュートリアルにあたってROSに関する知識はあった方がいいですが、Reactの方は無くても大丈夫です。React部分のフォローアップはなるべくするようにします。

ここではReactとは何か?の部分については特に触れませんので知らない人は適当に調べてみてください。雑に言うとHP作成JavaScriptライブラリです。

Rowmaとは

WebブラウザとROSのつなぎ込みの部分にはRowmaというシステムを使います。
詳しくはRowma: ROSロボットネットワーク化システムで紹介しているのですが、簡単に触れておきます。RowmaはROSベースロボットのネットワーク化を実現するOSSです。具体的に言うとRowmaネットワークに接続しているロボットやアプリケーション(PCやスマホ、プログラム等)同士で相互にインターネット経由でデータをやり取りできるシステムです。

RowmaにはJavaScript SDKがあるのでこのSDKを使って実装していきます。なおTypeScript対応です。

準備するもの

  • インターネットに繋がったROSが動くPC
  • インターネットに繋がったnodejsがインストールされたPC

ROSが動くPCとReact開発するPCは同じでも問題ありません。なおReact開発に必要なnodejsのインストールは各自でお願いします。

参考: React 開発環境構築

(準備) /chatterノードを作成

まず最初の準備としてここではシンプルな/chatterノードを作成します。以下を実行してchatterパッケージを作成します。このノードのベースはWriting a Simple Publisher and Subscriber (Python)です。

cd ~/catkin_ws/src
catkin_create_pkg chatter std_msgs rospy
cd ~/catkin_ws/src/chatter
mkdir scripts

~/catkin_ws/src/chatter/scripts/talkerを作成します。1秒に1回/chatterに文字列をPublishするノードです。

talker
#!/usr/bin/env python
import rospy
from std_msgs.msg import String

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('talker', anonymous=True)
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

if __name__ == '__main__':
    talker()

次に~/catkin_ws/src/chatter/scripts/listenerを作成します。

listener
#!/usr/bin/env python
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)

def listener():
    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback)
    rospy.spin()

if __name__ == '__main__':
    listener()

最後に実行権限を付けます。

chmod +x ~/catkin_ws/src/chatter/scripts/talker
chmod +x ~/catkin_ws/src/chatter/scripts/listener

これで完了です。

RowmaROSのインストールと起動

最初にROS用PCでRowma用ノードを立ち上げておきます。

RowmaROSはRowmaネットワークに接続するためのROSパッケージとなります。起動するだけでワークスペースの情報をサーバーに送信して接続を待ち受けます。

なお、デフォルトだと公開サーバーにロボットを登録するため注意が必要です。

ROS用PCで以下を実行してパッケージをインストールします。ワークスペースのパスにrowma_rosがインストールされます。

python <(curl "https://raw.githubusercontent.com/rowma/rowma_ros/master/install.py" -s -N)

インストールができたらノードを起動します。

rosrun rowma_ros rowma

実行が正しく成功すると以下のような出力が見えると思います。

$ rosrun rowma_ros rowma
(中略)
rowma_ros version 0.0.3
connection established
Your UUID is: xxxx-xxxx-xxxx

このUUIDがロボットのRowmaネットワーク上の識別子となります。あとで使います。

ではこのノードは起動したままでReactを使ったWebフロントエンド実装作業を行います。

新規Reactプロジェクトの作成

create-react-appというツールを使って新規プロジェクトを作成します。以下を実行してください。実行するとrowma-react-tutorial/ディレクトリが作成されます。プロジェクトの作成場所はどこでも構いません。

npx create-react-app rowma-react-tutorial --template typescript --use-npm

完了したら実行します。

cd rowma-react-tutorial
npm start

ブラウザでlocalhost:3000にアクセスして以下のページが見えたら成功です。

image.png

ref: https://create-react-app.dev/docs/adding-typescript/

rowma_jsのインストール

RowmaのJS用SDKであるrowma_jsをインストールします。rowma-react-tutorialディレクトリにて以下を実行します。

npm i rowma_js

1. ConnectionManagerへの接続

やっとコーディングを開始します。ここではReactからConnectionManagerというWebSocketサーバーに接続をします。RowmaではConnectionManagerが中心となってロボットを接続したりロボットに対してデータを送信したりします。そこでロボットに対してデータを送信するため、まずはConnectionManagerに接続する必要があります。

image.png

ここではConnectionManagerに接続中のロボットのUUID一覧を取得して表示するコードを書きます。なおデフォルトでは https://rowma.moriokalab.com に設置されている公開サーバーに接続されます。

App.tsxを以下のように変更します。(以下はApp.tsx全文)

App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import Rowma from 'rowma_js';

function App() {
  const [rowma, setRowma] = useState<any>(null);
  const [robotUuids, setRobotUuids] = useState<Array<string>>([]);
  const [selectedRobotUuid, setSelectedRobotUuid] = useState<string>('');

  useEffect(() => {
    const _rowma = new Rowma();
    setRowma(_rowma);

    _rowma.currentConnectionList().then((connList: any) => {
      setRobotUuids(connList.data.map((robot: any) => robot.uuid));
    })
  }, [])

  const handleConnectionListChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedRobotUuid(event.target.value)
  }

  return (
    <div className="App">
      <select onChange={handleConnectionListChange}>
        <option value=''>{''}</option>
        {robotUuids.length > 0 && (
          robotUuids.map((uuid: string) => {
            return(
              <option key={uuid} value={uuid}>{uuid}</option>
            )
          })
        )}
      </select>
    </div>
  );
}

export default App;

ブラウザを見るとこのようになっておりセレクトボックス内に接続中のロボットのUUIDが見えるはずです。

image.png

1.1 コード解説

最初なのでコードの解説をしておきます。なおここ以降の変更は似た感じなので解説はスキップします。

App.tsx
  const [rowma, setRowma] = useState<any>(null);
  const [robotUuids, setRobotUuids] = useState<Array<string>>([]);
  const [selectedRobotUuid, setSelectedRobotUuid] = useState<string>('');

まずこの4行です。これらは共通してuseState関数を呼び出していますね。これはReactのステートフック関数と呼ばれるもので、コンポーネント中の状態を管理してくれるものです。

これは分かってる人が分かってる人に対してする説明なので、よくわからないという人はReactで使う変数のようなものだと捉えておいてください。この4行は変数の宣言というわけです。

これを使うと何が良いのかというと、変数の中身が書き換わると自動で表示も書き換わるということです。

useStateの使い方は最初は

const[変数名, 変数に値を代入する関数の名前] = useState<変数の型>(変数の初期値);

で覚えていてよいと思います。


App.tsx
  useEffect(() => {
    const _rowma = new Rowma();
    setRowma(_rowma);

    _rowma.currentConnectionList().then((connList: any) => {
      setRobotUuids(connList.data.map((robot: any) => robot.uuid));
    })
  }, [])

次はこのuseEffectから始まる部分です。これはReact Hooksと呼ばれるものでReactコンポーネントのライフサイクルイベントを検知して処理を行えるものです。

はい、これも分かってる人が分かってる人に対してする説明です。分からない人はページがロードされた時、変数の値が更新された時、ページ移動が起こる時に実行される部分くらいのものだと捉えておきましょう。

ここでようやくrowma_jsの関数が出てきました。最初はcurrentConnectionList()です。

これは名前通り現在ConnectionManagerに接続しているロボットの一覧を取得する関数となっていて、ここではuseEffectの中で呼ばれているのでページがロードされたタイミングでロボット一覧をrobotUuidsに代入しています。


App.tsx
  const handleConnectionListChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedRobotUuid(event.target.value)
  }

  return (
    <div className="App">
      <select onChange={handleConnectionListChange}>
        <option value=''>{''}</option>
        {robotUuids.length > 0 && (
          robotUuids.map((uuid: string) => {
            return(
              <option key={uuid} value={uuid}>{uuid}</option>
            )
          })
        )}
      </select>
    </div>
  );

残りの部分です。残りは主にセレクトタグを表示する部分です。

ここで大事なのが以下の部分になります。これはrobotUuids配列の中の値を全てセレクトボックスの中に表示するためのループです。こういう場合普通のforループやforEach関数を使うとうまくいかない場合が多いのでmap関数を使うようにしましょう。

App.tsx
        {robotUuids.length > 0 && (
          robotUuids.map((uuid: string) => {
            return(
              <option key={uuid} value={uuid}>{uuid}</option>
            )
          })
        )}

次にselectタグ本体です。

App.tsx
      <select onChange={handleConnectionListChange}>

ここでonChangeはセレクトタグの中の値がクリックされたタイミングで呼ばれる関数のことで、handleConnectionListChange関数が呼び出されています。handleConnectionListChange関数の中では選択されたUUIDをselectedRobotUuidに代入する処理をしています。

2. ロボットに接続してノード一覧を取得する

まず、useStateを使って選択したロボットを格納できるようにします。

App.tsx
  const [robot, setRobot] = useState<any>(null);

次にロボットへの接続とロボット情報の取得を行うhandleConnectClicked関数の実装です。

ここではまずrowma.connect()関数を使ってセレクトボックスでConnectionManagerに接続し、接続状態をsetSocketsocketに代入します。そしてrowma.getRobotStatus()関数を使いロボットの情報を取得しsetRobotrobotに代入します。handleRostopicChangeの中身はあとで書きます。

App.tsx
  const handleConnectClicked = () => {
    rowma.connect(selectedRobotUuid)
    rowma.getRobotStatus(selectedRobotUuid).then((res: any) => {
      setRobot(res.data)
    })
  }

  const handleRostopicChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    console.log(event)
  }

</select>の下にbuttonとdivを追加します。ConnectボタンはクリックされるとhandleConnectClicked関数が呼び出されてロボットへの接続が行われ、robotに値が入るとその下のセレクトボックスが表示されるようになっています。

App.tsx
  return (
    <div className="App">
      <select onChange={handleConnectionListChange}>
        (略)
      </select>
      <button
        disabled={selectedRobotUuid === ''}
        onClick={handleConnectClicked}
      >
        Connect
      </button>
      <div>
        {robot && robot.rostopics.length > 0 && (
          <select onChange={handleRostopicChange}>
            <option value=''>{''}</option>
            {robot.rostopics.map((node: string) => {
              return(
                <option key={node} value={node}>{node}</option>
              )
            })}
          </select>
        )}
      </div>
    </div>
  );

上を実装してlocalhost:3000にアクセスし、自分のロボットのUUIDを選択してConnectボタンを押してノード一覧のセレクトボックスが表示されたら成功です。

Peek 2020-05-08 11-23.gif

3. ロボットにトピックをPublishする

ではロボットへ接続する処理も済んだのでいよいよロボットにROSトピックをPublish/Subscribeする実装に移ります。

まず、上で作成したchatterのlistenrノードを起動しておきます。

rosrun chatter listener

listenerの起動ができたら一度RowmaROSを再起動します。

rosrun rowma_ros rowma

ここまでできたら次にコードを変更していきます。まずはuseStateを追加します。selectedTopicNameはセレクトボックス中から選択したトピックの名前です。

App.tsx
  const [selectedTopicName, setSelectedTopicName] = useState<string>('');

さっきまでconsole.logしか実装されていなかったhandleRostopicChange関数の中身を実装します。ここではsetSelectedTopicNameを使ってセレクトボックス中から選択された値をselectedTopicNameにセットします。

次にhandlePublish関数を実装します。ここが実際にトピックをロボットに送信する本体です。rowma.publish関数がトピックの送信を担当する部分になります。

App.tsx
  const handleRostopicChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedTopicName(event.target.value)
  }

  const handlePublish = () => {
    const currentTime = new Date()
    const msg = { "data": `[${currentTime.toUTCString()}] Topic from browser!` }
    rowma.publish(selectedRobotUuid, selectedTopicName, msg)
  }

{selectedTopicName && (から)}の5行を<div className="App">内の一番最後に置きます。

App.tsx
  return (
    <div className="App">
      (略)
      {selectedTopicName && (
        <button onClick={handlePublish}>
          Publish
        </button>
      )}
    </div>
  );

これらを追加して保存したらブラウザを開いてlocalhost:3000を見ます。

このキャプチャの左側がReactで作ったWebページで、右側がROSの画面になります。画面上でPublishボタンを押すとlistenerノードがトピックを受け取って出力をしていることが分かります(キャプチャ上ではRowmaROSが受け取っているように見えますがちゃんとlistenerノードが受け取っています。これは開発とキャプチャの都合です。)

画面を隣同士に表示しているとローカルホストでデータをやり取りしているように見えますが、インターネット経由でデータをやり取りしているため、離れた場所でも可能です。

Peek 2020-05-08 11-52.gif

4. ロボットのトピックをSubscribeする

Subscribeは少しだけ複雑になっています。Subscribeを行うにはブラウザから「ロボット内に流れるトピック/xxxxをブラウザに転送せよ」という命令を選択中のロボットに送信する必要があります。

image (1).png

まずはTopicのインターフェイスをimportに追加しておきます。

import Rowma, { Topic } from 'rowma_js';

そして他と同じようにuseStateを追加します。これはSubscribeしたトピックを保存する配列となっています。

  const [receivedTopics, setReceivedTopics] = useState<Array<string>>([]);

handleSubscribeButtonClick内のrowma.setTopicRoute関数は「ロボット内に流れるトピック/xxxxをブラウザに転送せよ」という命令を選択中のロボットに送信する部分です。rowma.setTopicRoute関数の概要を以下の図に示しておきます。

image (6).png

handleTopicArrivalはトピック受信時に実行される関数です。ここではreceiveTopics配列に受信したトピックをpushしています。なおuseStateで作った配列のpushは以下のように行うので気をつけてください。

  const handleSubscribeButtonClick = () => {
    rowma.setTopicRoute(selectedRobotUuid, 'application', rowma.uuid, selectedTopicName);
    rowma.subscribe(selectedTopicName, handleTopicArrival)
  }

  const handleTopicArrival = (event: Topic) => {
    setReceivedTopics(topics => [...topics, JSON.stringify(event.msg)])
  }

次に要素を追加します。Publishボタンの隣に<button>要素と受信したトピックをそのまま表示する<div>領域を追加します。

      {selectedTopicName && (
        <div>
          <button onClick={handlePublish}>
            Publish
          </button>
          <button onClick={handleSubscribeButtonClick}>
            Start Subscribe
          </button>
        </div>
      )}
      {receivedTopics.map((topic: string, index: number) => (
        <div key={index}>
          <span>{topic}</span>
        </div>
      ))}

実装が終わったら以下のキャプチャのようになります。Publishボタンを押すたびに送ったトピックが表示されているのが見えるはずです。

これもローカルホストでやり取りしているようにも見えますが、データはブラウザ->サーバー->ロボット->サーバー->ブラウザ、というふうに流れています。

Peek 2020-05-08 17-49.gif

では次にROS側でtalkerノードを立ち上げましょう。ROS内でやり取りされているデータがブラウザでSubscribeできていることがわかると思います。

rosrun rowma_ros talker

Peek 2020-05-08 23-01.gif

まとめ

以上Rowmaを使ったROSトピックの送受信をReactで行うチュートリアルでした。

ここで作ったWebフロントエンドはasmsuechan/rowma-react-tutorialに公開しています。

何か問題を発見した方はここにコメントしたり該当するGitHubリポジトリでissueを立てたりしてください。

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

axios-hooksで楽々非同期処理!reduxにthunkもsagaもいらない?

はじめに

最近暇な時でGraphQLを触っていて、フロントエンドでApollo-clientをはじめて使ったんですが、
すごく使い勝手がいいhooksがあって(多分GraphQL分からなくても伝わるかなぁと):

// Apollo公式より抜粋
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';

const GET_DOGS = gql`
  {
    dogs {
      id
      breed
    }
  }
`;

function Dogs({ onDogSelected }) {
  const { loading, error, data } = useQuery(GET_DOGS); // これです

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <select name="dog" onChange={onDogSelected}>
      {data.dogs.map(dog => (
        <option key={dog.id} value={dog.breed}>
          {dog.breed}
        </option>
      ))}
    </select>
  );
}

これ凄くないか?
非同期処理を一行で終わらせてるぞ。

そこで考えたんですが、reduxのプロジェクトで使えたらいいなと。
…ん?待てよ。非同期関連のaxiosを使う人が山ほどいて、
同じことを考える人絶対いるんでしょ!?

調べたらいました、めちゃくちゃいました、山ほどいました(※そこまではいないです)。
2020-06-01 12-07-00.png
やったぜ。

axios-hooks

さて、今回紹介するのはその一つ:axios-hooksです
なぜこれを選んだのは単純に見比べてほしい機能が一番揃ってるからです。(自分なりに)
以下の例は公式のものです:

import useAxios from 'axios-hooks'

function App() {
  const [{ data, loading, error }, refetch] = useAxios(
    'https://api.myjson.com/bins/820fc'
  )

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error!</p>

  return (
    <div>
      <button onClick={refetch}>refetch</button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

公式のCodeSandboxです

useAxios(url|config, options)

引数

  • url | config - configはaxiosのconfig objectです、なので基本的にaxiosで設定できるものであれば使えます。
  • options - 手動実行とキャッシュの設定です
    • manual - デフォルトはfalseです。trueに設定すればdidMountの時は自動実行しない。大体GETはfalseでCUD(Create, Update, Delete)ではtrueで使うはず。
    • useCache ( true ) - デフォルトはtrueです。

Return object

[{ data, loading, error, response }, execute]

  • data - axiosのresponse.dataです。
  • loading - 名前の通りpending中ではtrueです.
  • error - axios error object
  • response - axios response object
  • execute([config[, options]]) - 手動実行用のfunctionです、引数は基本的に前のconfig、optionsと同じです。

もっと完全な例

自分で書いたものですが、注釈は英語になってます、時間ができたら日本語に直したいと思います。
テスト用APIはwww.mocky.io使わせていただきました。
試したいであれば公式のCodeSandboxにコピペすればできると思います。
(ReactDOMのインポートと下のrender部分は残ってください。)

import React from 'react';
import Axios from 'axios';
import useAxios, {configure} from 'axios-hooks'

// Define axios instance, you could use env to split up production & development
// The useAxios provides directly by axios-hooks use the same instance
// If you need more than one axios instance, explain later
const axiosInstance = Axios.create({baseURL: 'https://www.mocky.io/v2'})
configure({axios: axiosInstance})

// You should define your api url somewhere in your project
const api = {
  getMail: { url: () => '/5ed0a6ea3500005d00ff9d7b?mocky-delay=2000ms', method: 'GET'}, 
  putMail: { url: (id) => `/5ed0a49e3500009300ff9d6b/${id}`, method: 'POST'}
}

function App() {
  // The example to get some data
  // Execute once the component did mount
  const [{data = {iam: 'default data'}, loading, error}] = useAxios({
    url: api.getMail.url(),
    method: api.getMail.method, 
    data: {haha: "yes"}
  })
  // The example to CUD data
  // Pass { manual: true } into useAxios, then it won't execute when component did mount
  const [{data: updatedData}, updateData] = useAxios({method: api.putMail.method}, {manual: true})
  return (
    <div >
      {error && <div>error</div>}
      {data && <div>{`${JSON.stringify(data)} ${loading?'loading':''}`}</div>}
      {updateData && <div>{JSON.stringify(updatedData)}</div>}
      <button onClick={() => {
          // You can set the common authorization header like this way
          axiosInstance.defaults.headers.authorization='test101'
          // Example to update data
          updateData({url: api.putMail.url('myid'), data: {thedata: 'you want to put'}, headers: {'another-header': 'test202'}})
            .then((data) => {console.log(data)}) // Use it by the way you want, even with redux store
      }}>
        test
      </button>
    </div>
  );
}

// If you need more than one axios instance, set up with makeUseAxios & export it
// const anotherInstance = Axios.create({baseURL: 'http://some.another.api.url'})
// export const useAnotherAxios = makeUseAxios({axios: anotherInstance})

export default App;

終わりに

正直redux-sagaを使ったことないので、タイトルではクエスチョンマークを付けてます、ごめんなさい。
余談ですが公式では: axios-hooks is heavily inspired by graphql-hooksと記載しています。
同じgraphqlからの発想で作ったみたいで嬉しいです。

CodeSandboxと注釈は時間ができたら直します。

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

React hooksを基礎から理解する (useEffect編)

React hooksとは

React 16.8 で追加された新機能です。
クラスを書かなくても、stateなどのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。

  • React hooksを基礎から理解する (useState編)
  • React hooksを基礎から理解する (useEffect編) :point_left_tone2: 今ここ
  • React hooksを基礎から理解する (useContext編)
  • React hooksを基礎から理解する (useReducer編)
  • React hooksを基礎から理解する (useCallback編)
  • React hooksを基礎から理解する (useMemo編)
  • React hooksを基礎から理解する (useRef編)

useEffectとは

useEffectを使うと、useEffectに渡された関数はレンダーの結果が画面に反映された後に動作します。
つまりuseEffectとは、「関数の実行タイミングをReactのレンダリング後まで遅らせるhook」になります。

副作用の処理(DOMの書き換え、変数代入、API通信など)を関数コンポーネントで扱えます。
クラスコンポーネントでのライフサイクルメソッドに当たります。

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

参考:React公式サイト 副作用フックの利用法

create-react-appでReactのコードを書く

create-react-appをひさしぶりに npm installしようとしたらテンプレートが出来ない?
困っていて見つけた記事。解決出来ました。ありがとうございます。

参考:ひさしぶりにcreate-react-appしたらテンプレートができなかった時の対処法

Material-UIをinstall

Material-UIをinstallしたら、使いたいコンポーネントをすぐ見つけられるし、勝手にスタイリングしてくれるのでテンションあがります?

$ npm install @material-ui/core

参考:MATERIAL-UI

クリックしたらタイトルも同時に変更されるコンポーネントを作る

クラスコンポーネント

react.js
import React, { Component } from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup';
import Button from '@material-ui/core/Button';

class EffectClass extends Component {
  constructor(props){
    super(props);
    this.state = {
      count: 0,
    }
  }

  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

  render() {
    return (
      <>
        <p>{`${this.state.count}回クリックされました`}</p>
        <ButtonGroup color="primary" aria-label="outlined primary button group">
          <Button onClick={()=> this.setState({count: this.state.count + 1})} >
            ボタン
          </Button>
          <Button onClick={()=> this.setState({count: 0})}>
            リセット
          </Button>
        </ButtonGroup>
      </>
    )
  }
}

export default EffectClass

関数コンポーネント

react.js
import React, {useState, useEffect} from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'

const EffectFunc = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
    </>
  )
}

export default EffectFunc

クラスコンポーネントの場合

副作用は ReactがDOMを更新したあとに起こすようにしたいので、componentDidMountcomponentDidUpdateに記載します。するとReactDOMに変更を加えた後に、document.titleを更新しています。

react.js
  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

関数コンポーネントでuseEffectを使った場合

デフォルトでは、useEffectは毎回のレンダー後に呼ばれます。

react.js
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

React公式サイトのstate とライフサイクルをもう一度読むと理解が進みました。
参考:React公式サイト state とライフサイクル

useEffectの第2引数に配列を渡して特定の値が変わったときだけ再レンダーさせる

useEffect()の第2引数に[count]を渡すと、countに変化があったときだけ再レンダーします。

react.js
import React, {useState, useEffect} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input';

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
    },
  },
}));

const EffectFunc = () => {
  const classes = useStyles();
  const [count, setCount] = useState(0)
  const [name, setName] = useState({
    lastName: '',
    firstName: ''
  })
  useEffect(() => {
    document.title =`${count}回クリックされました`
  },[count])

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
      <p>{`私の名前は${name.lastName} ${name.firstName}です`}</p>
      <form className={classes.root} noValidate autoComplete="off">
        <Input
          placeholder=""
          value={name.lastName}
          onChange={(e)=>{setName({...name,lastName: e.target.value})}}/>
        <Input
          placeholder=""
          value={name.firstName}
          onChange={(e)=>{setName({...name,firstName: e.target.value})}}/>
      </form>
    </>
  )
}

export default EffectFunc
useEffectの第2引数に[count]を取るとき
react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[count])

nameが更新されても再レンダーされず、countが更新された場合だけ、document.titleが再レンダーされていることがわかります。

第2引数に[count]を取らないとき

useEffectはデフォルトで、副作用関数は初回のレンダー時および毎回の更新時に呼び出されます。

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  })

countが更新された場合だけだけでなく、nameが更新された場合にも、document.titleが再レンダーされていることがわかります。

第2引数にからの配列[]を取ったとき

レンダリングは初回のみで再レンダーされないので、document.titleは更新されません。

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[])

クリーンアップについて

クリーンアップとはイベントリスナの削除、タイマーのキャンセルなどのことです。
クリーンアップ関数をreturnすると、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

クラスコンポーネントの場合

componentWillUnmountは、クリーンアップ(addEventLitenerの削除、タイマーのキャンセルなど)に使用されます。componentDidMountに副作用を追加し、componentWillUnmountで副作用を削除します。

react.js
componentDidMount() {
  elm.addEventListener('click', () => {})
}

componentWillUnmount() {
  elm.removeEventListener('click', () => {})
}
関数コンポーネントの場合

上記に相当するhookは以下。「クリーンアップ関数」をreturnすることで、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

react.js
useEffect(() => {
   elm.addEventListener('click', () => {})

  // returned function will be called on component unmount 
  return () => {
     elm.addEventListener('click', () => {})
  }
}, [])

最後に

次回は useContext について書きたいと思います。

参考にさせていただいたサイト

https://reactjs.org/

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