Reactでcheckboxの状態をステートで管理する

React (Facebook): managed state of controlled checkboxesのパクリと自分の理解のためのメモ

index.htmlの準備

<!DOCTYPE html>
<html>
  <head>
  ¦ <meta charset="UTF-8" />
  ¦ <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.min.js"></script>
  ¦ <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.min.js"></script>
  ¦ <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
  </head>
  <body>
  ¦ <div id="content"></div>
    <script type="text/babel">
    // この中にReact書く
    </script>
  </body>
</html>

ReactクラスとState定義

2つあるinputのcheckboxがそれぞれチェックされているかどうかの状態を持つためのstateを準備。ついでにReactDOMレンダーも定義。

var CheckboxDemo = React.createClass({
  getInitialState: function () {
    return {
      data: [
        {id: 1, selected: false },
        {id: 2, selected: false }
      ]
    };
  },
  // この下にFormを書く
});
ReactDOM.render(
  <CheckboxDemo />,
  document.getElementById('content')
);

Form作成

Formの要素としてchecksを定義する。mapの中でCheckboxDemoオブジェクトにthisでアクセスできるようにbindしておく

render: function () {
  var checks = this.state.data.map(function (d) {
    return (
      <div>
        <input type="checkbox" checked={d.selected} onChange={this.__changeSelection.bind(this, d.id)} />
          {d.id}
          <br />
      </div>
    );
  }.bind(this));
  <form>
    {checks}
  </form>
}

まだ理解できてないところとして__changeSelection.bind(this, d.id)のthisはCheckboxDemoなのかdなのか

changeSelection定義

チェックボックスが押された時の__changeSelection関数を定義。チェックボックスが押されたらidを__changeSelectionを渡して呼び出して__changeSelectionの中でdataの中を全部チェックして、渡したidとdataのidが一致したらselectedの真偽値を変更してdataを丸ごと変更かける

__changeSelection: function (id) {
  var nextState = this.state.data.map(function (d) {
    return {
      id: d.id,
      selected: (d.id === id ? !d.selected: d.selected)
    };
  });

  console.log(nextState); // 確認用
  this.setState( {data: nextState });
},

全部チェックを実装

form内を修正

<form>
  <input type="checkbox" ref="globalSelector" onChange={this.__changeAllChecks } />CheckAll
  <br />
  {checks}
</form>

changeAllChecksの定義

globalSelectorのチェックボックスがチェックされているかどうかをgetDOMNodeでとってきてchecksに入れて、checksをdataの全てのselectedに反映する

__changeAllChecks: function () {
  var chekcs = this.refs.globalSelector.getDOMNode().checked;
  var nextState = this.state.data.map(function (d) {
    return {id: d.id, selected: chekcs };
  });

  console.log('change all checks -> ', nextState)
  this.setState( { data: nextState })
}

全体コード

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel">

    var CheckboxDemo = React.createClass({
      getInitialState: function () {
        return {
          data: [
            {id: 1, selected: false },
            {id: 2, selected: false }
          ]
        };
      },

      render: function () {
        var checks = this.state.data.map(function (d) {
          return (
            <div>
              <input type="checkbox" checked={d.selected} onChange={this.__changeSelection.bind(this, d.id)} />
                {d.id}
                <br />
            </div>
          );
        }.bind(this));
        return (
          <form>
            <input type="checkbox" ref="globalSelector" onChange={this.__changeAllChecks } />CheckAll
            <br />
            {checks}
          </form>
        )
      },

      __changeSelection: function (id) {
        var nextState = this.state.data.map(function (d) {
          return {
            id: d.id,
            selected: (d.id === id ? !d.selected: d.selected)
          };
        });

        console.log(nextState);
        this.setState( {data: nextState });
      },

      __changeAllChecks: function () {
        var chekcs = this.refs.globalSelector.getDOMNode().checked;
        var nextState = this.state.data.map(function (d) {
          return {id: d.id, selected: chekcs };
        });

        console.log('change all checks -> ', nextState)
        this.setState( { data: nextState })
      }

    });



    ReactDOM.render(
      <CheckboxDemo />,
      document.getElementById('content')
    );
    </script>
  </body>
</html>

ReactをGulpでbabelifyするとUnexpected tokenでこけたら

React書いてGulpでビルドしようとしたらUnexpected tokenエラーが出るのでググると babel-preset-react 入れないとダメだった。

こけた後に出来るだけ最小構成にしてReactの書き方がまずかったかgulpfile.jsの書き方がまずかったのか確認した内容の備忘録。

エラー内容

% gulp
[03:57:42] Using gulpfile ~/react-gulp/gulpfile.js
[03:57:42] Starting 'default'...
events.js:142
      throw er; // Unhandled 'error' event
      ^

