reactjs
React

React初心者のまとめ(基本編:3/3)

React初心者がツールを作りながら学習したことをまとめています。
前回の投稿が未読の方はそちらからお読みください。
React初心者のまとめ(基本編:1/3)
React初心者のまとめ(基本編:2/3)

フォームサンプル

ここまでの流れの総集編として、よくある入力フォームのサンプルをを示します。
このフォームは

  • 項目のリアルタイム入力チェック(入力されているかのチェックのみ)
  • すべての入力項目が入力されたときに送信ボタンがアクティブになる

の2つの機能を持っています。

2018.9.19 修正
FormInputTextクラスのinputにて、値の反映先をvalueではなくdefaultValueに変更しました。
(※valueの場合、入力チェックの負荷が高くなったときにうまく動作しなくなります。)

myForm.jsx
import React from 'react';
import { render } from 'react-dom';

// テキスト入力フィールドのコンポーネント
class FormInputText extends React.Component {

  // 未入力の場合はエラー表示
  renderErrorText() {
    return this.props.valid ? false : (
      <p className="invalid">項目が入力されていません。</p>
    )
  }

  render(){
    return (
      <div>
        <label>{this.props.label}</label>
        <input type="text" name={this.props.name} defaultValue={this.props.value} onChange={this.props.inputChk} />
        {this.renderErrorText()}
      </div>
    )
  }
}

// フォームのコンポーネント
class MyForm extends React.Component {

  constructor(props, context) {
    super(props, context)
    this.state = {
      field: {
        name: { name: 'name', label: 'お名前', value: null, valid: null },
        address : { name: 'address', label: '住所', value: null, valid: null }
      },
      errFlg : true,
    }
  }

  // 入力チェック(入力されていなかったらエラー)
  inputChk(e, fieldName) {
    const field = Object.assign({}, this.state.field);
    let value = e.target.value.trim();
    field[fieldName] = {
      value: value,
      valid: value ? true : false
    }
    this.setState({ field: field });
    // 単に this.setState({ field });でも可

    // 入力項目の状態全チェック
    this.allChk(field);
  }

  // 全項目の入力チェック
  allChk(fieldState) {
    let errFlg = false;
    let field = fieldState || this.state.field;

    Object.keys(field).forEach(_fieldName => {
      if (!field[_fieldName].valid) {
        errFlg = true;
      }
    });
    this.setState({ errFlg: errFlg });
    return !errFlg;
  }

  // 送信処理
  sendForm() {
    // 送信前に入力内容を再チェック
    if (!this.allChk()) {
      return false;
    }

    // 送信するデータをまとめる
    const sendData = Object.keys(this.state.field).map(_fieldName => {
      return {
        name: _fieldName,
        value: this.state.field[_fieldName].value,
      }
    });

    // 送信処理を行う関数
    this.props.sendForm(sendData);
  }

  render() {
    return (
      <div>
        <FormInputText {...this.state.field.name} inputChk={(e) => this.inputChk(e, 'name')} />
        <FormInputText {...this.state.field.address} inputChk={(e) => this.inputChk(e, 'address')} />
        <div>
          <button onClick={() => this.sendForm()} disabled={this.state.errFlg}>送信する</button>
        </div>
      </div>
    )
  }
}

render(<MyForm />, document.getElementById('root'));

解説

  • FormInputTextコンポーネント
    テキスト入力フォームを生成するコンポーネント。
    このコンポーネントが持つ機能はシンプルで、

    • propsから受け取ったnameとlabelの値を加えたinputタグを生成
    • エラー時にエラーテキストを出力
    • 入力値が変化したとき(onChange)にprops.inputChkを実行するよう記述

    のみになります。動的な処理はすべて親コンポーネントであるMyFormに処理させています。

  • MyFormコンポーネント
    フォーム全体の構成と項目毎の動的処理を行うコンポーネント。
    state.fieldでフォームを構成する情報を持つ。

全体の大まかな流れとしては
1. FormInputTextコンポーネント内のinputにテキスト入力->onChangeトリガー発火
2. onChangeをトリガーにして入力チェックメソッドinputChkを実行
3. 結果をMyFormコンポーネントのstate.fieldに反映
4. stateの更新を検出し再レンダリング、変更がFormInputTextコンポーネントのpropsへ引き継がれFormInputTextコンポーネントも再レンダリング

という感じになります。

ページ遷移(React Router v4)

ReactではstateをもとにDOMを書き換えるため、内容が書き換わっても常に同じURLです。
ただしブラウザでリロードした場合に毎回最初の状態に戻ってしまうため、必要に応じてURLを変更する必要があります。
ReactにはReact-routerというパッケージがあり、これを利用することでルーティングを実現できます。

React-routerは独立したパッケージになっているため、利用するにはnpmからインストールが必要です。

$ npm install --save react-router-dom

なおReactでのURL変更機能はReact内での記述のみでは実現できません。ページ配下のURLが変更しても常にReactを設置しているページを表示するように.htaccessやnginxなどで設定する必要があります。手順についてはそれぞれのWEBサーバーの解説を参考にしてください。

