aknow2

自分の興味のある事を連々と(プログラミング、モバイルアプリ、プログラミング教育)

create-react-native-appを使ってReactNative+Typescriptの環境を一瞬で作る方法

create-react-native-appを使うとReactNative+Typescriptの環境も一瞬で出来るのでサクッと紹介します。

方法

まずはインストール

npm -g install typescript create-react-native-app

次に以下のコマンドを打てば、my-appフォルダ内にTypescriptでReactNativeな環境が出来上がります。
恐ろしく簡単!

create-react-native-app my-app --scripts-version=react-native-scripts-ts

次にExpoを使ってアプリを起動してみましょう。
まずはお手元のスマホにExpoをインストールしてください。

https://play.google.com/store/apps/details?id=host.exp.exponent&hl=ja

次にreact-nativeを起動します。

cd my-app
npm start

QRコードが生成されるのでExpoで読み込めばアプリが起動します。
注意!スマホQRコードを生成したPCは同じローカルネットワークに属する必要があります。

ブラウザやタブを閉じた時にindexedDBに何か保存したい。save on exit

Webアプリでブラウザやタブを閉じた時に、今表示している内容をIndexedDBに保存したい場合ってありますよね。 ブラウザやタブが閉じる時の発火するイベントbeforeunloadを使えば上手く行きそうですが、、、。例えばこんな風に、、、

window.addEventListener('beforeunload', () => {
      saveToIndexedDB({data});
});

結論

この方法では出来ません

理由

indexedDBの保存処理は非同期のためです。 保存が終わる前に落ちてしまい保存できないです。 逆に言うとbeforeunload内は同期処理しか上手く動かないという事。

回避策

localstorageを使う。

localStorageは同期処理で保存が出来る。なので、ウィンドウが閉じた時は一旦LocalStorageに保存してから次にアプリが起動した時にindexedDBに移動させるというやり方。

// 終了時の処理
window.addEventListener('beforeunload', () => {
      localStorage.setItem('unsavedData', {key: '123',  data: 'hogehoge'});
});
// 起動時の処理
const unsavedData = localStorage.getItem('unsavedData');
if (unsavedData) {
  saveToIndexedDb(unsavedData);
  localStorage.removeItem('unsavedData');
}

気持ち悪い方法ですが、とりあえずこれで回避は出来ます。

もう一つ試した方法はServiceWorkerですが、デスクトップでブラウザを閉じると保存されず断念しました。

Google Cloud Functionsでcorsを有効にする。

Cloud Functions特有なところも無く一般的なクロスドメインの話ですが、忘れないようにメモしときます。

環境

有効する方法

サーバーサイドでレスポンスのヘッダーに'Access-Control-Allow-Origin'と'Access-Control-Allow-Headers'の設定をします。

// 設定例
res.header('Access-Control-Allow-Origin', "*");
res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept");

以下、簡単なチュートリアルを載せます。

チュートリアル

CloudFunctionの説明は省きます。

サーバーサイドですがCloud Functionを作成すると生成されるサンプルコードを使用します。 生成されるコードは以下です。

exports.helloWorld = (req, res) => {
  let message = req.query.message || req.body.message || 'Hello World!';
  res.status(200).send(message);
};

requestのクエリかボディにmessageという内容があれば、オウム返しするというシンプルな物です。 まずはこのままデプロイします。

次にクライアントサイドの例です。 ブラウザからFetchAPIで呼び出します。 送る内容は{"message": "hello gcf"}というJSON形式で送信します。
JSONで送るのでHeaderのContent-Typeをapplication/jsonにしています。
また、corsで送信するのでmode: 'cors'に設定します。 上手く実行されればChromeコンソール上に「hello gcf」と表示されるはず。

const headers = new Headers({
    "Content-Type": "application/json"
});
const body = JSON.stringify({message: 'gcf'});
const response = await fetch(
                                               'https://cloudfunctions/helloworld',
                                               {method: 'POST', mode:'cors', headers, body}
                                              );
