はじめに
前回、Reactをやってみるってことで3ステップで環境構築してみました。
今回はこれをベースにして、以前、Vueで作った『スクローラブルリスト』をReact
でやってみて Vue
との違いを理解してみたいと思います。
出来上がりはこんな感じです。
Vue
とあまり変わり映えしませんが、出来上がりはこんな感じです。
一応、React
のロゴになってるのわかりますでしょうか?
コードはここです。
GitHub - Shikataramuno/responsive-scrollable-grid-by-react: レスポンシブでスクロール可能なリスト 今度はReact
React
でやることを優先したので、Typescript
ではなく敢えてJavaScript
でやってみました。
React
の詳しい仕様はここを参照してください。
React – A JavaScript library for building user interfaces
MemberList コンポーネントの追加
まず、前回の記事を参考に test-project
ってプロジェクトを作っておいてください。
src
フォルダ以下に MemberList.js
ってファイルを作ってコンポーネントを定義します。コンポーネントのインターフェースは以下の様にします。
import React, { Component } from 'react'; import {Row, Col, Form, ProgressBar} from 'react-bootstrap'; import 'bootstrap/dist/css/bootstrap.min.css'; import './MemberList.css'; export default class MemberList extends Component { columns = ['id', 'name', 'admin', 'address', 'progress']; constructor(props) {...} filter = (e) => {...} sortBy = (name) => {...} handleAdminChanged = (member) => {...} dhead = () => {...} dlist = () => {...} list = () => {...} render () { return <this.list/> } componentDidMount () {...} }
9行目は、MemberList
クラスのコンストラクタです。
React
では、コンストラクタでコンポーネントの状態にはどんな項目があるかを定義します。MemberList
クラスでは、リストのソートキーとオーダー、リストのフィルタ文字列、メンバーのリストなんかをコンポーネントの状態として定義しておきます。
11行目~13行目はUIのイベントハンドラの定義です。
MemberList
コンポーネントでは、フィルタのテキスト入力イベント、ソートする列タイトルのクリックイベント、チェックボックスのチェンジイベントの3つのイベントハンドラを定義します。
何れも Arrow
関数で宣言し、暗黙的に this
参照を Component
に bind
する様にしています。
15行目~18行目の render
までがHTML
を生成するコードに該当します。
リストのヘッダ部分の html
を dhead()
メソッドで、リスト部分の html
をdlist()
メソッドで作成します。そして、それらの html
コードを list()
メソッドで統合します。これを render()
メソッドから呼び出す構造にしています。
19行目からはコンポーネントの LifeCycle
メソッドです。Vue
で言うところの created
やmounted
に該当するメソッドです。
今回はマウントされた時点でコンポーネントの状態を初期化する様にするため componentDidMount()
メソッドで初期化を実行します。
では、それぞれについて、もう少し詳しく見ていきましょう
コンストラクタ
コンストラクタの実装は次のとおりです。
constructor(props) { super(props); this.state = { members: [], sortOrders: {}, sortKey: "" } }
UIの操作や、外部との通信によって変化する情報があれば、それをコンポーネントの状態としてthis.state
で定義します。
MemberList
コンポーネントでは状態にメンバーオブジェクトの配列、ソートオーダー、ソートキーを定義し、それぞれ[],{},''
で初期化しています。
フィルタのテキスト入力イベント
- イベントハンドラの定義
filter = (e) => { const filter = e.target.value; let members = this.memberList; members = members.filter((member) => { return Object.keys(member).some((key) => { return String(member[key]).indexOf(filter) > -1; }); }); this.setState({members: members}) }
入力された文字列でリストをフィルタするので、filterという名前のメソッドをArrow形式で定義しています。Arrow形式で定義することでデフォルトでthis
にbind
されます。
2行目で引数で受け取ったイベントオブジェクトからイベントを検知したUIの要素を抽出しています。3~8行目の内容はVue
と何ら変わりません。
Vue
と異なるのは、9行目でフィルタした結果で明示的にコンポーネントの状態を更新しているところです(this.setState
)。
この更新操作によりコンポーネントの状態が更新され、それを契機にrender関数が呼ばれUIが再描画される一連の処理がリアクティブに動きます。
Vue
ではコンポーネントに閉じた状態であれば、状態を表す変数に直接アクセスして更新できましたが、React
では必ず this.setState()
で更新しなければなりません。React
は厳格に一方通行のデータフローに従うポリシーなのです。
- HTMLの生成とイベントハンドラの呼び出し
dhead = () => { return ( <div className="pc table-row header"> <Row> <Form.Label className="title" >メンバ一覧 </Form.Label> </Row> <Row className='query-box'> <Col xs={2}> <Form.Control as="input" type="text" id="search" className="filter" placeholder="フィルタ文字列" onChange={this.filter}/> </Col> <Col xs={10}></Col> </Row> <div className="wrapper attributes header"> ... 中略 ... </div> </div> ); }
dhead()
メソッドでは、JSX (JavaScript XML)
というマークアップ言語を記述するためのXML風シンタックスに従ってリストのヘッダ部分のHTMLを生成するロジックを記述しています。
そしてヘッダ部のForm要素でテキスト入力イベントを検知しますので、10行目で From.Control
の onChange
イベントで filter()
メソッドを呼び出す様にしています。
列タイトルのクリックイベント
- イベントハンドラの定義
sortBy = (name) => { let sortOrders = this.state.sortOrders sortOrders[name] = sortOrders[name] * -1; let members = this.state.members; members = members.slice().sort((a,b) => { a = a[name] b = b[name] return (a === b ? 0 : a > b ? 1 : -1) * sortOrders[name]; }); this.setState({sortKey: name}); this.setState({sortOrders: sortOrders}); this.setState({members: members}) }
このハンドラではイベントを検知した要素を知らなくてもよいので引数にイベントオブジェクトはありません。その代わり、イベントを検知したタイトル列の名前を引数で受け取る様にしています。
その他はテキスト入力イベントと同様です。
- イベントハンドラの呼び出し
dhead = () => { return ( <div className="pc table-row header"> <Row> ... 中略 ... </Row> <div className="wrapper attributes header"> { this.columns.map((name,col) => { const className = this.state.sortKey === name ? "active " + name : name const arrow = this.state.sortKey === name ? (this.state.sortOrders[name] > 0? <span className={"arrow asc"}></span> : <span className="arrow dsc"></span>) : ""; return ( <div className={className} key={col} onClick={() => this.sortBy(name)}> {name} {arrow} </div> ) }) } </div> </div> ); }
sortBy()
の呼び出しは、filter()
と同じくヘッダ部のイベントで呼び出されます。17行目が呼び出しで、引数に9行目の this.columns.map
の処理内で個々の列名を示すname
を渡しています。
今回は、イベントを検知したUI要素を特定する必要がないので、引数にイベントオブジェクトを含めていません。イベントオブジェクトが必要でかつ、Arrow形式で定義する場合はイベントオブジェクトを明示的に引数に含めてやんなきゃいけません。そうした場合、17行目はこんな感じになります。
onClick={(e) => this.sortBy(name, e)}>
チェックボックスイベントハンドラ
- イベントハンドラの定義
handleAdminChanged = (member) => { let list = this.state.members; const target = list.find(rec => { return rec.id === member.id }) target.admin = !target.admin; this.setState({members: list}) }
引数の定義などは `sortBy()'と同じなので割愛しまぁ~す (謝)
- イベントハンドラの呼び出し
dlist = () => { return ( <div className="data-field"> { this.state.members.map((member, row) => { return ( <div className="table-row wrapper attributes data" key={row}>{ this.columns.map((name,idx) => { if(name === "admin") { return ( <div className={name} key={idx}> <Form.Check type="checkbox" variant="success" checked={member[name]} onChange={() => this.handleAdminChanged(member)} /> </div> ) } else if(name === "progress") { return ( <div className={name} key={idx}> <ProgressBar variant="success" now={member[name]} label={`${member[name]}%`}/> </div> ) } else { return ( <div className={name} key={idx}> {member[name]} </div> ) } }) }</div> ); }) } </div> ); }
dhead()
メソッドと同じく JSX
でリスト部分のHTMLを生成するためのロジックを記述しています。
5~35行目の this.state.members.map((member, row) => {...}
の中でメンバオブジェクトの配列の要素ごとにHTMLを生成しています。
index.jsを変更する
あとは、index.jsで 'App' コンポーネントを呼び出すようになっているところを MeberList
コンポーネントに変更します。4行目と7行目です。
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import MemberList from './MemberList'; <----- ここ import * as serviceWorker from './serviceWorker'; ReactDOM.render(<MemberList />, document.getElementById('root')); <---- ここ serviceWorker.unregister();
これで修正は完了です。
出来上がり!
では、npm start
してみましょう。
ビルドが完了するとテストサイト( http://localhost:3000
)でブラウザが自動で立ち上がります。
如何ですか?きちんとリストが表示され、ソート、フィルタ、チェックの操作ができましたか?
ポイントは、MemberList.jsでコンポーネントを追加し、index.jsで呼びだしをAppからMemberListに変えてやるところでしょう。
デフォルトで作られるApp.jsは不要になるので、削除しておkです。
(あと、見栄えのためにMemberList.cssも追加しとかないと綺麗に表示されませんのでご注意を)
謝辞
ひとまず React.js
でスクローラブルリストをやってみました。
Vue
との違いがいくつかありましたが注意すべきは、HTMLを書くのではなく、HTMLを生成するロジックをコードで書くという観点と、イベントハンドラの呼び出しコンテキスト、引数と思います。
次の記事で、この点を整理して要点を抑えておきたいと思います。
最後まで読んでいただきありがとうございます! 少しでもお役に立てたなら幸いです。
ではでは