React Nativeの公式ドキュメントを和訳してみる 4 - State

注意
  • 当記事はReact Native 0.51の公式ドキュメントの独自和訳です。間違いなどあればご指摘ください。
  • バージョン0.51のドキュメントに準拠します。
  • 参照リンク先は基本的に元サイトです。リンク先サイトを和訳したらそのページに差し替えます。
  • 本家サイトではシミュレータがあり、コードを編集しながら実行でき便利です。
  • Facebook社に無断なので怒られたら消します。

元サイト
facebook.github.io

コンポーネントをコントロールするデータには2種類あります。propsとstateです。propsはその親コンポーネントによって設定され、そのコンポーネントが続く限り固定された値です。変更を伴うデータには、stateを使わなければなりません。

一般的には、stateはconstructorで初期化すべきです。stateを変更したい場合はsetStateを呼びましょう。

例えば、テキストをずっと点滅させつづけたいとしましょう。テキスト自体は点滅コンポーネントが作成された時に一度だけ設定されるため、テキストはpropです。”テキストが今表示されているか否か”は時間とともに変化しますので、stateで持っておくべきでしょう。

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class Blink extends Component {
  constructor(props) {
    super(props);
    this.state = {showText: true};

    // 一秒ごとにstateを切り替える
    setInterval(() => {
      this.setState(previousState => {
        return { showText: !previousState.showText };
      });
    }, 1000);
  }

  render() {
    let display = this.state.showText ? this.props.text : ' ';
    return (
      <Text>{display}</Text>
    );
  }
}

export default class BlinkApp extends Component {
  render() {
    return (
      <View>
        <Blink text='I love to blink' />
        <Blink text='Yes blinking is so great' />
        <Blink text='Why did they ever take this out of HTML' />
        <Blink text='Look at me look at me look at me' />
      </View>
    );
  }
}

// Create React Native Appつかってたらとばす
AppRegistry.registerComponent('AwesomeProject', () => BlinkApp);

実際のアプリでは、stateをタイマーで設定することはおそらくないでしょう。おそらくサーバから新しいデータが届いたり、ユーザーがデータを入力したりした際にstateを設定することでしょう。Reduxのようなstateコンテナーを使ってデータフローをコントロールすることもできます。その場合、直接setStateを呼び出すよりもReduxを使ってstateを変更することになるでしょう。

setStateが呼び出されると、BlinkAppはコンポーネントを再描画します。タイマー内でsetStateを呼び出すことで、時計の針が進むたびにコンポーネントが再描画されるようになります。

stateはReactと同じように動作しますので、stateの操作についての詳細はReact.Component APIを参照してください。ここまでくると、良い加減ずっと退屈なデフォルトの黒テキストにイライラしてきているんじゃないでしょうか。より美しくするために、styleについて学びましょう。

React Nativeの公式ドキュメントを和訳してみる 3 - Props

注意
  • 当記事はReact Native 0.51の公式ドキュメントの独自和訳です。間違いなどあればご指摘ください。
  • バージョン0.51のドキュメントに準拠します。
  • 参照リンク先は基本的に元サイトです。リンク先サイトを和訳したらそのページに差し替えます。
  • 本家サイトではシミュレータがあり、コードを編集しながら実行でき便利です。
  • Facebook社に無断なので怒られたら消します。

元サイト
facebook.github.io

おおよそのコンポーネントは作成時、別々のパラメーターでカスタマイズできます。この新規作成時のパラメーターをpropsと呼びます。

例えば、ReactNativeの基本コンポーネントの一つにImageがあります。画像を作成する際に、sourceと名付けたpropを使い表示する画像を制御できます。

import React, { Component } from 'react';
import { AppRegistry, Image } from 'react-native';

export default class Bananas extends Component {
  render() {
    let pic = {
      uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
    };
    return (
      <Image source={pic} style={{width: 193, height: 110}}/>
    );
  }
}