const message = await response.text();
console.log(message);

この状態でクライアントサイドを実行して、Chromeの開発者ツールのコンソールを覗くと、以下のようなcorsのエラーが発生していると思います。 f:id:aknow2:20180911161455j:plain

サーバー側でAccess-Control-Allow-Originを付けろ!というエラーなので、サーバーサイドの修正を行います。エラーに出ている通りAccess-Control-Allow-Originを追加します。

exports.helloWorld = (req, res) => {
  let message = req.query.message || req.body.message || 'Hello World!';
  res.header('Access-Control-Allow-Origin', "*"); //追加した
  res.status(200).send(message);
};

この状態でデプロイ後、再びクライアント側を実行すると違うエラーが出てきます。
f:id:aknow2:20180911163651j:plain
次のエラーはサーバー側でAccess-Control-Allow-Headersを付けろ!と言う内容なので、またサーバーサイドを編集します。

exports.helloWorld = (req, res) => {
  let message = req.query.message || req.body.message || 'Hello World!';
  res.header('Access-Control-Allow-Origin', "*"); 
  res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept");//さらに追加した
  res.status(200).send(message);
};

編集が終わったら再デプロイします。
この状態で実行するとChromeのコンソールにhello gcfと表示されるはずです!

【ASO・Android】自分のアプリがPlayストアどんな検索ワードで調べられているか知る方法

ASOが重要な事は言わずもがなですが、自分のPlayStoreのページにユーザがどの様に検索して辿り着いたか気になるところです。 実はplay consoleで自分のアプリのページがどんな検索キーワードでたどり着いたを知る事が出来ます。

どの様に検索されているか知ることで、アプリの紹介文やタイトルを見直す良い判断材料となるのではないでしょうか。

以下にその確認方法を紹介します。

【確認方法】

1.左部ドロワーの獲得レポートを開く
f:id:aknow2:20180910163652p:plain

2.獲得レポート下部のユーザー獲得チャネルの表にある「Playストア」→「検索」を開く
f:id:aknow2:20180910164146p:plain

3.開いたページの下部に検索されたキーワードの一覧が表示されています! f:id:aknow2:20180910164538p:plain

「デバッグ編」nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせて、デバッグ。

nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。 - aknow2
上記リンク先の記事の続きです。 先にこちらを御覧ください

前回の方法でTypescriptを編集した際に自動リロードする事が出来ました。
次にデバッグする方法です。

1.準備

chromeがあればOKです。

2.nodemonの設定変更

nodemonのexecの設定を以下の様に変更します。

// 前回の設定
{
    "watch": ["src"],   // 監視対象とするフォルダを指定
    "ext": "ts",    // 拡張子.tsを指定する
    "exec": "ts-node ./src/index.ts"  // nodemonを起動したら実行されるスクリプト
}

// 新しい設定
{
    "watch": ["src"], 
    "ext": "ts",  
    // 次を変更!
    "exec": "node --inspect -r ts-node/register ./src/index.ts"  
}

3.デバッグする

実行できたら、chromeを起動して chrome://inspect/#devicesにアクセスすれば
デバッグ出来るようになります!

これでTypescriptで楽に開発出来る環境が出来ました!

[PR]

play.google.com

React Router(v4)でcomponentクラス外から手動でページ遷移を行う。

まずReact Router v4 基本的なページ遷移の方法からです。
やり方は以下で出来ます。

// タグで指定する
class Hoge extends React.component {
  render (): JSX.Element {
    return <Link to='/my/link'>Link</Link>;
  }
}

// historyを利用する
class Hoge2 extends React.component {
   
   handleClick () {
        this.props.history.push('/my/link');
   }

   render (): JSX.Element {
    return <button onClick={this.handleClick}> Link </button>
  }
}

