- 投稿日:2019-09-09T22:22:02+09:00
React.memoしたコンポーネントのdisplayNameを取得する
Reactのデバッグ用に、コンポーネントの名前を出力していたのですが、その過程で
React.memo
したコンポーネントは特殊な扱いが必要でした。
displayName
とは以前にべからず集でも触れましたが、コンポーネントに
displayName
がセットしてあると、それがデバッグ時にコンポーネント名として表示されます。そして、React公式サイトにあるコード片にも
WrappedComponent.displayName || WrappedComponent.name
のようなコードがあるように、関数宣言やクラスなどでname
が設定されていれば、それで代用できます。
React.memo
を使った場合ところが、
React.memo
を使った場合、displayName
はセットされません(もちろん関数生成ではないので、自動的にname
が付くこともありません)。なので、displayName || name
のコードでは何も取れません。ただ、Reactのデバッグツールでは
Memo(WrappedComponent)
のような名前がしっかりと出ています。このような名前を取得できないか調べてみました。
react-is
とはそして、調べてみると、
React.memo
で生成したコンポーネントにはtype
というプロパティがあって、ここにもとのコンポーネントが来ることが判明しました。あとはメモ化コンポーネントを識別できれば、要件は片付きます。もちろん内部データにアクセスして調べられなくもないのかもしれませんが、それをやっていると将来的に内部構造が変化したときに死にます。そこでReactチームが公式に用意している手法として、
react-is
というライブラリがあります(GitHub)。以下のようなメソッド・定数が用意されています。
ReactIs.isValidElementType(arg)
…arg
がReactコンポーネントにできるもの(タグ名の文字列・関数コンポーネント・クラスコンポーネントなど)かを判定するReactIs.typeOf(arg)
…arg
の種類を、以下の定数のどれかで返す
ReactIs.ConcurrentMode
ReactIs.ContextConsumer
ReactIs.ContextProvider
ReactIs.Element
ReactIs.ForwardRef
ReactIs.Fragment
ReactIs.Memo
ReactIs.Lazy
ReactIs.Portal
ReactIs.Profiler
ReactIs.StrictMode
ReactIs.Suspense
ReactIs.is***
(上の定数に対応したメソッドがあります)…それぞれの種類かを判定するなお、どういうわけか
import ReactIs from 'react-is'
の形では読み込めず、import {isMemo} from 'react-is'
と単品で呼ぶか、全部読み込む場合はimport * as ReactIs from 'react-is'
とする必要があります。実際に書いてみた
素材が揃ったので、あとはコードに起こすだけです。
import {isMemo} from 'react-is'; function getDisplayName(component) { const {name, displayName} = component; // displayNameがついていればそれを採用 if(displayName) return displayName; // メモ化コンポーネントの場合 if(isMemo(component)) return `Memo(${getDisplayName(component.type)})`; // あとはnameなどをチェック return name || null; }
- 投稿日:2019-09-09T21:01:41+09:00
【Mac】ReactでVR!? React 360を使用して、パノラマ画像を表示させる手順方法。
はじめに
セブ島もくもく会の中で、初学者を対象にしたVR開発入門講義を行いました。
環境構築から、パノラマ画像を表示させるまでの手順をこちらに残しておきます。やること
タイトル通りです。
ただし、各用語の解説はいたしません。手順のみです。
ご了承ください。React 360って?
Facebook社製のVR専用アプリケーションフレームワークです。
実はReactの書き方で、VRアプリも開発できます!
https://facebook.github.io/react-360/
開発環境(筆者の環境です。)
- macOS Mojave 10.14.5
- Node.js 12.6
- npm 6.9
Node.jsのインストール方法はこちらから。
https://qiita.com/AwesomeArsAcademia/items/4f685e2f46bab122f6cf必要なツールはこちらから。
開発環境を整える
react-360-cli
をインストールhttps://facebook.github.io/react-360/docs/setup.html
公式ドキュメントを参考に、開発環境を構築していきます。npmを使って、
react-360-cli
をインストールします。$ npm install -g react-360-cliアプリを立ち上げる
下記のコマンドを打つとフォルダが作成されます。
$ react-360 init Hello360 Creating new React 360 project... Project directory created at Hello360 ~省略~ success Saved lockfile. ✨ Done in 21.60s. Done! Now enter the new project directory by running `cd Hello360` Run `npm start` to initialize the development server From there, browse to http://localhost:8081/index.html Open `index.js` to begin editing your app. $ ls Hello360上記の指示通り、Hello360に移動して
npm start
を実行します。http://localhost:8081/index.html$ cd Hello360 $ npm start
上記のURLにアクセスしてみましょう。ロードに時間がかかると思いますが、しばらくすると下記のような表示が出るかと思います。
Welcome to React 360
からHello World
に変えてみる。(index.js)エディタを開きます。
index.js
を開くと下記が記載されているかと思います。ここで、15行目のindex.jsimport React from 'react'; import { AppRegistry, StyleSheet, Text, View, } from 'react-360'; export default class Hello360 extends React.Component { render() { return ( <View style={styles.panel}> <View style={styles.greetingBox}> <Text style={styles.greeting}> Welcome to React 360 </Text> </View> </View> ); } }; const styles = StyleSheet.create({ panel: { // Fill the entire surface width: 1000, height: 600, backgroundColor: 'rgba(255, 255, 255, 0.4)', justifyContent: 'center', alignItems: 'center', }, greetingBox: { padding: 20, backgroundColor: '#000000', borderColor: '#639dda', borderWidth: 2, }, greeting: { fontSize: 30, }, }); AppRegistry.registerComponent('Hello360', () => Hello360);
Welcome to React 360
をHello World
に書き換えます。
ファイルを保存して、ブラウザを更新しましょう。
下記のように表示されれば成功です!
パノラマ画像を表示させる。
パノラマ画像のフリー素材ですが、僕は下記のURLからダウンロードしました。
http://panoroman.nao3.net/ダウンロードしたら、
static_assets
のフォルダの配下に移動します。
名前も変更しましょう。
※今回はp1.jpg
で進めていきます。背景の画像を変更する際は
client.js
のファイルを編集します。client.js// This file contains the boilerplate to execute your React app. // If you want to modify your application's content, start in "index.js" import {ReactInstance} from 'react-360-web'; function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, { // Add custom options here fullScreen: true, ...options, }); // Render your app content to the default cylinder surface r360.renderToSurface( r360.createRoot('Hello360', { /* initial props */ }), r360.getDefaultSurface() ); // Load the initial environment r360.compositor.setBackground(r360.getAssetURL('360_world.jpg')); } window.React360 = {init};20行目にある
こちらのr360.compositor.setBackground(r360.getAssetURL('360_world.jpg'));360_world.jpg
を先ほどダウンロードしたファイル名に書き換えます。
今回はp1.jpg
に変更します。
保存して、ブラウザを更新しましょう。
下記の表示になれば成功です!
マウスでグリグリ動かしてみましょう。
最後に
解説が欲しい方は下記の記事がおすすめです。
https://qiita.com/shiruco/items/3e77babe80a373c71fd5
https://qiita.com/bayarea-techblog/items/46531e0a64ffa1c0d181
- 投稿日:2019-09-09T06:32:01+09:00
React Native + Expo アプリで unstatedのStateにaxiosを使って簡易テストサーバーからデータ取得
この記事は、「【連載】初めてのReact Native + Expo開発環境構築入門」の子記事です。環境などの条件は、親記事をご覧ください。
前回までに、unstatedに格納した請求書情報をきれいに画面上にリスト表示できるようになったので、今回は請求書情報をサーバーから取得できるようにします。テスト用に簡易ローカルHTTPサーバーも立ち上げます。
目標
サーバーとの通信は、Reduxよりunstatedが楽
Reduxを使った場合、そもそもそのままではサーバーからデータを取得してグローバルStateに取得データを格納できません。(厳密にはできますが、各コンポーネント内に非同期処理を書かなければいけないので、コンポーネント間でコード再利用ができない。)なので通常redux-thunkというミドルウェアを使って
Action
で非同期処理を実行できるようにします。これに比べて、unstatedなら2つの点で断然有利です。
- ミドルウェア無しで(追加モジュール無しで)非同期処理できる
- Containerのメンバーメソッドとして非同期処理を書ける。ActionやReducerなどあちこちに処理を書かなくていい
※もちろんReduxのほうがいい場合もあります。
テストサーバーを立てる
実際のコーディングの前に、まずはテストサーバーを立てましょう。ローカルPCにnode.jsのサーバーを立てて、そこにinvoice.jsというファイルを置いて、HTTP通信で取得できるようにします。今回はnode.jsのサーバーを簡易的に立ててくれるhttp-serverを使います。
まず
http-server
をグローバルインストール。npm install -g http-server次に、テストサーバーのドキュメントルートフォルダを設置。プロジェクトフォルダの横とかがわかりやすいかもですね。
たとえば今回のプロジェクトは私の場合C:\ExpoProjects\hello-world-test
に設置しているので、C:\ExpoProjects\hello-world-testdata
というフォルダを作って、以下のinovice.js
を設置します。invoice.js{"customers":[{"id":0,"name":"ABC Store","addr1":"123 Abc St.","addr2":"","city":"New York","state":"NY","zip":"10001"},{"id":1,"name":"123 Deli","addr1":"1 Def Ave.","addr2":"","city":"New York","state":"NY","zip":"10002"},{"id":2,"name":"Xyz mart","addr1":"23 Xyz Blvd.","addr2":"","city":"New York","state":"NY","zip":"10003"},{"id":3,"name":"Xyz2 mart","addr1":"1 Xyz Blvd.","addr2":"","city":"New York","state":"NY","zip":"10004"}],"products":[{"id":0,"name":"Blue ribbon","shortName":"B.R.","price":10.5,"cost":7.2},{"id":1,"name":"Red ribbon","shortName":"R.R.","price":9.5,"cost":6},{"id":3,"name":"White shirt","shortName":"W.S.","price":15,"cost":9.3}],"invoices":[{"id":0,"date":"2/2/2019","customer":0,"items":[{"product":0,"qty":5,"adjust":0,"credit":0},{"product":1,"qty":3,"adjust":0,"credit":0},{"product":2,"qty":4,"adjust":0,"credit":0}]},{"id":1,"date":"2/2/2019","customer":0,"items":[{"product":0,"qty":7,"adjust":0,"credit":0}]},{"id":2,"date":"2/2/2019","customer":3,"items":[{"product":0,"qty":5,"adjust":0,"credit":0},{"product":1,"qty":3,"adjust":0,"credit":0},{"product":2,"qty":4,"adjust":0,"credit":0}]},{"id":3,"date":"2/2/2019","customer":2,"items":[{"product":0,"qty":5,"adjust":0,"credit":0},{"product":1,"qty":3,"adjust":0,"credit":0},{"product":2,"qty":4,"adjust":0,"credit":0}]}]}長いのでコンパクト化していますが、中身が気になる方は、オンラインJSONエディタに貼り付けてみてください。このデータにはInvoiceが4つ入っています。
ではローカルサーバーを立てます。PowerShellを開き(Visual Studio Codeの中ではなく、独立したPowerShellを立ち上げます)、ドキュメントルートまで移動して
http-server
を実行。cd \ExpoProjects\hello-world-testdata http-server上のようにどこに立ち上がってるか表示してくれるので、実験に使ってるモバイル実機のブラウザでアクセスしてみます。このとき、ポート番号とファイル名を忘れないように注意します。
axiosをインストール
axiosはHTTP通信を「よしなに」処理してくれる便利屋さんです。なくてもいいですが、記述が簡単になるのでぜひ。
npm install axiosunstatedコンテナに通信用メソッドを追加
以前の記事で作ったunstatedコンテナ
containers/InvoiceContainer.js
に、API通信を実行するメソッドを追加します。/containers/InvoiceContainer.jsimport axios from 'axios'; const INVOICE_API_ENDPOINT = 'http://192.168.1.8:8080/invoice.js'; ... export default class InvoiceContainer extends Container { constructor(props = {}) { super(); this.state = { data: props.initialSeeding ? Seeder.getSeed() : this.getEmptyData(), isDataLoading: false }; } ... getDataFromServer() { this.setState({ isDataLoading: true }); axios .get(INVOICE_API_ENDPOINT, { params: {} }) .then(results => { console.log("HTTP Request succeeded."); console.log(results); this.setState({ data: results.data }); this.setState({ isDataLoading: false }); }) .catch(() => { console.log("HTTP Request failed."); this.setState({ isDataLoading: false }); }); } ... }
INVOICE_API_ENDPOINT
には、簡易テストサーバーのアドレスに対象データファイル名まで入れたURLを指定します。グローバルStateとして、
isDataLoading
を追加しました。axiosがサーバーと通信中にこれをオンにすることで、画面コンポーネント側でこのフラグを見て表示内容を変更できることを狙っています。追加した
getDataFromServer()
では、axiosによるサーバーとの通信が実装されています。最初にロード中フラグisDataLoading
を立てて、INVOICE_API_ENDPOINT
と通信し、成功(.then
)したらグローバルStateのdata
に書き込んでからロード中フラグオフ、失敗(.catch
)したら単にロード中フラグオフ。これだけで通信コーディングは終わりです。実践ではエラーになった場合の処理についてもう少し書く必要がありますね。
API通信を開始するボタンを作る
単純に、画面コンポーネントに
Import
ボタンとイベントを実装します。HomeScreen.jsclass HomeScreenContent extends React.Component { constructor(props) { super(props); this.onImportClick = this.onImportClick.bind(this); } onImportClick() { this.props.globalState.getDataFromServer(); } ... render() { let globalState = this.props.globalState; let invoiceList = <Text>No invoice</Text>; // Wait for data loading... if (globalState.state.isDataLoading) { return ( <View> <Text>loading...</Text> </View> ); } ... return ( <Container> <Content> <View style={{ flexDirection: "row" }}> <Left> <Button light style={{ justifyContent: "flex-start" }} onPress={() => this.onImportClick()}> <Icon type="FontAwesome5" name="file-import" /> <Text style={{ paddingLeft: 0 }}>Import</Text> </Button> </Left> <Right> <Button style={{ justifyContent: "flex-end" }} onPress={() => this.props.navigation.navigate("Summary")}> <Icon type="FontAwesome5" name="poll-h" /> <Text style={{ paddingLeft: 0 }}>Summary</Text> </Button> </Right> </View> {invoiceList} <Button style={{ justifyContent: "flex-start" }} onPress={() => this.props.navigation.navigate("InvoiceEdit")}> <Icon type="FontAwesome5" name="file-invoice-dollar" /> <Text style={{ paddingLeft: 0 }}>InvoiceEdit</Text> </Button> </Content> </Container> ); } } ...まずイベントハンドラ
onImportClick()
を登録します。この中では、グローバルStateコンテナに書いたgetDataFromServer
を呼んでるだけです。
次に、render()
内に、通信中(グローバルStateのisDataLoadingがオン)だったら「loading...」と表示するコードを入れます。これにより、通信中にほかの操作をされることを防ぎます。
最後に、Importボタンを追加します。タップされたらthis.onImportClick()
を呼ぶだけです。
※さらに若干ボタンの配置を変えています。修正が終わったら、テスト用サーバーがPowerShell上で起動していることを確認して、モバイル実機でテストします。結果は、以下のようになります。
初期画面
Importボタンを押した後
いかがでしょうか。Redux+Thunk+axiosを使ったことがある方は、あまりの簡単さに驚きが隠せないのでは?私はよっぽど大きなプロジェクトでない限り、もうReduxに戻りたくないです。。。