20190909のReactに関する記事は3件です。

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;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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を実行します。

$ cd Hello360
$ npm start
http://localhost:8081/index.html
上記のURLにアクセスしてみましょう。ロードに時間がかかると思いますが、しばらくすると下記のような表示が出るかと思います。

Welcome to React 360からHello Worldに変えてみる。(index.js)

エディタを開きます。
index.jsを開くと下記が記載されているかと思います。

index.js
import 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);

ここで、15行目の
Welcome to React 360Hello 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

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

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 axios

unstatedコンテナに通信用メソッドを追加

 以前の記事で作ったunstatedコンテナcontainers/InvoiceContainer.jsに、API通信を実行するメソッドを追加します。

/containers/InvoiceContainer.js
import 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.js
class 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に戻りたくないです。。。

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