LoginSignup
22
21

More than 5 years have passed since last update.

超簡単 Reactjs もうflux、store、reduxで悩まなくても良い。onChangeのhandlerも記述不要。

Last updated at Posted at 2016-01-27

GitHub simplereactjs https://github.com/mikeshimura/simplereactjs

Reactjsは、本来 Viewのみの部分で、それを補完するために、fluxやreduxが必要と考えられています。

しかし、下記の原則にそって記述すれば、簡単に少ないコードで作成できます。

  • 状態を管理する stateは、一番 TOPのコンポーネントにのみ定義する。
  • 一番 TOPのコンポーネントの参照が容易になるよう、Global変数(今回の例では $w.app)に設定する。
  • 上記のGlobal変数から、どこからでも stateが参照できる。(今回の例では $w.app.state)
  • stateの変更は、必ず SetState()関数を使用する。(今回の例では $w.app.SetState(XXXXX))
  • Inputコンポーネントの nameは、stateの階層構造を#で区切って設定する。
    例えば、form.nameは form#name。
    これにより、onChange eventは、共通処理 プログラムで、 例えば \$c.onChangeを呼び出すだけで、自動的に設定される。(CheckBoxの場合は、$c.onChecked)

サンプルプログラム

Simple1

なお当プログラムは、HerokuでDemoが見られます。http://simplereactjs.herokuapp.com/

また下記リンクからダウンロードして、解凍すればローカルで動かせます。

index.htmlやmain.jsxを編集して、自分で試す事が簡単に出来ます。

なお今回は、JSXTransforerをブラウザで動かすことで、jsxファイルの事前コンパイルを不要にしています。

このため最新のバージョンでなく、0.13.3を使用しています。(0.14以降はサポートされていません。)

windows

mac amd64

サンプルプログラムの動かし方

  • データ読み込みボタン
    サーバーからデータを読み込んで、テーブルに表示します。下のフォームはクリアされます。
    テーブル内をクリックすると、その行のデータが選択されて下のフォームに表示されます。
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dataRead} name="btnSearch"
                style={{width:$w.W.b1,marginLeft:20}}>データ読込 </b.Button>
    ///////////////////////////////////////////////////////////////////////////
    callback: function(res,status) { 
        var para =
            {
                rcds:res,
                selRow:-1,
                form:$c.deepCopy($w.app.state.blank)
            }
         $w.app.setState(para)
    },    
    dataRead: function() {    
        $c.ajaxPostJson("dataget",{},this.callback)
    },
  • 更新ボタン
    フォームで修正した内容を上記テーブルに反映します。 本来はサーバーにデータを送り、戻ってきたデータを反映するのですが、今回は省略して、直接反映しています。
    IDがnullの場合は、新規追加処理、入っている場合は、データ更新処理を行います。
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.updateClick} name="btnUpdate"
                style={{width:$w.W.b2,marginLeft:20}}>更新</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    updateClick:function(e){
        var para=$w.app.state
        if ($w.app.state.form.id==null){
            var len=$w.app.state.rcds.length
            para.rcds[len]=$c.deepCopy($w.app.state.form)
            para.rcds[len]["id"]=this.getId()
            para.selRow=-1
            para.form=$c.deepCopy($w.app.state.blank)
        } else {
            para.rcds[$w.app.state.selRow]=$c.deepCopy($w.app.state.form)
        }
        $w.app.setState(para)
  • 新規ボタン
    フォームをクリアします。
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.newClick} name="btnNew"
                style={{width:$w.W.b2,marginLeft:20}}>新規</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    newClick:function(e){
        var para={
            selRow:-1,
            form:$c.deepCopy($w.app.state.blank)
        }
        $w.app.setState(para)
    },
  • 削除ボタン
    フォームのデータをテーブルから削除します。
    本来はサーバーにデータを送り、戻ってきたデータを反映するのですが、今回は省略して、直接反映しています。
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.deleteClick} name="btnDelete"
                style={{width:$w.W.b2,marginLeft:20}}>削除</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    deleteClick:function(e){
        var para=$w.app.state
        para.rcds.splice(para.selRow,1)
        para.selRow=-1
        para.form=$c.deepCopy($w.app.state.blank)
        $w.app.setState(para)
    },
  • ダイアログ ボタン
    モーダルダイアログを表示します。
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dialogTest} name="btnSearch2"
                style={{width:$w.W.b1,marginLeft:20}}>ダイアログ</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    dialogTest:function(e){
        para=$w.app.state.alert
        para["isShow"]=true
        para["message"]="これはダイアログの\nテストです。"
        $w.app.setState(para)
    }
  • ダイアログ の了解 ボタン
    モーダルダイアログをクローズします。 共通のjsxファイルにあります。
    <b.Button bsStyle="primary" onClick={this.onClick} 
                name="alert#CloseBtn">了解</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    onClick:function(){
        para=$w.app.state.alert
        para["isShow"]=false
        para["message"]=""
        $w.app.setState(para)
    }

