Falcor+Reactフルスタック（views層とcomponents層）

[Meteor and Reactによるリアクティブシステム「ラーメン野郎を追いかけろ！ ＠ Twitter」を作ってみた](http://qiita.com/sand/items/02219ec5e42cc406ee5c "Meteor and Reactによるリアクティブシステム「ラーメン野郎を追いかけろ！ ＠ Twitter」を作ってみた")

[Falcor+Reactフルスタック（開発環境）](http://qiita.com/sand/items/06571033f99b55fffcb2 "Falcor+Reactのフルスタック（開発環境）")
[Falcor+Reactフルスタック（react-router）](http://qiita.com/sand/items/0a42ada13712abe87711 "Falcor+Reactのフルスタック（react-router）")
[Falcor+Reactフルスタック（views層とcomponents層）](http://qiita.com/sand/items/a969dd786b78554cf9fd "Falcor+Reactのフルスタック（views層とcomponents層）")
[Falcor+Reactフルスタック（formsy-react）](http://qiita.com/sand/items/4466b970f251445e13a4 "Falcor+Reactのフルスタック（formsy-react）")
[Falcor+Reactフルスタック（エラー処理）](http://qiita.com/sand/items/1ef8a19b15ac71c2fb99 "Falcor+Reactのフルスタック（エラー処理）")
[Falcor+Reactフルスタック（Material-UI）](http://qiita.com/sand/items/73d4aea4ae2697bfba2b "Falcor+Reactのフルスタック（Material-UI）")


　今回は、Reactのプログラム構成についてです。Reactはviewを作成するライブラリですが、一般的には複数のcomponentsから成り立ちます。ではどのようにしてcomponentsを作成して組み合わせていけばよいのでしょうか？

　大雑把に言えば、一般的には１つの画面を作成するのに２つのcomponentsを作成します。２つのcomponentはviews層とcomponents層に属し親子関係にあります。viewsのcomponentが親で、components層のcomponentが子になります。

　views層のcomponentは外部とのデータのやり取りやハンドラの定義を行い、データやハンドラをcomponents層のcomponentに渡します。components層のcomponentはより低レベルで具体的な画面を描画します。

　具体的にユーザ登録画面で説明しましょう。ユーザ登録画面はviews層としてsrc/views/RegisterView.jsを使い、components層としてsrc/components/RegisterForm.jsを使います。この２ファイルで定義された２つcomponentでユーザ登録画面が作られています。
http://www.mypress.jp:3019/

```js:src/views/RegisterView.js
import React from 'react';
import falcorModel from '../falcorModel.js';
import { RegisterForm } from '../components/RegisterForm.js';


export default class RegisterView extends React.Component {
  constructor(props) {
    super(props);
    this.register = this.register.bind(this);
    this.state = {
      error: null
    };
  }

  async register (newUserModel) {
    await falcorModel
      .call(['register'],[newUserModel])
      .then((result) => result);

    const newUserId = await falcorModel.getValue(['register', 'newUserId']);

    if (newUserId === 'INVALID') {
      const errorRes = await falcorModel.getValue('register.error');

      this.setState({error: errorRes});
      return;
    }

    this.props.history.push({pathname: '/login'}); 
  }

  render () {
    return (
      <div>
        <h1>登録画面</h1>
        <div style={{maxWidth: 450, margin: '0 auto'}}>
          <RegisterForm
            onSubmit={this.register} />
        </div>
      </div>
    );
  }
}
```

　このviews層ではregister()というハンドラを作成し、components層の子component(RegisterForm)にonSubmitプロパティで渡しています。このハンドラは非同期処理（Falcor通信）を含んでいるので、async/awaitが使われています。これが起動されるのはcomponents層ですが、動作はviews層で行われます（あくまで概念的にですが）。views層で外部との通信(Falcor通信)が行われています。ユーザ登録が成功すれば、react-routerの機能を使ってlogin画面にリダイレクトします。これは前回の説明通りです。

```
this.props.history.push({pathname: '/login'});
```


```js:src/components/RegisterForm.js
import React from 'react';
import Formsy from 'formsy-react';
import { RaisedButton, Paper } from 'material-ui';
import DefaultInput from './DefaultInput';

export class RegisterForm extends React.Component {
  constructor(props) {
    super(props);
    this.enableButton = this.enableButton.bind(this);
    this.disableButton = this.disableButton.bind(this);
    this.state = {
      canSubmit: false
    }; 
  }

  enableButton() {
    this.setState({
      canSubmit: true
    });
  }

  disableButton() {
    this.setState({
        canSubmit: false
    });
  }

  render() {
    return (
      <Formsy.Form onSubmit={this.props.onSubmit} onValid={this.enableButton} onInvalid={this.disableButton}>
        <Paper zDepth={1} style={{padding: 32}}>
          <h3>ユーザ登録フォーム</h3>
          <DefaultInput name='username' title='ユーザ名' value="" required />
          <DefaultInput name='firstName' title='名前' value="" required />
          <DefaultInput name='lastName' title='苗字' value="" required />
          <DefaultInput name='email' validations='isEmail' validationError='This is not a valid email' title='メール' value="" required />
          <DefaultInput type='password' name='password' title='パスワード' value="" required />
          <div style={{marginTop: 24}}>
            <RaisedButton
            disabled={!this.state.canSubmit}
            secondary={true}
            type="submit"
            style={{margin: '0 auto', display: 'block', width: 150}}
            label={'登録'} />
          </div>
        </Paper>
    </Formsy.Form>
    );
  }
}
```

　このcomponents層のcomponentは、JSXでユーザ登録画面を定義しているだけのシンプルなものです。通常components層のものはこのように画面の定義だけで、処理は親から受け取ったハンドラを起動するだけです。ここでの注意点としては、フォームの定義にFormsyライブラリを使っていることと、Input要素としてより低レベルのcomponents層のcomponentであるDefaultInputを使っていることです。このフォーム周りは次回以降に説明するつもりです。

　前回の説明でRouteの定義を示しましたが、それぞれのパスが１画面に相当します。Routeで指定されたcomponentがviews層のcomponentであると言えます。PageNotFoundは例外ですが。

```:Routeの定義
<Route exact path='/' component={HomeApp} name='home' />
<Route path='/login' component={LoginView} name='login' />
<Route path='/logout' component={LogoutView} name='logout' />
<Route path='/dashboard' component={DashboardView} name='dashboard' />
<Route path='/register' component={RegisterView} name='register' />
<Route component={PageNotFound}/>
```

　views層では、falcor通信の他に、Redux Storeとのやり取りやminimongoとのやり取り(Meteorの場合)などが行われます。components層では親からデータを受け取り単純に画面を描画します。