前回、アプリケーションの雛形を作成したので、続いて作成する簡易カンバンの雛形を作ります。
とりあえず、Reactのコンポーネントのpropsを通じて静的なデータを受け渡すことで、画面イメージを作ります。
この記事で触れること
- react-bootstrap
- react(props)
画面とコンポーネントの設計をする
見せる用に綺麗に書くのも時間がかかるので、ホワイトボードに書いたものを貼り付けておきます。
TrelloやJIRAイメージしています。
黒字と青字なので少し見辛いですが、青枠で囲った単位でコンポーネントを作成していきます。
index.js
まずは、受け渡すデータの構造を作成します。
とりあえずは固定値で画面を描画するため、基点ファイルでデータを定義して下層コンポーネントに受け渡していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; const card1Tasks = [ { id: 1, label: '設計', isDone: true, orderBy: 1 }, { id: 2, label: '固定値', isDone: true, orderBy: 2 }, { id: 3, label: 'Appコンポーネント', isDone: true, orderBy: 3 }, { id: 4, label: 'Laneコンポーネント', isDone: true, orderBy: 4 }, { id: 5, label: 'Cardコンポーネント', isDone: false, orderBy: 5 }, { id: 6, label: 'Taskコンポーネント', isDone: false, orderBy: 6 }, { id: 7, label: '画面デザイン', isDone: false, orderBy: 7 } ]; const card2Tasks = [ { id: 8, label: 'あんなこといいな', idDone: false, orderBy: 1 }, { id: 9, label: 'できたらいいな', idDone: false, orderBy: 2 }, { id: 10, label: 'あんな夢こんな夢いっぱいあるけど', idDone: false, orderBy: 3 } ]; const cards = [ { id: 1, label: '値が固定の雛形を作る', description: 'React-Bootstrapを使う', status: 1, tasks: card1Tasks, orderBy: 1 }, { id: 2, label: '状態を変化できるようにする', description: '', status: 1, tasks: card2Tasks, orderBy: 2 }, { id: 3, label: 'アプリケーションの雛形を作る', description: 'http://kuneo.org/javascript/2778/', status: 3, tasks: [], orderBy: 1 } ]; ReactDOM.render(<App cards={cards}/>, document.getElementById('root')); registerServiceWorker(); |
前回と今回の記事に該当しそうなカード、タスクのデータにしてみました。
ポイントは、L99で定義したデータをAppコンポーネントに受け渡しているところでしょうか。コンポーネント間で値を受け渡す、Reactの基本的な構文ですね。
Appコンポーネント
続いて、カンバンの大枠を作成します。
とりあえず、ステータスがTodo、WIP、Doneの3つであることを想定してLaneコンポーネントを配置していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import React from 'react'; import './App.css'; import Lane from './components/Lane' export default class App extends React.Component { render() { const LANE = { TODO: { ID: 1, LABEL: 'TODO' }, WIP: { ID: 2, LABEL: 'WIP' }, DONE: { ID: 3, LABEL: 'DONE' }, }; return ( <div className="app"> <Lane id={LANE.TODO.ID} label={LANE.TODO.LABEL} cards={this.props.cards.filter(card => card.status === LANE.TODO.ID)} /> <Lane id={LANE.WIP.ID} label={LANE.WIP.LABEL} cards={this.props.cards.filter(card => card.status === LANE.WIP.ID)} /> <Lane id={LANE.DONE.ID} label={LANE.DONE.LABEL} cards={this.props.cards.filter(card => card.status === LANE.DONE.ID)} /> </div> ); } } |
コンポーネントに渡されたpropsを取得する方法は、this.props.xxxですね。
ポイントとしては、先ほどindex.jsで定義してpropsとして受け渡した値を、各Laneコンポーネントに割り当てられているCardオブジェクトに絞り込んで受け渡しているところでしょうか。
Lane
続いて、ステータスごとにカードを配置する縦列のコンポーネントを作っていきます。
ポイントとしては、列に配置されるCardオブジェクトを配列で持つことでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import React from 'react'; import { Col } from 'react-bootstrap'; import Card from './Card'; export default class Lane extends React.Component { // orderByの昇順にソート compare(a, b) { let comparison = 0; if (a.orderBy > b.orderBy) { comparison = 1; } else { comparison = -1; } return comparison; } render() { const cards = this.props.cards.sort(this.compare).map(card => { return ( <Card key={card.id} id={card.id} label={card.label} description={card.description} tasks={card.tasks} orderBy={card.orderBy} /> ); }); return ( <Col className={"lane"} md={4} xs={4}> <h2>{this.props.label}</h2> {cards} </Col> ); } } |
L21-L32でCardコンポーネントのDOMツリーを生成して、L37で割り当てています。
先ほども述べたとおり、現時点ではカンバンのステータスはTodo、WIP、Doneの3つを想定しているので、列を3分割します。CSSを書くのをサボるためにBootstrapを使いたいので、react-bootstrapをパッケージに加えます。
また、L24でkeyを設定しています。これを設定しないと、デベロッパーツールなどで警告が出ます。VirtualDOMから実際のDOMへの変更を最小限にするために大切な設定です。一意である必要があるので、idを指定します。
1 |
npm install --save react-bootstrap |
Card
続いて、Cardコンポーネントを作っていきます。
カード名と説明、そして複数のタスクを持つ単純なコンポーネントです。
例によってデザインを考えるのが面倒なので、BootstrapのPanelコンポーネントを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import React from 'react'; import { Panel } from 'react-bootstrap'; import Tasks from './Tasks'; export default class Card extends React.Component { render() { return ( <Panel header={this.props.label}> <strong>{this.props.description}</strong> <Tasks cardId={this.props.id} tasks={this.props.tasks}/> </Panel> ); } } |
特に目新しいところはないですね。
Tasksコンポーネントに、Cardが持つタスクを受け渡しています。
Tasks
続いてTasksコンポーネントです。
タスクというからには、チェックボックスか何かで完了したかをチェックするチェックボックスが欲しいですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import React from 'react'; import { Checkbox } from 'react-bootstrap'; export default class Tasks extends React.Component { // orderByの昇順にソート compare(a, b) { let comparison = 0; if (a.orderBy > b.orderBy) { comparison = 1; } else { comparison = -1; } return comparison; } render() { const tasks = this.props.tasks.sort(this.compare).map(task => ( <Checkbox key={task.id} checked={task.isDone} readOnly> {task.label} </Checkbox> )); return ( <div className="tasks"> {tasks} </div> ); } } |
L21、やっぱり楽したいのでBootstrapのCheckboxコンポーネントを使います。
trueで固定ならば、checkedをマークするだけでOKです。今回はisDoneの値によって表示を変えたいため、値を設定します。
CSSの調整
最後に、見栄えを調整するためにCSSを少しだけ書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
html, body, #root, .app { height: 100%; } .lane h2 { text-align: center; } .lane { height: 100%; } .lane:first-child { border-right: dashed 1px lightgray; } .lane:last-child { border-left: dashed 1px lightgray; } .tasks { text-align: left; } |
Laneの高さをブラウザの高さいっぱいに揃えたいので、その親となっている要素のheightも全て100%にしています。
他にいい方法あるんでしょうかね?この辺りはあまり詳しくないのでとりあえず知っている方法で。
画面の確認
ここまで作った画面を確認していきます。
なお、create-react-appで自動生成された使わないファイルは削除しているため、現時点でのファイルツリーは以下のようになっています。
Reactコンポーネントのpropsを使って、静的な値の受け渡すことで、なんとなくカンバンっぽい画面ができました。
今回は値しか受け渡していませんが、当然関数を渡すことも可能です。
次回は、stateを使って動的な値の受け渡しを行なっていきます。
現時点でのソースコードは、Githubに置いておきます。