ということで、早速ルーターを利用したソースを示します。

基本型

browserRouter.jsx
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';

// 入力画面
class FormInput extends React.Component {
 …
}

// 確認画面
class FormConfirm extends React.Component {
 …
}

// 完了画面
class FormComplete extends React.Component {
 …
}

class RouterComponent extends React.Component {
    render(){
        return(
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={FormInput}/>
                    <Route path="/confirm" component={FormConfirm}/>
                    <Route path="/complete" component={FormComplete}/>
                </Switch>
            </BrowserRouter>
        )
    }
}

解説とポイント

  • <BrowserRouter />で囲うことで機能を実装
  • <Route />で「あるURL」(path)に対しての「表示するコンポーネント」(component)を決める
    Routeにexact属性を入れることでpathの値が完全一致したときのみ描画対象となるようになります。
    逆に言えば、exactを入れなければpathで入力した値はURLに前方一致していれば描画対象になります。

  • <Route />群は<Switch />で囲う
    Switchは無くても動作する場合はありますが、もしURLが複数のRouteにmatchしている場合でも、Switchで囲うことで配下の<Route>が1つだけ選択されるようになり、排他的に描画されるようになります。

もっと詳しく: https://reacttraining.com/react-router/web/api/BrowserRouter

hash型

/abc/#xxx という感じで#でのハッシュ型でURLを変化させるにはHashRouterを使います。

hashRouter.jsx
import React from 'react';
import {HashRouter, Route, Switch} from 'react-router-dom';

/* (省略) */

class RouterComponent extends React.Component {
    render(){
        return(
            <HashRouter hashType="noslash">
                <Switch>
                    <Route exact path="/" component={FormInput}/>
                    <Route path="/confirm" component={FormConfirm}/>
                    <Route path="/complete" component={FormComplete}/>
                </Switch>
            </BrowserRouter>
        )
    }
}

解説とポイント

  • BrowserRouter と書き方はほぼ一緒
  • hashTypeでURLの形式を指定(slash / noslash / hashbang)

    • "slash" - Creates hashes like #/ and #/sunshine/lollipops
    • "noslash" - Creates hashes like # and #sunshine/lollipops
    • "hashbang" - Creates “ajax crawlable” (deprecated by Google) hashes like #!/ and #!/sunshine/lollipops

    引用:https://reacttraining.com/react-router/web/api/HashRouter

もっと詳しく: https://reacttraining.com/react-router/web/api/HashRouter

Routeに値を持つコンポーネントを渡す

Routeにて、先の例で挙げたようなcomponent={FormInput}のかたちで表示するコンポーネントを書く場合、
<FormInput data={this.state.data} />のように値を持つコンポーネントを渡すことができません。
その場合はcomponentではなくrenderを使うことで指定できます。

<Route exact path="/" component={FormInput}/>
    ↓
<Route exact path="/" render={props => <FormInput data={this.state.data} />} />

もっと詳しく: https://reacttraining.com/react-router/web/api/Route

パラメータを含むURL

URLにパラメータを持たせたいとき、つまりコンポーネント内でそのパラメータを使いたいときは下記のように記述できます。

<Route path="/edit/:id" component={EditPage}/>

":id"のところに入る値がパラメータになります。idは変数名であり、自由に指定できます。
取得したパラメータは子コンポーネント内のprops.match.paramsに自動的に引き継がれます。変数名をidとしたときは
props.match.params.idに値が入る形になります。

class EditPage extends React.Component {
    render(){
        return(
            <p>ID:{props.match.params.id}</p> // URLから取得したIDを表示
        )
    }
}

リンク

サイト内で別ページへ遷移したいとき(URLを変えたいとき)、通常のアンカーリンクにあたるものがLinkコンポーネントになります。

<ul>
    <li><Link to="/">Home</Link></li>
    <li><Link to="/about">About</Link></li>
    <li><Link to="/company">Friends</Link></li>
</ul>

toでリンク先URLを指定します。

もっと詳しく: https://reacttraining.com/react-router/web/api/Link

リダイレクト

Reactの内部でページ遷移をしたいときの方法の1つとしてリダイレクトがあります。
リダイレクトはRedirectというコンポーネントで記述します。具体的なソースは以下の通り。

redirectSample1.jsx
<Route exact path="/" render={() => (
    loggedIn ? (
        <Redirect to="/dashboard"/>
    ) : (
        <PublicHomePage/>
    )
)}/>

この例ではloggedInの値でログイン状態を判定し、ログインしていたら/dashboardへリダイレクト、ログインしていなかったらPublicHomePageコンポーネントを表示、という形になります。

もっと詳しく: https://reacttraining.com/react-router/web/api/


今回は以上になります。
なおここまでの各章で解説した機能には実際はもっとたくさんのオプション機能がありますが、よく使うと思われる機能だけに絞って説明しています。もっと詳しく知りたい方は、各章の「もっと詳しく」のリンクから参照してください。

次回からはReduxの説明を行いたいと思います。

Redux入門(イントロダクション): https://qiita.com/tomi_shinwatec/items/edb769bdfc80bc6c0f28