Application定義の最初の部分(例)

ここで、stateの設定をしている。

 var Application = React.createClass({
    getInitialState: function() {
        $w.app = this; //ここでGlobal変数に設定。なお$wは予め定義してある。
        return {
                    blank:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    rcds:[],
                    form:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    selRow:-1,
                    alert:{
                        isShow:false,
                        message:""
                    }
                  };
    },

テキスト Input (例)

<b.Input type="text" name="form#name" 
                value={$w.app.state.form.name} onChange={$c.onChange} 
                style={{width:"120",marginLeft:20,color:"#000000"}}/>

チェックボックス (例)

<b.Input type="checkbox" name="form#action" 
            checked={$w.app.state.form.action?"checked":""} 
            onChange={$c.onChecked} 
            style={{width:"20",height:"20",marginTop:-10
            ,marginLeft:0,color:"#000000"}}/>

セレクトボックス (例)

<b.Input type="select" label='' name={"form#cat"} 
              onChange={$c.onChange}  
              style={{height:30, width:88,color:"#000000"}}>
                {$c.Option($w.app.state.form.cat,$w.catMap)}
            </b.Input>

main.jsx

  $c.checkAndCreate("$w");
 $w.tableColW={c1:120,c2:200,c3:40,c4:40,c5:40}
 $w.W={b1:100,b2:50,l1:60}
 $w.catMap={
     "":"",
     "1":"CAT1",
     "2":"CAT2",
     "3":"CAT3",
 }
 var b = ReactBootstrap;
 var Item = React.createClass({ 
  render: function() {
    var c = this.props.rcd;
    return (
      <tr key={this.props.no*10} id={"row"+this.props.no} 
        onClick={$w.app.rowClick}  style={{backgroundColor: 
            (this.props.no==$w.app.state.selRow)?"#FFD0D0":"#FFFFFF"}} >
        <td key={this.props.no*10+1}
            style={{width:$w.tableColW.c1,border:1,borderStyle:"solid"}}>
                {c.name}</td>
        <td key={this.props.no*10+2}
            style={{width:$w.tableColW.c2,border:1,borderStyle:"solid"}}>
                {c.mail}</td>
        <td key={this.props.no*10+3}
            style={{width:$w.tableColW.c3,border:1,borderStyle:"solid"}}>
                {c.action?"":""}</td>
        <td key={this.props.no*10+4}
            style={{width:$w.tableColW.c4,border:1,borderStyle:"solid"}}>
                {$w.catMap[c.cat]}</td>
        <td key={this.props.no*10+5}
            style={{width:$w.tableColW.c5,border:1,borderStyle:"solid"}}>
                {c.id}</td>
      </tr>
    );
  }
 });
  var Application = React.createClass({
    getInitialState: function() {
        $w.app = this;
        return {
                    blank:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    rcds:[],
                    form:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    selRow:-1,
                    alert:{
                        isShow:false,
                        message:""
                    }
                  };
    },
    render: function() {
        var rcds = $w.app.state.rcds;
        var object_list = [];
        for (var i = 0; i < rcds.length; i++) {
            object_list.push(
                <Item rcd={rcds[i]} no={i}/>
            );
        }
         return (  
            <div style={{width:480}}>
        <b.Row style={{margin:20}}>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dataRead} name="btnSearch"
                style={{width:$w.W.b1,marginLeft:20}}>データ読込 </b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dialogTest} name="btnSearch2"
                style={{width:$w.W.b1,marginLeft:20}}>ダイアログ</b.Button>
        </b.Row>
        <table cellPadding={10} cellSpacing={10} 
            style={{border:1,borderStyle:"solid",
             width:500,backgroundColor: "#F0F0F0"}}>
            <thead>
            <tr>
              <th width={$w.tableColW.c1} style={{border:1,borderStyle:"solid"}}>
                名前</th>
              <th width={$w.tableColW.c2} style={{border:1,borderStyle:"solid"}}>
                メール</th>
              <th width={$w.tableColW.c3} style={{border:1,borderStyle:"solid"}}>
                Action</th>
              <th width={$w.tableColW.c4} style={{border:1,borderStyle:"solid"}}>
                Cat</th>
              <th width={$w.tableColW.c5} style={{border:1,borderStyle:"solid"}}>
                id</th>
            </tr>
            </thead>
            <tbody>

            {object_list}
            </tbody>
        </table>        
        <b.Row style={{margin:20}}>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.updateClick} name="btnUpdate"
                style={{width:$w.W.b2,marginLeft:20}}>更新</b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.newClick} name="btnNew"
                style={{width:$w.W.b2,marginLeft:20}}>新規</b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.deleteClick} name="btnDelete"
                style={{width:$w.W.b2,marginLeft:20}}>削除</b.Button>
        </b.Row>  
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>名前</span>
            <b.Input type="text" name="form#name" 
                value={$w.app.state.form.name} onChange={$c.onChange} 
                style={{width:"120",marginLeft:20,color:"#000000"}}/>
          </b.Row> 
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>メール</span>
             <b.Input type="text" name="form#mail" 
                value={$w.app.state.form.mail} onChange={$c.onChange}  
                style={{width:"300",marginLeft:20,color:"#000000"}}/>
        </b.Row> 
        <b.Row style={{marginLeft:20,height:40}}>
           <span style={{width:$w.W.l1,marginLeft:20,float:"left",
            color:"#000000"}}>Action</span>
            <b.Input type="checkbox" name="form#action" 
            checked={$w.app.state.form.action?"checked":""} 
            onChange={$c.onChecked} 
            style={{width:"20",height:"20",marginTop:-10
            ,marginLeft:0,color:"#000000"}}/>
        </b.Row> 
          <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>CAT</span>
            <b.Input type="select" label='' name={"form#cat"} 
              onChange={$c.onChange}  
              style={{height:30, width:88,color:"#000000"}}>
                {$c.Option($w.app.state.form.cat,$w.catMap)}
            </b.Input>
        </b.Row> 
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,float:"left",
                marginLeft:20,color:"#000000"}}>ID</span>
            <b.Input type="text" value={$w.app.state.form.id}
                disabled  style={{width:"60",color:"#000000"}}/>
        </b.Row> 
        <span style={{clear:"both"}}></span>
        <$c.Alert/>          
        </div>
        )
    } ,
    callback: function(res,status) { 
        var para =
            {
                rcds:res,
                selRow:-1,
                form:$c.deepCopy($w.app.state.blank)
            }
         $w.app.setState(para)
    },    
    dataRead: function() {    
        $c.ajaxPostJson("dataget",{},this.callback)
    },
    rowClick:function(e){
        row=e.target.parentNode.id.substr(3)
        var para={
            selRow:parseInt(row,10),
            form:$c.deepCopy($w.app.state.rcds[row])
        }
        $w.app.setState(para)
    },
    newClick:function(e){
        var para={
            selRow:-1,
            form:$c.deepCopy($w.app.state.blank)
        }
        $w.app.setState(para)
    },
    getId:function(){
        var maxid=-1
        for (i=0;i<$w.app.state.rcds.length;i++){
            if ($w.app.state.rcds[i]["id"] > maxid){
                maxid = $w.app.state.rcds[i]["id"]
            }
        }
        return maxid+1
    },
    deleteClick:function(e){
        var para=$w.app.state
        para.rcds.splice(para.selRow,1)
        para.selRow=-1
        para.form=$c.deepCopy($w.app.state.blank)
        $w.app.setState(para)
    },    
    updateClick:function(e){
        var para=$w.app.state
        if ($w.app.state.form.id==null){
            var len=$w.app.state.rcds.length
            para.rcds[len]=$c.deepCopy($w.app.state.form)
            para.rcds[len]["id"]=this.getId()
            para.selRow=-1
            para.form=$c.deepCopy($w.app.state.blank)
        } else {
            para.rcds[$w.app.state.selRow]=$c.deepCopy($w.app.state.form)
        }
        $w.app.setState(para)
    },
    dialogTest:function(e){
        para=$w.app.state.alert
        para["isShow"]=true
        para["message"]="これはダイアログの\nテストです。"
        $w.app.setState(para)
    }
  });