ただ、これらは<Router>の配下のComponent、又はwithRouter利用したComponent内でしか利用出来ません。
Componentの外からページ遷移をしたい場合、どの様にすれば良いか一例を紹介したいと思います。
(例はTypescriptで書いていますが、Javascriptでも殆ど変わらないと思います。

先ほどの例で this.props.history.push('my/link') とありましたが、
実際にページ遷移を実行しているのはhistoryオブジェクトです。
このhistoryオブジェクトをどうにかして、何処からでもアクセス出来るようにする
というのが今回とった方法です。

何処からでもアクセスさせるには
createBrowserHistoryという関数を利用して
historyオブジェクトを自前で生成しexportします。
これで何処からでもhistoryオブジェクトが参照出来るようになります。
今回はhistory.tsというファイルを作成して実装してます。

//history.ts
import createHistory from 'history/createBrowserHistory';
const History = createHistory();
export default History;

次にhistoryをReact Routerに設定します。
設定するには, <Router>のプロパティであるhistoryに指定すると出来ます。
下記はindex.tsがこのReactAppのルートファイルである場合の例です。

// index.ts
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import { Router, Route } from 'react-router-dom';
import history from './history'; // <- 先ほど作ったhistoryをimport

ReactDOM.render(
        <Router history={history}> // <-ここがポイント
          <Route path={'/'} component={App} />
        </Router>
  document.getElementById('root') as HTMLElement
);

下準備はこれでOKです。
ページ遷移を実行したい箇所でhistoryをインポートして pushメソッドを呼び出せばページ遷移が出来ます。

import history from './history.ts' 
class Linker {

    linkToMyLink() {
        history.push('/my/link');
    }

}

ただ、これをすると何処からでもページ遷移が出来てしまうので
設計時にどのレイヤーでページ遷移をすべきかちゃんと考えたいですね。 以上です。

【ホットリロード】nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。

TypeScript+node.jsでサーバーサイドを開発してる時に、煩わしいこと

  1. TypeScriptをjavascriptコンパイルすること。
  2. ソースコードを編集したい場合、node.jsのプロセスを立ち上げなおす必要があること。

この二つの問題を問題を解決していきたいと思います。

追記: ボイラープレート作りました。設定面倒な人はこっちで試してみて下さい。
Node.js向けのTypescriptのボイラープレートを作った - aknow2

問題1 Typescirptからjavascriptへ変換

ts-nodeを利用。
ts-nodeとは、TypeScriptをnode上で実行してくれるREPLです。
そして、使い方も簡単です。

インストール方法
npm install -g ts-node
実行方法
ts-node ./src/index.ts  // 今回はsrcフォルダ下にあるindex.tsファイルを起動すると仮定

ts-nodeを利用するだけで、javascriptに変換する煩わしさが消えました。素晴らしい!
次!

問題2 ソースコード編集後のnode.js再起動

nodemonを利用する!
nodemonを使うとコード編集を自動で検知し、node.jsのプロセスも立ち上げ直してくれます。

インストール方法
npm install -g nodemon

これでターミナルでnodemon hoge.jsと実行すれば良いだけなのですが、、、
デフォルトの設定ではJavascriptのファイル(.js)しか検知してくれません。
TypeScriptの場合は少し手を入れる必要があります。

プロジェクトフォルダ直下にnodemon.jsonを作成し、
以下のように記述します。

{
    "watch": ["src"],   // 監視対象とするフォルダを指定
    "ext": "ts",    // 拡張子.tsを指定する
    "exec": "ts-node ./src/index.ts"  // nodemonを起動したら実行されるスクリプト
}

あとはターミナルでnodemonを実行するだけ

nodemon

これでTypeScriptからJavascriptへの変換、
node.jsの再起動の二つの煩わしさから解放されます!

お次はデバッグする方法です!
次の記事をご覧ください。

「デバッグ編」nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせて、デバッグ。 - aknow2

参考

ts-node https://github.com/TypeStrong/ts-node

nodemon
https://github.com/remy/nodemon#config-files https://github.com/remy/nodemon/blob/master/doc/sample-nodemon.md

[PR]

https://play.google.com/store/apps/developer?id=Aknow2Gamesplay.google.com