// Create React Native Appつかってたらこの行は飛ばす
AppRegistry.registerComponent('AwesomeProject', () => Bananas);

{pic}が中括弧に囲まれていることに注目してください。picという変数をJSXに埋め込むためにこうなっています。JSX内には中括弧を使いJavaScriptを記述をすることができます。

独自に作成したコンポーネントにもpropsを使うことができます。そのため、たった一つのコンポーネントをアプリ内の様々な異なる場所で使い、それぞれの場所でプロパティを微妙に変化させていくことができます。this.propsをrenderファンクション内で参照するだけです。例をあげましょう。

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class Greeting extends Component {
  render() {
    return (
      <Text>Hello {this.props.name}!</Text>
    );
  }
}

export default class LotsOfGreetings extends Component {
  render() {
    return (
      <View style={{alignItems: 'center'}}>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

// Create React Native Appつかってたらこの行は飛ばす
AppRegistry.registerComponent('AwesomeProject', () => LotsOfGreetings);

propとしてnameを使うことでGreetingコンポーネントをカスタマイズすることができます。よって、このコンポーネントを挨拶のたびに再利用できます。この例はImageのような構築済みコンポーネントのようにJSX内でGreetingコンポーネントを使用しています。この強みによって、ReactはCoolになっています - もし異なるUIベースのセットを一緒に使いたいと思い立ったとしても、新しいコンポーネントをまた一つ作れば良いだけなのです。

他にも新しいことが起こっていますね。Viewコンポーネントです。Viewは他のコンポーネントを格納し、スタイルやレイアウトを調整するのに便利です。

propsと基本コンポーネントであるText, Image, Viewコンポーネントを使って、多種多様な静的な画面を作ることができます。時間ごとにアプリを変化させる方法を学ぶために、Stateについて学びましょう。

React Nativeの公式ドキュメントを和訳してみる 2 - 基本を学ぶ(Learn the Basics)

注意
  • 当記事はReact Native 0.51の公式ドキュメントの独自和訳です。間違いなどあればご指摘ください。
  • バージョン0.51のドキュメントに準拠します。
  • 参照リンク先は基本的に元サイトです。リンク先サイトを和訳したらそのページに差し替えます。
  • 本家サイトではシミュレータがあり、コードを編集しながら実行でき便利です。
  • Facebook社に無断なので怒られたら消します。

元サイト
facebook.github.io


React NativeはReactのようですが、webコンポーネントの代わりにネイティブコンポーネントを用いブロックを組み立てていきます。よって、ReactNativeアプリの基本構造を理解するためには、JSX、 コンポーネント、stateやpropsといった幾つかのReactの基本概念を理解する必要があります。もしすでにReactを知っているのであれば、あとはネイティブコンポーネントのようなReactNative固有のものを追加で学ぶ必要があります。このチュートリアルはReact経験の有無に関わらず全ての方を対象としています。

さぁ、やっていきましょう。

Hello World

古代からの風習に習い、アプリ構築の最初は”Hello World”と言わせる以外にないでしょう。このように。

import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';

export default class HelloWorldApp extends Component {
  render() {
    return (
      <Text>Hello world!</Text>
    );
  }
}

// Create React Native App使ってたらこの行は飛ばす
AppRegistry.registerComponent('AwesomeProject', () => HelloWorldApp);

興味深いですか?(本家webサイト上の)シミュレータでは直接サンプルコードで遊ぶことができます。これをApp.js、index.iox.js、またはindex.android.jsのファイルに貼り付けることもできます。ローカルマシン上で実際のアプリを作成できるでしょう。

なにが起こっているの?

上のコードはJavaScriptらしくないと感じる部分もあるかもしれません。
慌てないでください。これが未来です。

まずはじめに、ES2015(ES6としても知られています)はJavaScriptの複数の改修をまとめたものであり、今では公式水準の一部となっています。しかし、未だに全てのブラウザでのサポートはされておらず、web開発の場においてもしばしば不採用となっています。ReactNativeはES2015サポートと共にリリースされました。したがって、これらの仕様に競合の心配はいりません。import, from, class, extends, そして()=> 記法といった上記の例内のものは全てES2015の特徴です。もしES2015に馴染みがなくとも、チュートリアル内にあるようなサンプルコードを読んで行くだけでその記法を選択するようになるでしょう。よければ、このページ(https://babeljs.io/learn-es2015/)にES2015の特徴の良い概要が載っているので見てみてください。
上記コードのHello world!も普段見ない書き方ですね。これはJSXという、JavaScript内にXMLを埋め込むための記法です。多くのフレームワークがそれぞれ特別なテンプレート言語を使い、マークアップ言語の中にコードを埋め込めるようにしています。Reactでは、その逆を行きます。JSXによってコード内にマークアップ言語を埋め込めるようになります。web上のHTMLのように見えますが、

といったwebで言うところのタグの代わりにReactコンポーネントを用います。今回のケースでは、は文字列を表示するための構築済みコンポーネントです。

コンポーネント

と言うことで、このコードはHelloWorldAppという新しいコンポーネントを定義します。ReactNativeアプリを構築する際には新しいコンポーネントを大量に作ることでしょう。スクリーン上で見る全てのものはコンポーネントの並びで成り立っています。コンポーネント一つ一つは非常にシンプルです。必須なのは、描画するJSXを返すためのrenderファンクションただ一つのみです。

このアプリはたいしたことできないね

良い着眼点です。もっとコンポーネントが面白いことをできるように、propsについて学びましょう。

React Nativeの公式ドキュメントを和訳してみる - はじめに(Getting Started)※iOSアプリ開発向け

注意
  • 当記事はReact Native 0.51の公式ドキュメントGetting Started · React Nativeの独自和訳です。間違いなどあればご指摘ください。
  • バージョン0.51のドキュメントに準拠します。
  • 参照リンク先は基本的に元サイトです。リンク先サイトを和訳したらそのページに差し替えます。
  • Facebook社に無断なので怒られたら消します。

facebook.github.io

はじめに

このページでは最初のReact Native アプリをインストールし構築するまでを解説します。もしすでにReact Vativeをインストール済みなら、次のチュートリアルへ進んでください。*1

 Quick Start
→ Building Projects with Native Code

プロジェクト内にネイティブコードを含む必要がある場合はこちらの説明に従ってください。例えば、すでにあるアプリとReact Nativeを統合する場合や、Create React Native Appに弾かれた場合*2はこのセクションが役立つでしょう。

開発環境のOS、またiOSAndroidのどちらから開発をスタートしたいかによって説明が少し変わってきます。iOSAndroidの両方を開発したい場合も、セットアップが微妙に異なるのでとりあえずどちらか一方を選んでください。((公式ドキュメントでは環境をタブ選択できますが、本記事では

を選択したものを対象とします。))

Development OS;
macOS
  Windows
  Linux

Target OS:
iOS
  Android

インストール依存環境

Node, Watchman, React Nativeのコマンドラインインターフェース, Xcodeが必要です。

アプリ開発のエディターは好きなものを使えますが、XcodeのインストールはReact NativeのiOSアプリ構築のための必須ツールを設定するために必要です。

Node、 Watchman

NodeとWatchmanはHomebrewを使ってインストールすればよいでしょう。Homebrewをインストール後、下のコマンドをターミナルで走らせてください。

brew install node
brew install watchman

Nodeがインストール済みの場合は、バージョンが4かそれより新しいものであることを確認してください。

WatchmanFacebook製のファイル変更監視ツールです。より良いパフォーマンスのために、インストールすることを強くおすすめします。*3

React Native CLI*4

Nodeをインストールするとnpmコマンドが使えるようになるので、npmを使ってReact Native コマンドラインインターフェースをインストールしましょう。

次のコマンドをターミナルで実行しましょう。

npm install -g react-native-cli

注: もし Cannot find module 'npmlog' のようなエラーが出た時は、次のコマンドで直接npmをインストールしてみてください。curl -0 -L https://npmjs.org/install.sh | sudo sh

Xcode

Xcodeのインストールには、App Store経由が一番簡単です。
Xcodeをインストールすると、IOS シミュレーターとその他iOS appの構築に必要なすべてのツールがインストールされます。

すでにXcodeをインストールしている場合は、バージョンが8以上か確認してください。

Xcodeコマンドラインツールも必要です。Xcodeを開き、”Preference..."をメニューから選択しましょう。"Locations"パネルへ行き、Command Line Toolsのドロップダウンで最新版を選択しツールをインストールしてください。

f:id:divrots:20171124213355p:plain
訳者環境で作成

新規アプリケーション作成

React Native コマンドラインインターフェース を使って、"AwesomeProject"という新しいReact Native プロジェクトを生成しましょう。

react-native init AwesomeProject

ReactNativeをすでにあるアプリに統合する場合や、Create React Native Appに弾かれた場合、または既存のReactNativeプロジェクトにiOSのサポートを追加しようとしている場合などはこのコマンドは必須ではありません。( Platform Specific Code参照)

React Native アプリケーションの起動

React Native プロジェクトフォルダでreact-native run-iosを実行してください。

cd AwesomeProject
react-native run-ios

間も無く、iOS シミュレーターで新しいアプリが起動されるのを確認できるでしょう。

f:id:divrots:20171124214344p:plain
引用元:http://facebook.github.io/react-native/releases/0.51/docs/getting-started.html
react-native run-ios
はアプリを起動する一つの方法でしかありません。XcodeNuclide*5から直接起動することも可能です。

もしうまく動かない場合はトラブルシューティングをごらんください。

バイス上での起動

上記コマンドは初期設定では自動的にiOS シミュレーター上でアプリを起動します。もしiOSの実機上で起動したいならば、Xcodeを使いましょう。*6

アプリの編集

さぁ、アプリの起動に成功しました。次は編集をしましょう。

  • App.js*7テキストエディタやその他お好きなエディタで開きます。
  • iOSシミュレーターで ⌘R を叩きアプリを更新し、変更を確認しましょう
完成!

おめでとう!初めてのReact Native アプリを起動~編集まで達成しました!

お次は?

  • 開発者メニューからLive Reloadをonにしましょう。変更の保存に応じてアプリが自動で更新されるようになります。
  • もしこの新しいReactNativeコードを既存のアプリケーションに組み込みたい場合は、インテグレーションガイドを参照してください

もっとReact Nativeを学びたいなら、引き続きチュートリアルへ進みましょう

*1:Quick StartではなくBuilding Projects with Native Code(ネイティブコードを含めたプロジェクト構築)を対象とします。

*2:ビルド設定なしでReact Nativeのアプリを立ち上げられるnpmモジュール。要はQuick Startがうまくいかなかった人向け。

*3:エディターの保存と同時にシミュレーターを自動アップデートする機能が使えるようになるのでデザイン時に重宝します。

*4:コマンドラインインターフェース

*5:Facebook社製のオープンソースの開発環境。javascriptデバッグに強いらしい

*6:Xcode起動->プロジェクト選択->左上のプロジェクト名>デバイス選択欄から有線でつないだiOSバイス選択->二つ左の三角。開発者登録してなくても実機検証できます。

*7:バージョン0.49からindex.ios.jsとindex.android.jsはindex.ios.jsに統合されました。

Cakephp2でController、ModelからViewにdebug出力

debug('hoge')をつかうだけで、body内の一番上に表示してくれる

出力例

<body>
  <div class="cake-debug-output">
    <span><strong>/app/Controller/SearchController.php</strong> (line <strong>109</strong>)</span>
    <pre class="cake-debug">'piyopiyo'
    </pre>
  </div>
~~

余談

ajaxとかシェルとかでrender()しないと無力なので$this->log()でファイルに書き出そう。

余談2

3だと内にぶちこまれてファビコンが消えた思い出

Cakephp2でHashクラスのマッチャーのたてかた(使用例もあるよ)

レファレンスを見ても最初なんのこっちゃピンとこなかった記憶があるので。

Cakephp2ではモデルからデータを読み込むと配列で返ってくる。
その配列をいい感じに整形したい時にHashクラスを使うとforeachのネストからおさらばできるかもしれない。
特にアソシエーションして引っ張ってくる時に有効です。

参考 
https://book.cakephp.org/2.0/ja/core-utility-libraries/hash.html

基本

式とマッチャーで同定する。
式がキー判定でマッチャーがバリュー判定。

{n} なんか数字のキー。0,1,2,3,...に当てはまる
{s} なんか文字のキー。'id','name','status',...に当てはまる
{*} 全部のキー
Foo 完全に同じ時。{n}.Userだと[0=>['User'=>[...], 1=>['User'=>[...]]が返る

マッチャー

[id] idのキーが存在してたら返す
[id=2] idが2だったら返す
[id!=2] idが2じゃなかったら返す
[id>2] idが2より大きかったら返す
[id>=2] idが2以上だったら返す
[id<2] idが2より小さかったら返す
[id<=2] idが2以下だったら返す
text=/.../ 正規表現...に引っかかったら返す

式はすべてのメソッドで使えるけれど、マッチャは使えるメソッドが限られているので注意

使えるもの

extract(), combine(), format(), check(), map(), reduce(), apply(), sort(), insert(), remove(), nest()

使用例

<?php
// 対象になる配列
$a = array(
    array(
        'User' => array(
            'id' => 2,
            'group_id' => 1,
            'Data' => array(
                'user' => 'mariano.iglesias',
                'name' => 'Mariano Iglesias'
            )
        )
    ),
    array(
        'User' => array(
            'id' => 14,
            'group_id' => 2,
            'Data' => array(
                'user' => 'phpnut',
                'name' => 'Larry E. Masters'
            )
        )
    ),
    array(
        'User' => array(
            'id' => 34,
            'group_id' => 2,
            'Data' => array(
                'user' => 'unijerry',
                'name' => 'David Pretter'
            )
        )
    ),
);

$result = Hash::extract($a, {n}.User);
// $aと同じ

$result = Hash::extract($a, {n}.User.id);
// [2, 14]

$result = Hash::extract($a, {n}.User[group_id=1]);
/**
 array(
            'id' => 2,
            'group_id' => 1,
            'Data' => array(
                'user' => 'mariano.iglesias',
                'name' => 'Mariano Iglesias'
            )
        )
**/

$result = Hash::extract($a, {n}.User[id>10].Data[user=phpnut].name);
// ['Larry E. Masters']

ポイント

  • マッチャは式に使う(要素の条件[マッチャ]でキー[式]の要素を絞り込む)
  • マッチャで絞り込んだ後に式を使って絞り込める。

おまけ findとconditionで書きかえてみる

<?php
// 前提条件 上の$aを再定義する
$this->User->bindModel(['hasOne'=>['Data'=>['conditions'=>['User.id = Data.user_id']]]]);

$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>['Users.id'=>[2,14,34]]
];

$a = $this->User->find('all', $options);

この$aが前述の例の配列になるとすると、例にあげた$resultをモデルから直接抽出する$optionsは次のようになる。

// $result = Hash::extract($a, {n}.User);
$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>['Users.id'=>[2,14,34]]
];

// $result = Hash::extract($a, {n}.User.id);
$options=>[
  fields=>['Users.id'],
  conditions=>['Users.id'=>[2,14,34]]
];

// $result = Hash::extract($a, {n}.User[group_id=1]);
$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>[
    'Users.id'=>[2,14,34],
    'Users.group_id'=>1
  ]
];

// $result = Hash::extract($a, {n}.User[id>10].Data[user=phpnut].name);
$options=>[
  fields=>['Data.name'],
  conditions=>[
    'Users.id'=>[2,14,34],
    'Users.id >'=>10,
    'Data.user'=>'phpnut'
  ]
];

余談2 conditions(SQLのWHERE句)とアソシエーション

hasOneとbelongsToのアソシエーションで発行されるSQLはLEFT JOINなので(デフォルトのon句が違うだけ)、conditions(=where句)でそのまま条件を指定しても問題ない.

hasManyだとSQL上ではJOINしないためconditionsに書くとカラムが見つからずエラーとなる。
※最初のテーブルでSELECTして得たidを元にアソシエーションテーブルでSQLを回すといった手順を踏む
そのためhasManyで条件指定する場合はhasManyしつつjoinやbelongsToを同時に行ったりして絞り込む必要があるが気が向いたら記事にします。

上記の例ではhasOneなので問題無し。

Cakephp2でページ毎のjsタグが散らばるので一括管理する

どのページにどのタグを埋めたか煩雑になるのでエレメントとヘルパーで管理する
ヘッダーでなくbodyの最後に埋めるタグがややこしくなったためテコ入れ。

Google Tag Manager使えよってコメントはなしで。


// View/Elements/body_tags.ctp でタグと読み込みページ設定

<?php
  // タグ毎に読み込むコントローラーとビューの指定
  // ['Controller'=>['action1', 'action2']];
  // のように配列で指定

  $yahoo = ['Goods'=>['index','detail'] ];
  $facebook = '*'; // 
  $user_trace = '*';
  $client_lp= ['Pages'=>['home'], 'Goods'=>['index', 'detail'], 'Contacts'=>['index']];
?>

<?php if( $this->Tags->is_able_tag($yahoo)): ?>
<!-- yahoo tag -->
  <script>
  ~~~~~
  </script>
<!--// yahoo tag -->
<?php endif ?> 

<?php if( $this->Tags->is_able_tag($facebook)): ?>
<!-- facebook tag -->
  <script>
  ~~~~~
  </script>
<!--// facebook tag -->
<?php endif ?> 

<?php if( $this->Tags->is_able_tag($user_trace)): ?>
<!-- user_trace tag -->
  <script>
  ~~~~~
  </script>
<!--// user_trace tag -->
<?php endif ?> 

// View/Helper/TagsHelper.php で判定の処理部分

<?php
App::uses('AppHelper', 'View/Helper');

class TagsHelper extends AppHelper {
    function is_able_tag($service){
      return $service==='*' || (isset($service[$this->name]) && isset(array_flip($service[$this->name])[mb_strtolower($this->action)]));
    }
}

// View/Layouts/default.ctp でエレメント読み込み

<?php

~~~~
$this->element('body_tags');
</body>

// Controller/AppController.php でヘルパー読み込み

<?php
class AppController extends Controller {
    public $helpers = ['Tags'];

余談 TagsHelperの解説

改行して訳し下してみました。

<?php
App::uses('AppHelper', 'View/Helper');

class TagsHelper extends AppHelper {
    function is_able_tag($service){
      return $service==='*'  // '*'だったらtrueで終了->常に読み込み
      || // '*'じゃなかったら 
      (
        isset($service[$this->name]) // 今のコントローラー名がキーになっていて
        &&  //かつ
        isset(
          array_flip($service[$this->name])  // その値の配列とkeyとvalueを入れ替えた時
          [strtolower(  // 念のため小文字にした
            $this->action  // アクション名が
          )] // keyになったものが  
        ) // 存在している
      );  //ならばtrue
    }}

isset(array_flip($array)[$value])
はin_arrayと同じく配列に値が含まれているか検索している。
こちらの方が早いので、valueに重複がない配列の際はおすすめ。

コントローラー名とアクション名については前回の記事をごらんあれ。