SyntaxError: ~/react-gulp/src/app.js: Unexpected token (6:2)
  4 |
  5 | ReactDOM.render(
> 6 |   <Child />,
    |   ^
  7 |   document.getElementById('container')
  8 | );
  9 |
    at Parser.pp.raise (~/react-gulp/node_modules/babylon/lib/parser/location.js:22:13)
    at Parser.pp.unexpected (~/react-gulp/node_modules/babylon/lib/parser/util.js:91:8)
    at Parser.pp.parseExprAtom (~/react-gulp/node_modules/babylon/lib/parser/expression.js:510:12)
    at Parser.pp.parseExprSubscripts (~/react-gulp/node_modules/babylon/lib/parser/expression.js:265:19)
    at Parser.pp.parseMaybeUnary (~/react-gulp/node_modules/babylon/lib/parser/expression.js:245:19)
    at Parser.pp.parseExprOps (~/react-gulp/node_modules/babylon/lib/parser/expression.js:176:19)
    at Parser.pp.parseMaybeConditional (~/react-gulp/node_modules/babylon/lib/parser/expression.js:158:19)
    at Parser.pp.parseMaybeAssign (~/react-gulp/node_modules/babylon/lib/parser/expression.js:121:19)
    at Parser.pp.parseExprListItem (~/react-gulp/node_modules/babylon/lib/parser/expression.js:988:16)
    at Parser.pp.parseCallExpressionArguments (~/react-gulp/node_modules/babylon/lib/parser/expression.js:341:20)

プロジェクト構成

.
├── gulpfile.js
├── index.html
├── node_modules
├── package.json
└── src
    ├── app.js
    └── child.react.js

node_modules

入れたものリスト

"devDependencies": {
  "babelify": "^7.2.0",
  "browserify": "^12.0.1",
  "gulp": "^3.9.0",
  "vinyl-source-stream": "^1.1.0"
},
"dependencies": {
  "react": "^0.14.3",
  "react-dom": "^0.14.3"
}

gulpfile.js

var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');

gulp.task('default', function () {
  return browserify('./src/app.js')
    .transform(babelify)
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'));
});

index.html

<!DOCTYPE html>
<html>
  <body>
    <div id="container"></div>
    <script src="bundle.js"></script>
  </body>
</html>

src/app.js

var React = require('react');
var ReactDOM = require('react-dom');
var Child = require('./child.react.js');

ReactDOM.render(
  <Child />,
  document.getElementById('container')
);

src/child.react.js

var React = require('react');

var Child = React.createClass({
  render: function () {
    return (
      <div id="child">
        <h2>Child</h2>
      </div>
    );
  }
});

module.exports = Child;

解決策

これで gulp を実行すると冒頭のエラーが出るので冒頭に書いているように babel-preset-react をインストール(npm install --save-dev babel-preset-react)してgulpfile.jsを修正すればビルド成功する。

インストール後のdevDependencies(package.js)

"devDependencies": {
  "babel-preset-react": "^6.3.13",
  "babelify": "^7.2.0",
  "browserify": "^12.0.1",
  "gulp": "^3.9.0",
  "vinyl-source-stream": "^1.1.0"
}

修正後のgulpfile.js

var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');

gulp.task('default', function () {
  return browserify('./src/app.js')
    .transform(babelify,{presets: ["react"]})
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'));
});

ビルド結果

これでgulpを実行するとちゃんとbundle.jsができあがってブラウザで<h2>Child</h2>のレンダリング結果が表示される。

% gulp
[04:11:17] Using gulpfile ~/react-gulp/gulpfile.js
[04:11:17] Starting 'default'...
[04:11:20] Finished 'default' after 3.67 s

ビルド後の構成

.
├── bundle.js
├── gulpfile.js
├── index.html
├── node_modules
├── package.json
└── src
    ├── app.js
    └── child.react.js

参考

ステップバイステップReact.jsで作るTodoアプリ

復習のためReact.jsでTodoアプリのViewを書いてみました。

このTodoアプリを通して次のことを勉強出来ました。

– stateとpropsの使い方
– bindを使ってコールバック関数内のオブジェクト指定

まだわかっていないのがコンポーネント分割の単位なので、見通しが立ったらまたブログ書きます。

では、書いていきます。

## ファイル構造

“`
MyApp
├── index.html
└── react-0.13.3
├── README.md
├── build
│   ├── JSXTransformer.js
│   ├── react-with-addons.js
│   ├── react-with-addons.min.js
│   ├── react.js
│   └── react.min.js
“`

## HTML雛形作成
React.jsでReactコンポーネントを作る前にHTMLの雛形を作ります。

“`html:index.html








“`と“`“`の間に書く。

##必要なコンポーネント
コードを書き始める前に必要なコンポーネントを考えます。

今回は3つのReactコンポーネントを作ります。

