では ReactJS 第2回目いってみましょう。
今回は下図のような感じが完成図です。

単純にToDoが一覧されており、ToDoの横には削除ボタンがあり。
ToDoを追加したい場合は上部のテキストボックスに文字を入力し
追加ボタンを押す。
たったこれだけです。
まぁこのたったこれだけが結構難しいんですけどね。
ではいってみましょう。
まずは初期値となる変数をこんな感じで用意します。
[bash]
var DATAS = [
{id:1, content:”Softbank解約”},
{id:2, content:”R-SIM10購入”},
{id:3, content:”圏外病を克服するSIMフリールーターゲット”},
{id:4, content:”楽天モバイル申し込み”}
]
[/bash]
この変数がToDoリストの初期値です。
では次、画面イメージから、何個の独自クラスを定義しなきゃいけないか考えましょう。
簡単に言ってますが、これかなり重要なポイントです。
要はどんなが完成図があるから、どういうパーツを組み合わせて画面を作ろうか??
と考えるのです。そのパーツが classであり、その classが独自タグなんですね。
ToDoリストは至ってシンプル。
ToDoの一覧となるパーツ。これ⇓⇓⇓。

とToDoを追加するためのパーツ。これ⇓⇓⇓。

そしてToDoの一覧の一行を構成するパーツ。これ⇓⇓⇓。

パーツは決まりましたので、このパーツをクラスとして定義することにします。
まず最初のパーツ ToDo一覧を DataList。
次にToDoを追加するパーツを NewToDo。
ToDoの一行を構成するパーツを DataLine
最後にこれら全部を包含する ToDoList とします。
図で書くとこんな感じになります。