React.render(React.createElement(Application, null),
     document.getElementById('content'));

commonjsx.jsx

var b = ReactBootstrap;
$c.MulitLine = React.createClass({
  render: function () {
    var sarray = this.props.value.split("\n");
    var lines = sarray.map(function(line,i){
        if (i===0){
          return <span key={i}>{line}</span>;
        } else {
          return <span  key={i}><br/>{line}</span>;
        }
      },this);
    return (
        <div>
        {lines}
        </div>
      );
  }
});
$c.Alert = React.createClass({
    mixins: [b.OverlayMixin],
    render: function () {
        return (
            <span/>
        );
    },
    renderOverlay: function () {
        if ($w.app.state.alert.isShow==false){
            return (
            <span/>
        );
    }
    return (
        <b.Modal onRequestHide={function(){}} className="alert" >
            <div className="modal-body">
            <$c.MulitLine value={$w.app.state.alert.message} />
                </div>
            <div className="modal-footer">
            <b.Button bsStyle="primary" onClick={this.onClick} 
                name="alert#CloseBtn">了解</b.Button>
            </div>
            </b.Modal>
        );
    },
    onClick:function(){
        para=$w.app.state.alert
        para["isShow"]=false
        para["message"]=""
        $w.app.setState(para)
    }
});  
$c.Option= function(value,map){
var options = []
for (key in map){
        options.push( 
        <option  
        value={key} label={map[key]} 
        selected={(key==value)?"selected":""}
        >{map[key]}</option> )
    }  
    return options  
}