– TodoApp ・・・アプリ全体
– TodoCreator ・・・Todoを新規で追加するため
– TodoList ・・・ Todoリストを描画するため

##TodoAppコンポーネントを描画

“タグの間の一番最後でrenderメソッドで描画するコンポーネントとロードするdivのidを指定します。

“`jsx
React.render(
,
document.getElementById(‘myApp’)
);
“`

##TodoAppコンポーネント

###renderメソッドを定義

“`jsx
var TodoApp = React.createClass({
render: function(){
return (


);
}
});
“`

TodoCreatorコンポーネントとTodoListコンポーネントを返すTodoAppコンポーネントを定義しています。

###Todoリストの初期値の定義

Todoリストの初期値をTodoAppクラス内で定義します。
テストの為に、sampleというキーと0(未完了/false)の値をセットしています。(後でこの中身を消して空のJSONオブジェクトとして定義します。)

“`jsx
getInitialState: function() {
return {
todos = [{item:”sample”, status:0}
}
},
“`

###TodoListコンポーネントの定義
Todoリストを表示するためのTodoListコンポーネントを定義します。

“`jsx
var TodoList = React.createClass({
render: function() {
return (

    {
    this.props.todos.map(function(todo, i){
    if (todo.status == 0) {return

  • {todo.item}
  • }
    else {return

  • {todo.item}
  • }
    })
    }

);
}
});
“`

次にTodoAppコンポーネントからgetInitialStateで定義したtodosのステートを渡すために““を次のように書き換えます。

“`
// // 変更前

“`

これでtodosの状態をTodoListコンポーネントに渡し、TodoList側でpropsを通じてtodosの中身を参照することになります。

###TodoCreatorコンポーネントの定義
Todoの内容を入力し、Addボタンを押してtodosリストに追加するためのコンポーネントを作ります。

“`jsx
var TodoCreator = React.createClass({

_onAdd: function(){
var newTodo = this.refs.inputText.getDOMNode().value;
this.props.onAdd(newTodo);
},

render: function(){
return (


);
}

});

“`

Addボタンが押されたら_onAddメソッドを呼び出します。次に_onAddメソッド内ではgetDOMNodeを使ってtextボックス内の値を取得して、newTodo変数に格納し、propsを通じてnewTodoを引数に親であるTodoAppコンポーネントのonAddメソッドを実行します。

####TodoAppのonAddメソッド
TodoAppコンポーネントにonAddメソッドを追加します。

“`jsx
onAdd: function(newTodo){
this.setState({
todos : this.state.todos.concat({item:newTodo, status:0})
});
},
“`

onAddメソッドの内部でsetStateを実行todosの状態を更新します。

こうすることで、todosのステートが更新され、TodoListの表示が更新されます。

##TodoCreatorコンポーネントの修正
Addボタンを押したときに、入力した内容がちゃんと消えるようにするために加筆します。

“`jsx
var TodoCreator = React.createClass({

getInitialState: function(){
return {
value: “”
}
},

_onAdd: function(){
var newTodo = this.refs.inputText.getDOMNode().value;
this.props.onAdd(newTodo);
this.setState({value: “”});
},

_onChange: function(e){
this.setState({
value: e.target.value
});
},

render: function(){
return (


);
}
});
“`

valueという変数をgetInitialStateで定義しておき、inputボックスの中身が変化したら_onChangeメソッドでvalue変数を更新します。Addボタンが押されたら、親コンポーネントのtodosに更新作業を行った後に、value変数の中身をNullに戻しています。

##完了Todoの更新
Todoの完了を示すチェックボックを表示し、完了したものは取り消し線で表示するようにTodoListコンポーネントを修正します。

###Todoの表示部分の変更
“`jsx
render: function() {
return (

    {
    this.props.todos.map(function(todo,i){
    if (todo.status == 0) {
    return (


  • {todo.item}
  • )
    } else {
    return

  • {todo.item}
  • }
    },this)
    }

);
}
“`

###Todoステータスの更新
チェックボックスがクリックされると、まずTodoListコンポーネントの_onDeleteが呼ばれ_onDelete内の“`this.props.onDelete“`で親であるTodoAppコンポーネントのonDeleteメソッドが実行されています

まず、TodoListコンポーネントの_onDeleteの定義から

“`jsx
_onDelete: function(i){
this.props.onDelete(i);
}
“`

次にTodoAppコンポーネントのonDeleteメソッドを定義します。

“`jsx
onDelete: function(i){
var targetTodo = this.state.todos[i];
targetTodo.status = 1;
this.setState({
todos: this.state.todos
});
},
“`

また、propsで渡すためにTodoAppコンポーネントからTodoListへonDeleteを渡すためにTodoListのロード部分を変更します。

“`jsx

“`

これでindex.htmlファイルをブラウザで表示すると下図のTodo画面が出来上がります。

## 全体コード

“`html








“`