ではまずToDo一覧である DataListをクラスとして定義してみましょう。
[bash]
class DataList extends React.Component{
constructor(props){
super(props);
}
handleOnDelete(id){
this.props.onDelete(id);
}
render() {
var dlist = [];
var i;
for (i=0; i < this.props.datas.length; i++){
var data = this.props.datas[i];
dlist.push( this.handleOnDelete(id)}/>);
}
return (
{dlist}
);
}
}
[/bash]
なんのことか理解できますか? 普通難しいなぁと思いますよね。
クラス定義の説明は HelloWorldを出力する部分でやったので割愛します。
では 次のconstructor とは何でしょうか・・・
要はクラスの初期化処理をここで行いますよ ということなのですが・・
初期化ですることは特にないので、お決まりのおまじない super(props)だけを記述しておきます。
[bash]
constructor(props){
super(props);
}
[/bash]
次の handleOnDelete これなんですが・・ 必要ですが、この説明は後回しにします。
[bash]
handleOnDelete(id){
this.props.onDelete(id);
}
[/bash]
最後に renderメソッドです。
HelloWorldでもやったように、renderメソッドで、このクラスが出力する
HTMLタグを構築するんです。
ToDoの一覧なんで、このクラスを作るときにToDoの一覧となるデータを引き渡してもらうことを想定します。
それが this.props.datas という変数で参照できます。
renderメソッドの中では、この変数をループで回して、 dlistという変数に pushしていってます。
pushする内容は、ToDoの1行を構成する DataLineタグ(オブジェクト)です。
そして renderメソッドがリターンする内容は <div>タグの中に上記の配列変数を組み込んだものになります。
[bash]
render() {
var dlist = [];
var i;
for (i=0; i < this.props.datas.length; i++){
var data = this.props.datas[i];
dlist.push( this.handleOnDelete(id)}/>);
}
return (
{dlist}
);
}
[/bash]
肝心なことを忘れていました。
3つのパーツを包含する一番大きなパーツが必要です。これをToDoListクラスとします。
ToDoListクラスのソースは以下のような感じになります。
[bash]
class ToDoList extends React.Component{
constructor(props){
super(props);
this.state = {
datas: props.datas
}
}
render(){
return (
);
}
}
[/bash]
ToDoListクラスの renderメソッドでは、NewToDoタグとDataListタグを構築しているのが解ると思います。
そして DataListタグの datasアトリビュートに this.state.datas を渡しています。
this.state.datas とは何でしょうか?
stateとはクラスが管理する動的な変数のようなものです。
ReactJSでは stateの値が変更されると、自動的にそのstateの値が指し示す HTMLを書き換える仕組み
になっているのです。
で、実際にここでは this.state.datas に何が入っているかというと、最初の方で説明した
ToDoリストの中身である DATAS変数が入ることになります。
これでは言葉足らずですね。
ToDoListクラスが全クラスのトップレベルのクラスであるので、
ReactDOM.render では ToDoListクラスを renderしてもらいます。
ソースで書くとこんな感じ。
[bash]
ReactDOM.render(
,
document.getElementById(“root”)
);
[/bash]
で、これだけだと、ToDoListの初期値を渡せていないことになるので、
正しくは 以下のようになります。
要はクラス内で使いたいデータは アトリビュートで渡そうね。ということになります。
で、データとは動的に変化するものなので {} で括って渡そうねというルールになります。
[bash]
var DATAS = [
{id:1, content:”Softbank解約”},
{id:2, content:”R-SIM10購入”},
{id:3, content:”圏外病を克服するSIMフリールーターゲット”},
{id:4, content:”楽天モバイル申し込み”}
]
ReactDOM.render(
,
document.getElementById(“root”)
);
[/bash]
で、まだこれでも DataListクラスの this.props.datas に DATASの値が渡っていく仕組みが説明できていませんね。
DataListクラスは ToDoListクラスの子供です。つまり DataListクラスは ToDoListクラスのrenderメソッドで
作成され、その時にDataListクラスが扱う変数として datasアトリビュートに DATASを渡すことになります。
日本語で書くとややこしいですね。
つまりは、ToDoListクラスのrenderメソッドでは 以下のようなことが定義されていることになります。
[bash]
class ToDoList extends React.Component{
constructor(props){
super(props);
this.state = {
datas: props.datas
}
}
render(){
return (
);
}
}
[/bash]
renderメソッドのアトリビュートで渡された変数は、 クラス内で props(プロパティ)として参照することが可能です。
ToDoListクラスは datasアトリビュートで渡された DATAS変数の中身を自身のクラスの stateとして管理しようとします。
これが constructorの中にある。
this.state = {datas: this.props.datas};
これで ToDoListクラス 内では this.state.datas で DATASの内容を参照できることになります。
やっとこれで DataListに DATASの値が渡ることが説明できましたかね・・・
では、次はDataListの renderメソッドでToDoの一行一行を構成するDataLineクラスを作っているところを
見てみましょう。
[bash]
render() {
var dlist = [];
var i;
for (i=0; i < this.props.datas.length; i++){
var data = this.props.datas[i];
dlist.push();
}
return (
{dlist}
);
}
[/bash]
配列変数 dlistに DataLineオブジェクトをpushしていってますね。
そして、DataLineオブジェクトの dataアトリビュートに DATASの1行の値
つまりToDo一件をデータとして渡していることになります。
DataLineオブジェクトでは this.props.dataでこの値を参照できることになります。
keyアトリビュートについてですが、ReactJSでは繰り返しループで同じオブジェクトを生成
するときは、1つ1つをユニークにする値をキー(key)として設定しないといけない
というルールがありますので、ここでは DATASの中のデータの idをキーとして設定しています。
では、ToDoの1件にあたるDataLineオブジェクトはなにをレンダーするでしょうか・・
[bash]
class DataLine extends React.Component{
constructor(props){
super(props);
}
render(){
return (
{this.props.data.id} — {this.props.data.content}
);
}
}
[/bash]
このようにDataLineクラスのrenderメソッドではボタン1つと、文字列としてのデータのidとcontentを出力しています。
ここまでで、ようやく、ToDoListで削除ボタン付きの一覧ができました。
でも、これじゃまだToDoも追加できないし、削除もできない。
まずは、削除の方の説明に入りましょう。
で、削除を考える前に、ReactJSでアプリを構築する上で重要なポイントを説明しておきます。
アプリはどんなパーツで構成されるか・・・、これはもう説明済みですね。
次にアプリで動的に変化する値で管理していかなければならないものは何か・・・
これを考えなくてはなりません。
動的に変化する値で、管理しなければならないもの=ステート(state)
なんです。
そして、もうちょっと厳密に言うと・・・、他の値から計算したりして求めることができる値は
ステートとしては管理しません。
どう計算しようとしても、どこからも導き出せない変化する値のみをステートとして管理します。
さて、ToDoリストにおいてステートはなにになるでしょうか・・・
まず当然ですがToDoリストの一覧データですね。
そしてもうひとつ、ToDoリストの1件を値を入力するテキストボックスに入力された値です。
そしてそして、最後にもうひとつ。ToDoリスト1件1件はユニークなものなので、
ToDo1件に対して1つのIDを持ちます。そのIDは重複してはいけないので IDの最大値を
ステートとして管理します。
で、またまたここも重要なポイントなのですが・・・
ステートを誰が、つまりどのクラスが管理するかということなのですが・・
一番トップレベルにあるクラスがステートを管理する
ということです。
厳密に言うと、本当はこれは嘘だと思いますが、慣れないうちは
一番トップレベルのクラスに管理させるのが良いでしょう。
つまり今回のToDoリストで言うと ToDoListクラスに管理させます。
[bash]
class ToDoList extends React.Component{
constructor(props){
super(props);
var maxId = 0;
props.datas.forEach(function(data){
if (data.id > maxId){
maxId = data.id;
}
});
this.state = {
datas: props.datas,
maxId: maxId,
content: ”
}
}
[/bash]
よって ToDoListクラスのコンストラクタは上記のようになります。
(コンストラクタではステートで管理する値の初期値を設定します。)
this.state = {ステートで管理する変数群}
そして、 this.state = と記述して、ステートの値を設定できるのはコンストラクタだけだと理解してください。
ReactJSでは、ステートの値が変化すると必要なところだけレンダリングを自動で行います。
ステートを変化させる。つまりステートの値をセットして自動レンダリングするには
クラスの setStateメソッドを使います。
例えば、ToDoリストの最大ID(maxId)を変化させようとするならば
ステートを管理するクラスであるToDoListクラスのメソッドの中で
[bash]
this.setState({maxId: xxxx});
[/bash]
と記述してやります。
IDの最大値を変えても、画面上変化する必要のあるところはないのでReactJSは
ステートの値のみを変化させ、なにもレンダリングは行いません。
ToDoリストからデータを削除することを考えると・・・
ToDoListクラス内の何らかのメソッドで
[bash]
var newDatas = [];
var i;
for (i = 0 ; i < this.state.datas.length ; i++){
var data = this.state.datas[i];
if (data.id != 削除するID){
newDatas.push(data);
}
}
this.setState({datas: newDatas});
[/bash]
としてやる必要があります。
ReactJSが管理する datasステートが変化すると、その変数の値を使って
レンダリングされている部分が自動的に調整されるのです。
例えば jQueryなんかで同じようなことをしようと思えば、
ToDoリストの一覧のタグの子供を(ToDo1件)ループさせながら
不要な行のHTMLタグを removeするなどですね。
では次にページ上で起こるイベントはどのようにして扱うのでしょうか?
まず、ToDo一件を削除する処理、つまりToDoリストの左側に表示されている
「削除ボタン」をクリックした処理をどこに記述して、クリックによって
ステートの値を変化させる必要があります。
「削除ボタン」はDataLineクラス内でレンダリングされているので、
当然クリックイベントの処理はDataLineクラス内に記述します。
どう書くかというと以下のようになります。
[bash]
class DataLine extends React.Component{
constructor(props){
super(props);
}
handleClick(event){
this.props.onDelete(this.props.data.id);
}
render(){
return (
{this.props.data.id} — {this.props.data.content}
);
}
}
[/bash]
ここは結構普通なのですが、削除ボタンをレンダーしている部分で
onClickイベントを定義してやります。
ReactJSでは onclickではなく、onClickです。
そしてonClickを処理するメソッドは自分のクラス内の handleClickメソッドとします。
で、handelClickメソッド内の記述を見てみると
[bash]
handleClick(event){
this.props.onDelete(this.props.data.id);
}
[/bash]
となっていて、ToDoリストのdatas を削除していませんね。
なぜかというと、今回管理するステートはトップレベルのクラスToDoListクラスで管理しているから・・
その変数を削除しようにも、削除できないのです。
では、どうするか・・・
自分でステートを処理できないなら、上位のクラスにお願いすれば良い
[bash]
this.props.onDelete(this.props.data.id);
[/bash]
これは、自分のクラスのプロパティに onDeleteというのを作るということを意味します。
そしてそれは変数を管理するプロパティではなくて、イベントを処理するプロパティだということです。
難しいですね。かなり頑張って理解してください。
DataLineクラスのonDeleteプロパティはイベントを処理するプロパティである。
で・・このイベントをDataLineクラスより上位のクラスで処理するにはどうするかというと
DataLineをレンダーする部分にアトリビュートとして、上位クラスのメソッドを定義してやれば良い。
ということになります。
具体的には
[bash]
class 上位クラス extends React.Component{
handleDelete(id){
ステートをなんとか削除しようと努力する。
}
render(){
}
}
[/bash]
こんな感じになります。
で、今回の例で言うと、DataLineの1つ上位のクラスは DataListクラスなので・・・
データ一覧のステート変数を参照できません。
出来ない????
と思う方もいらっしゃるかもしれません。
DataListはデータの一覧を出力してるんだから、変数の値を削除できるじゃん。
そう思うかもしれませんね。
でも ReactJSの鉄則
ステートは最上位のクラスで管理する
DataListは最上位じゃありませんね。つまりステートを管理していない。
なので、削除しようにも削除できません。
一覧データはステート(state)ではなく、プロパティ(props)で参照できるのみです。
じゃあ困った・・・ どうすりゃいいんだとなりますが・・・
自分のクラスでステートを変化させることが出来ないなら、上位に伝えりゃいい
で、伝える方法ですが、これはDataLineクラスの onDeleteプロパティと同じで、
DataListクラスにも onDeleteプロパティを作り、そこを経由して、最上位の
ToDoListクラスに情報を伝えりゃいい、ということになります。
では、先ほどの部分を書き換えてみます。
[bash]
class 上位クラス extends React.Component{
handleDelete(id){
//ステートを直接削除するのではなく、上位に伝える。
this.props.onDelete(id);
}
render(){
}
}
[/bash]
で、DataListクラスの上位クラスは ToDoListクラスなので、
ToDoListクラスの renderメソッドで DataListクラスをレンダーする際に
onDeleteプロパティの値を ToDoListクラス内のメソッドへ転送してやれば良い。
具体的には、
[bash]
class ToDoList extends React.Component{
handleOnDelete(delid){
var datas = this.state.datas.slice();
var newDatas = [];
datas.forEach(function(data){
if (data.id != delid){
newDatas.push(data);
}
});
this.setState({datas: newDatas});
}
render(){
return (
);
}
}
[/bash]
このように記述してやります。
なにをしているかというと、 DataListクラス内で起こった onDeleteイベントは
自分のクラス内のメソッド handleOnDeleteで処理するよ。ということです。
ToDoListクラスのhandleOnDeleteでは、ようやく ToDo一覧のステートを参照することが
できるので、いよいよここでステートの値を変化させます。
handleOnDeleteメソッドの中を見ていただくと・・・
state.datas をループさせながら、そのデータのidがメソッドの引数として渡ってきた。
削除対象IDかどうかをチェックして、対象外なら新たな配列変数にpushする。
そして、全データのループが終わったタイミングで
[bash]
this.setState({datas: newDatas});
[/bash]
として、ステートの変更をReactJSに通知しています。
長かったですね。理解できましたか??
では次行ってみましょう。
いよいよToDoの追加です。
追加は NewToDoクラスで行います。
[bash]
class NewToDo extends React.Component{
constructor(props){
super(props);
}
handleClick(){
this.props.onAdd();
}
handleChange(event){
var val = event.target.value;
this.props.onContentChange(val);
}
render(){
return (
this.handleChange(e)}/>
);
}
}
[/bash]
いきなりソース全部貼り付けたので ぎょっとされるかもしれませんが・・・
ここまでのことが理解できていたらなんとかなると思います。
まずは renderメソッドから説明。
divタグの中にlabel1つと、input1つとbutton1つをレンダーしてます。
labelは単なる目印なので説明は省略するとして・・・
input はToDoの中身を記述するテキストボックスです。
button はToDoを追加するためのボタンです。
では、ここで改めてToDoList全体が管理するステートが何だったか思い出してみましょう。
- ToDoリストの一覧データ
- ToDoリストの1件を値を入力するテキストボックスに入力された値
- データのIDの最大値
そしてこれらのデータはすべてトップレベルのクラス ToDoListで管理されるのでした。
つまりNewToDoクラスの input に入力された値は、NewToDoクラスで管理するのではなく
上位のToDoListクラスに管理を任せなければならない。
そして、NewToDoクラスのbuttonクリックでToDoリストに追加したいが、これも
上位のToDoListクラスにボタンが押されたことを通知して、処理を任せなければならない。
このことが
[bash]
this.handleChange(e)}/>
[/bash]
inputでは onChange つまり値が1文字でも変化するたびに、親クラスであるToDoListに
イベントを経由してステートの変化を伝えている。
ボタンのクリックは clickしたタイミングで親クラスであるToDoListに
イベントを経由してステートの変化を伝えている。
これだけです。NewToDoクラスは自分ですることは、ほとんどなにもありません。
変化(イベント)があったことを親クラスへ通知しているのみです。
では最後、トップレベルの ToDoListクラスを覗いてみましょう。
[bash]
class ToDoList extends React.Component{
constructor(props){
super(props);
var maxId = 0;
props.datas.forEach(function(data){
if (data.id > maxId){
maxId = data.id;
}
});
this.state = {
datas: props.datas,
maxId: maxId,
content: ”
}
}
handleContentChange(ct){
this.setState({content: ct});
}
handleOnAdd(){
var maxId = this.state.maxId + 1;
var datas = this.state.datas.slice();
datas.push({id: maxId, content: this.state.content});
this.setState({maxId: maxId, content: ”, datas: datas});
}
handleOnDelete(delid){
var datas = this.state.datas.slice();
var newDatas = [];
datas.forEach(function(data){
if (data.id != delid){
newDatas.push(data);
}
});
this.setState({datas: newDatas});
}
render(){
return (
this.handleOnAdd()}
onContentChange={(ct) => this.handleContentChange(ct)}
/>
);
}
}
[/bash]
まず注目していただきたいのは renderメソッド。
- NewToDoクラスのレンダー
- DataListクラスのレンダー
NewToDoクラスの onAddプロパティ(イベント)に自分のクラスの handleOnAddメソッドへの転送を設定。
onContentChangeプロパティ(イベント)に自分のクラスの handleContentChangeメソッドへの転送を設定。
DataListクラスのonDeleteプロパティ(イベント)に自分のクラスの handleOnDeleteメソッドへの転送を設定。
DataListクラスのonDeleteの方はすでに説明済みなので、NewToDoクラスの方の説明をします。
[bash]
handleContentChange(ct){
this.setState({content: ct});
}
[/bash]
NewToDoクラスの input(テキストボックス)で起こった値の変化をイベント経由で通知してもらって
setStateメソッドで ステートを変更。
[bash]
handleOnAdd(){
var maxId = this.state.maxId + 1;
var datas = this.state.datas.slice();
datas.push({id: maxId, content: this.state.content});
this.setState({maxId: maxId, content: ”, datas: datas});
}
[/bash]
NewToDoクラスのボタンクリックをイベント経由で通知してもらって、
最大値を1加算して、ToDoデータの最後尾にデータを追加(push)。
そして、setStateで maxIdとcontentとdatas の変更を伝え
ReactJSにレンダーを依頼します。
長かったですね。ようやくToDoリストの完成です。
部分部分を切り取ったソースで見づらかったと思いますので、全ソースを以下に貼り付けておきます。
[bash]
[/bash]
Leave a comment