- 投稿日:2020-06-01T23:35:09+09:00
reactでシューティングゲーム
canvasの勉強をするために本に載ってたシューティングゲームの写経をした。
ただ、写経するだけではつまらないので、以下のように変更しながら学習をした。技術スタック
フレームワークなし、js
→React + Redux Toolkit + Redux Saga + TypeScript
設計
継承のみ活用
→継承、DIを活用
https://github.com/pokotyan/shooting-game-react
ここで実際に遊べる。(PCのみ)コード書いてて感じたのはcanvasでゲームを作るときに、reactやreduxとかのフレームワークはいらないなと感じた。ただ複雑になってるだけのように感じる。reactとcanvasは相性悪いみたいな記事も作ってる最中に見かけたりした。
あと、Redux Toolkit が結構いい感じだった。
createSlice
でreducerとactionの両方が自動で作られるのは楽。ただ、ミドルウェアはthunkしか対応してなくて、sagaを使うためにはちょっとガチャガチャしないといけないのがめんどかった。
- 投稿日:2020-06-01T19:40:51+09:00
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.jsimport React from 'react'; class Language extends React.Component { render() { return( JSXの記述 ); }コンポーネントの表示
1.作成したコンポーネントをexportする
Language.jsexport default Language;2.App.jsで、コンポーネントをインポートし、JSX内に記述する。
App.jsimport 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.jsimport App from './compornents/App'; ReactDOM.render(<App />,document.getElementById('root');index.htmlの中身は以下の記述を記載する。
<div id = 'root'> // 指定したid名の要素の中に挿入される </div>
- 投稿日:2020-06-01T16:06:33+09:00
ROSトピックをインターネット経由で送受信できるWebフロントエンドの作成チュートリアル(React + ROS + Rowma)
このチュートリアルではReactを使ってROSのトピックをインターネット越しにPublish/SubscribeできるWebフロントエンドを、Rowmaというシステムを使って実装していきます。
以下のキャプチャは完成図です。左がReactで作ったWebフロントエンドで右がROS側のターミナルです。
まず/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にアクセスして以下のページが見えたら成功です。
ref: https://create-react-app.dev/docs/adding-typescript/
rowma_jsのインストール
RowmaのJS用SDKであるrowma_jsをインストールします。rowma-react-tutorialディレクトリにて以下を実行します。
npm i rowma_js1. ConnectionManagerへの接続
やっとコーディングを開始します。ここではReactからConnectionManagerというWebSocketサーバーに接続をします。RowmaではConnectionManagerが中心となってロボットを接続したりロボットに対してデータを送信したりします。そこでロボットに対してデータを送信するため、まずはConnectionManagerに接続する必要があります。
ここではConnectionManagerに接続中のロボットのUUID一覧を取得して表示するコードを書きます。なおデフォルトでは
https://rowma.moriokalab.com
に設置されている公開サーバーに接続されます。App.tsxを以下のように変更します。(以下はApp.tsx全文)
App.tsximport 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が見えるはずです。
1.1 コード解説
最初なのでコードの解説をしておきます。なおここ以降の変更は似た感じなので解説はスキップします。
App.tsxconst [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.tsxuseEffect(() => { 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.tsxconst 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.tsxconst [robot, setRobot] = useState<any>(null);次にロボットへの接続とロボット情報の取得を行う
handleConnectClicked
関数の実装です。ここではまず
rowma.connect()
関数を使ってセレクトボックスでConnectionManagerに接続し、接続状態をsetSocket
でsocket
に代入します。そしてrowma.getRobotStatus()
関数を使いロボットの情報を取得しsetRobot
でrobot
に代入します。handleRostopicChange
の中身はあとで書きます。App.tsxconst 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.tsxreturn ( <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ボタンを押してノード一覧のセレクトボックスが表示されたら成功です。
3. ロボットにトピックをPublishする
ではロボットへ接続する処理も済んだのでいよいよロボットにROSトピックをPublish/Subscribeする実装に移ります。
まず、上で作成したchatterのlistenrノードを起動しておきます。
rosrun chatter listenerlistenerの起動ができたら一度RowmaROSを再起動します。
rosrun rowma_ros rowmaここまでできたら次にコードを変更していきます。まずは
useState
を追加します。selectedTopicName
はセレクトボックス中から選択したトピックの名前です。App.tsxconst [selectedTopicName, setSelectedTopicName] = useState<string>('');さっきまで
console.log
しか実装されていなかったhandleRostopicChange
関数の中身を実装します。ここではsetSelectedTopicName
を使ってセレクトボックス中から選択された値をselectedTopicName
にセットします。次に
handlePublish
関数を実装します。ここが実際にトピックをロボットに送信する本体です。rowma.publish
関数がトピックの送信を担当する部分になります。App.tsxconst 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.tsxreturn ( <div className="App"> (略) {selectedTopicName && ( <button onClick={handlePublish}> Publish </button> )} </div> );これらを追加して保存したらブラウザを開いてlocalhost:3000を見ます。
このキャプチャの左側がReactで作ったWebページで、右側がROSの画面になります。画面上でPublishボタンを押すとlistenerノードがトピックを受け取って出力をしていることが分かります(キャプチャ上ではRowmaROSが受け取っているように見えますがちゃんとlistenerノードが受け取っています。これは開発とキャプチャの都合です。)
画面を隣同士に表示しているとローカルホストでデータをやり取りしているように見えますが、インターネット経由でデータをやり取りしているため、離れた場所でも可能です。
4. ロボットのトピックをSubscribeする
Subscribeは少しだけ複雑になっています。Subscribeを行うにはブラウザから「ロボット内に流れるトピック/xxxxをブラウザに転送せよ」という命令を選択中のロボットに送信する必要があります。
まずはTopicのインターフェイスをimportに追加しておきます。
import Rowma, { Topic } from 'rowma_js';そして他と同じように
useState
を追加します。これはSubscribeしたトピックを保存する配列となっています。const [receivedTopics, setReceivedTopics] = useState<Array<string>>([]);
handleSubscribeButtonClick
内のrowma.setTopicRoute
関数は「ロボット内に流れるトピック/xxxxをブラウザに転送せよ」という命令を選択中のロボットに送信する部分です。rowma.setTopicRoute
関数の概要を以下の図に示しておきます。
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ボタンを押すたびに送ったトピックが表示されているのが見えるはずです。
これもローカルホストでやり取りしているようにも見えますが、データはブラウザ->サーバー->ロボット->サーバー->ブラウザ、というふうに流れています。
では次にROS側でtalkerノードを立ち上げましょう。ROS内でやり取りされているデータがブラウザでSubscribeできていることがわかると思います。
rosrun rowma_ros talkerまとめ
以上Rowmaを使ったROSトピックの送受信をReactで行うチュートリアルでした。
ここで作ったWebフロントエンドはasmsuechan/rowma-react-tutorialに公開しています。
何か問題を発見した方はここにコメントしたり該当するGitHubリポジトリでissueを立てたりしてください。
- 投稿日:2020-06-01T15:56:16+09:00
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を使う人が山ほどいて、
同じことを考える人絶対いるんでしょ!?調べたらいました、めちゃくちゃいました、山ほどいました(※そこまではいないです)。
やったぜ。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> ) }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 bygraphql-hooks
と記載しています。
同じgraphql
からの発想で作ったみたいで嬉しいです。CodeSandboxと注釈は時間ができたら直します。
- 投稿日:2020-06-01T00:15:15+09:00
React hooksを基礎から理解する (useEffect編)
React hooksとは
React 16.8 で追加された新機能です。
クラスを書かなくても、state
などのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。
- React hooksを基礎から理解する (useState編)
- React hooksを基礎から理解する (useEffect編) 今ここ
- 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
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.jsimport 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.jsimport 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を更新したあとに起こすようにしたいので、
componentDidMount
とcomponentDidUpdate
に記載します。するとReact
がDOM
に変更を加えた後に、document.title
を更新しています。react.jscomponentDidMount(){ document.title =`${this.state.count}回クリックされました` } componentDidUpdate(){ document.title =`${this.state.count}回クリックされました` }関数コンポーネントで
useEffect
を使った場合デフォルトでは、
useEffect
は毎回のレンダー後に呼ばれます。react.jsconst [count, setCount] = useState(0); useEffect(() => { document.title =`${count}回クリックされました` })React公式サイトのstate とライフサイクルをもう一度読むと理解が進みました。
参考:React公式サイト state とライフサイクルuseEffectの第2引数に配列を渡して特定の値が変わったときだけ再レンダーさせる
useEffect()
の第2引数に[count]を渡すと、count
に変化があったときだけ再レンダーします。react.jsimport 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.jsuseEffect(() => { document.title =`${count}回クリックされました` console.log(`再レンダーされました`) },[count])
name
が更新されても再レンダーされず、count
が更新された場合だけ、document.title
が再レンダーされていることがわかります。第2引数に
[count]
を取らないとき
useEffect
はデフォルトで、副作用関数は初回のレンダー時および毎回の更新時に呼び出されます。react.jsuseEffect(() => { document.title =`${count}回クリックされました` console.log(`再レンダーされました`) })
count
が更新された場合だけだけでなく、name
が更新された場合にも、document.title
が再レンダーされていることがわかります。第2引数にからの配列
[]
を取ったときレンダリングは初回のみで再レンダーされないので、
document.title
は更新されません。react.jsuseEffect(() => { document.title =`${count}回クリックされました` console.log(`再レンダーされました`) },[])クリーンアップについて
クリーンアップとはイベントリスナの削除、タイマーのキャンセルなどのことです。
クリーンアップ関数
をreturnすると、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。クラスコンポーネントの場合
componentWillUnmountは、クリーンアップ(addEventLitenerの削除、タイマーのキャンセルなど)に使用されます。componentDidMountに副作用を追加し、componentWillUnmountで副作用を削除します。
react.jscomponentDidMount() { elm.addEventListener('click', () => {}) } componentWillUnmount() { elm.removeEventListener('click', () => {}) }関数コンポーネントの場合
上記に相当するhookは以下。「クリーンアップ関数」をreturnすることで、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。
react.jsuseEffect(() => { elm.addEventListener('click', () => {}) // returned function will be called on component unmount return () => { elm.addEventListener('click', () => {}) } }, [])最後に
次回は
useContext
について書きたいと思います。参考にさせていただいたサイト