Step3: Reactコンポーネントを作ろう!
このステップではチュートリアルに必要なReactコンポーネントを作成します。
Reactコンポーネント
Reactはコンポーネントという形でUI(User Interface)とロジックを1つのJSファイルにまとめて管理します。このチュートリアルではコンポーネントを再利用しやすいように、1ファイル1コンポーネントで作成していきます。
今回作成するコンポーネントとコンポーネント間の親子関係は以下のようになります。
<AppLayout>
<Header />
<MemoList>
<MemoItem />
<MemoItem />
<MemoItem />
...
</MemoList>
</AppLayout>
それでは、各コンポーネントを一つ一つ作成していきましょう。
AppLayout コンポーネント(レイアウト)
AppLayoutは複数のコンポーネントを管理したり配置を決める役割を持つコンポーネントです。このチュートリアルではレイアウトと呼ぶことにします。
Meteorアプリディレクトリ内に、imports/ui/layouts
ディレクトリを作成し、その中に AppLayout.js
を作成します。
$ mkdir -p imports/ui/layouts
$ touch imports/ui/layouts/AppLayout.js
imports/ui/layouts/AppLayout.js
import React from 'react';
export default class AppLayout extends React.Component {
render() {
return (
<div className="container">
<h1>Hello, Meteor React World!</h1>
<h2>App Layout</h2>
</div>
);
}
}
そして client/main.js
を以下のように書き換えます。
client/main.js
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { render } from 'react-dom';
import AppLayout from '../imports/ui/layouts/AppLayout';
Meteor.startup(() => {
render(
<AppLayout />,
document.getElementById('render-root')
);
});
これでAppLayoutが読み込まれるようになりました。
ポイント
- ES2015のclass構文を使って
React.Component
を継承したオブジェクトを作成する export
で別ファイルからimport
できるようにする- client/main.js の中で
AppLayout.js
をimport
するexport default
の場合は、import
時に波括弧{ }
が不要import
時、ファイル名の拡張子は省略可能
- Meteor 1.3からはimportsディレクトリ内にファイルを置くことで、Meteorの自動読み込みの対象外となり、必要な時に読み込まれるようになる(遅延読み込み)
Header コンポーネント
続いてページのヘッダー部分のHeaderコンポーネントを作成します。
imports/ui/components
ディレクトリを作成し、その中に Header.js
ファイルを作成します。
$ mkdir -p imports/ui/components
$ touch imports/ui/components/Header.js
imports/ui/components/Header.js
import React from 'react';
export default class Header extends React.Component {
render() {
return (
<header className="header">
<h1>Simple Memo</h1>
</header>
);
}
}
そして AppLayout 内で Header コンポーネントを読み込みます。
imports/ui/layouts/AppLayout.js
import React from 'react';
import Header from '../components/Header';
export default class AppLayout extends React.Component {
render() {
return (
<div className="container">
<Header />
</div>
);
}
}
次のように表示されます。
MemoItem / MemoList コンポーネント
最後にメモ自身である MemoItemコンポーネントと、メモ一覧を表すMemoListコンポーネントを作成します。
それぞれ imports/ui/componets
ディレクトリ内に作成していきます。
$ touch imports/ui/components/MemoItem.js
$ touch imports/ui/components/MemoList.js
imports/ui/components/MemoItem.js
import React from 'react';
export default class MemoItem extends React.Component {
render() {
const { memo } = this.props;
return (
<div className="memo-item">
<textarea className="textarea" defaultValue={memo.content} />
</div>
);
}
}
MemoItem.propTypes = {
memo: React.PropTypes.object.isRequired,
};
imports/ui/components/MemoList.js
import React from 'react';
import MemoItem from './MemoItem';
export default class MemoList extends React.Component {
render() {
const { memos } = this.props;
return (
<div className="memo-list">
{memos.map(memo => (
<MemoItem key={memo._id} memo={memo} />
))}
</div>
);
}
}
MemoList.propTypes = {
memos: React.PropTypes.array.isRequired,
};
AppLayoutにMemoListコンポーネントを追加します。
imports/ui/layouts/AppLayout.js
import React from 'react';
import Header from '../components/Header';
import MemoList from '../components/MemoList';
export default class AppLayout extends React.Component {
render() {
const { memos } = this.props;
return (
<div className="container">
<Header />
<MemoList memos={memos} />
</div>
);
}
}
AppLayout.propTypes = {
memos: React.PropTypes.array.isRequired,
};
最後にAppLayoutを呼び出すときにメモデータを渡すようにします。
client/main.js
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { render } from 'react-dom';
import AppLayout from '../imports/ui/layouts/AppLayout';
// sample data
const memos = [
{_id: 'memo1', content: 'This is sample data 1'},
{_id: 'memo2', content: 'This is sample data 2'},
{_id: 'memo3', content: 'This is sample data 3'},
{_id: 'memo4', content: 'This is sample data 4'},
{_id: 'memo5', content: 'This is sample data 5'},
];
Meteor.startup(() => {
render(
<AppLayout memos={memos} />,
document.getElementById('render-root')
);
});
ポイント
- Reactでは親コンポーネントから子コンポーネントに値を渡すことができ、子コンポーネント内で
this.props
を参照することで親から渡された値を取得できる - 親コンポーネントでデータを管理し、子コンポーネントに必要なデータを渡すようにすると、コンポーネントの保守性・再利用性を高めることができる
static get propTypes()
で親コンポーネントから受け取った props のバリデーションを行っている(Reactの機能
コードを書き換えると、以下のように表示されます。
これで必要なコンポーネントが作成されました。
スタイルの適用
アプリの見た目を整えるため、スタイルを適用します。
client/main.css
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background-color: #E8E8E8;
color: #4E4E4E;
font-weight: 100;
line-height: 1.4;
font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN","メイリオ", sans-serif;
}
.container {
padding: 10px;
}
.header {
margin-left: 10px;
}
.header h1 {
margin: 5px 0 10px
}
.add-button {
margin-left: 10px;
margin-bottom: 10px;
font-size: 20px;
padding: 5px 20px;
}
.memo-list {
width: 100%;
}
.memo-item {
display: inline-block;
position: relative;
background-color: #FEF3A1;
min-height: 200px;
width: 300px;
border: 1px solid rgba(200, 200, 200, 0.2);
box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.2);
border-radius: 2px;
padding: 5px 10px;
margin: 10px;
font-size: 20px;
}
@media screen and (max-width: 705px) {
.memo-item {
width: calc(100% - 45px);
}
}
.memo-item .remove-button {
display: none;
position: absolute;
top: 3px;
right: 5px;
font-size: 24px;
text-decoration: none;
}
.memo-item:hover {
box-shadow: 1px 1px 3px 3px rgba(0,0,0,0.1);
}
.memo-item:hover .remove-button {
display: block;
}
.memo-item .textarea {
width: 100%;
height: 100%;
min-height: 200px;
border: none;
background: none;
overflow: inherit;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
resize: none;
font: inherit;
}
.memo-item .textarea:focus {
border: none;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none; /* non-prefixed version, currently not supported by any browser */
}
.memo-item.is-color-yellow { background-color: #FEF3A1; }
/*.memo-item.is-color-blue { background-color: #42afe3; }*/
.memo-item.is-color-blue { background-color: #AFF4FE; }
/*.memo-item.is-color-green { background-color: #97cd76; }*/
.memo-item.is-color-green { background-color: #B4FDA5; }
.memo-item.is-color-orange { background-color: #f68b39; }
.memo-item.is-color-red { background-color: #ed8c83; }
/*.memo-item.is-color-turquoise { background-color: #1fc8db; }*/
.memo-item.is-color-gray { background-color: #aeb1b5; }
.memo-item.is-color-pink { background-color: #FEC7C8; }
.memo-item.is-color-white { background-color: #FEFEFE; }
スタイルを適用すると次のような表示になります。
メモアプリっぽくなりましたね!
次のステップに進みましょう。