common.js

"use strict";
var checkAndCreate;
checkAndCreate = function (v) {
    if (window[v] == null) {
        return window[v] = {};
    }
};
checkAndCreate("$c");
$c.checkAndCreate = checkAndCreate;
$c.contextpath = "/";
$c.onChange = function (e) {
    var state = $w.app.state;
    var names = e.target.name.split("#")
    switch (names.length) {
        case 1: state[names[0]] = e.target.value
            break;
        case 2: state[names[0]][names[1]] = e.target.value
            break;
        case 3: state[names[0]][names[1]][names[2]] = e.target.value
            break;
        case 4: state[names[0]][names[1]][names[2]][names[3]]
             = e.target.value
            break;
        default:
            console.error("name length over")
    }
    $w.app.setState(state)

};
$c.onChecked = function (e) {
    var state = $w.app.state;
    var names = e.target.name.split("#")
    switch (names.length) {
        case 1: state[names[0]] = e.target.checked ? true : false
            break;
        case 2: state[names[0]][names[1]] = e.target.checked ?
             true : false
            break;
        case 3: state[names[0]][names[1]][names[2]] =
            e.target.checked ? true : false
            break;
        case 4: state[names[0]][names[1]][names[2]][names[3]] = 
            e.target.checked ? true : false
            break;
        default:
            console.error("name length over")
    }
    $w.app.setState(state)
};
$c.ajaxPost = function (url, data, contenttype, callback) {
    $.ajax({
        type: "POST",
        url: $c.contextpath + url,
        data: data,
        contentType: contenttype
    }).fail(function (jqXHR, textStatus) {
        console.log("Internet or Server Error")
        callback(jqXHR, textStatus)
    }).done(callback);
}
$c.ajaxPostJson = function (url, param, callback) {
    var data = JSON.stringify(param);
    $c.ajaxPost(url, data, "text/json", callback);
}
$c.deepCopy = function (obj) {
    return $.extend(true, {}, obj)
}

22
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
21