はじめに
Fullstack React: 30 Days of React の意訳です。
ちょいちょい読んでるのでせっかくなので訳そうと思った次第。飽きるまで続きます。かなり意訳です。
本編
Driving Components Through Data
Day4までのデータがハードコーディングされている状態ではアプリはまったく役にたちません。Day5では、外部データを使ってデータドリブンなコンポーネントを作っていきます。
ここまで最初をコンポーネントを作り、それら親子関係を作って整理しましたが、今までのコンポーネントはデータを読み込んでいません(要はハードコード)。
Going data-driven
Day4では、ヘッダーとアクティビティを持つタイムラインのコンポーネントを作りました。
Day4では、最終的に、静的JSXを使って、それぞれ別の3つのコンポーネントに分けタイムラインを構築しました。しかし、このままでデータの更新ができず不便ですね。
では、Reactコンポーネントにデータを渡す方法を見てみましょう。まずは <Header />
コンポーネントからです。このコンポーネントは一見使いまわしが効く良いコンポーネントのようですが、どの場面でも Timeline
というタイトルが表示されてしまうのは困ります。
Reactを使ってタイトルを設定する方法を見てみましょう。
Introducing props
Reactでは、HTMLのアトリビュート(imgのsrcとかみたいなの)と同様の記法で、コンポーネントにデータを渡すことができます。Reactにおける prop
はimgタグのsrcと同様なものだと考られます。
渡したプロパティは、コンポーネント内で this.props
でアクセスすることができます。
<Header />
コンポーネントを再度思い出してください。
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="fa fa-more"></div>
<span className="title">Timeline</span>
<input
type="text"
className="searchInput"
placeholder="Search ..." />
<div className="fa fa-search searchIcon"></div>
</div>
)
}
}
このコンポーネントを親コンポーネントである App
コンポーネント内で使うときは下記ようにします。
<Header />
このコンポーネントにタイトルを更新するために、 title
という prop
を渡してみましょう。
<Header title="Timeline" />
コンポーネント内部では、 {this.props.title}
でデータにアクセスできます。これで静的に書かれていた部分をプロパティを渡すことで動的にすることができました。
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="fa fa-more"></div>
<span className="title">
{this.props.title}
</span>
<input
type="text"
className="searchInput"
placeholder="Search ..." />
<div className="fa fa-search searchIcon"></div>
</div>
)
}
}
これで文字列を渡して自由にタイトルを変更できるようになりました。例えば以下のように書いてみましょう。
<Header title="Timeline" />
<Header title="Profile" />
<Header title="Settings" />
<Header title="Chat" />
4つの <Header />
コンポーネントは以下のように表示されるはずです。
これのプロパティには、文字列以外にも、数字やオブジェクト、関数でさえ渡すことができます。これらについてはのちのち詳しくみていきます。
Contents
コンポーネントも同様にプロパティを使って書き換えてみましょう。Day4の例は以下のようでした。
class Content extends React.Component {
render() {
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
<div className="item">
<div className="avatar">
<img src="http://www.croop.cl/UI/twitter/images/doug.jpg" />
Doug
</div>
<span className="time">
An hour ago
</span>
<p>Ate lunch</p>
<div className="commentCount">
2
</div>
</div>
</div>
)
}
}
以下のものをプロパティを使って受け渡すようにしましょう。
- ユーザーのアバター画像(URL)
- アクティビティのタイムスタンプ
- アクティビティアイテムのテキスト
- コメント数
以下のようなアクティビティアイテムのオブジェクトがあるとします。日時、テキスト、userのオブジェクト、コメントのオブジェクト配列などを持ちます。
{
timestamp: new Date().getTime(),
text: "Ate lunch",
user: {
id: 1,
name: 'Nate',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [
{ from: 'Ari', text: 'Me too!' }
]
}
<Header />
コンポーネントのときと同じようにプロパティを使ってこのデータオブジェクトを受け渡して表示してみましょう。
class Content extends React.Component {
render() {
const {activity} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
<div className="item">
<div className="avatar">
<img src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
</div>
)
}
}
ES6のdestructuringと呼ばれる記法を使っています。以下の2行は透過です。
// these lines do the same thing
const activity = this.props.activity;
const {activity} = this.props;
これでコンテンツ部分のコンポーネントにプロパティを渡してつかうことができます。
<Content activity={moment1} />
すばらしい!これでデータを渡して表示することができるようになりました。しかしこれでは複数のアクティビティを表示するために、複数のコンポーネントを書かないといけないと思ったかもしれません。その解決策として、オブジェクトの配列をコンポーネントに渡すことができます。
const activities = [
{
timestamp: new Date().getTime(),
text: "Ate lunch",
user: {
id: 1, name: 'Nate',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [{ from: 'Ari', text: 'Me too!' }]
},
{
timestamp: new Date().getTime(),
text: "Woke up early for a beautiful run",
user: {
id: 2, name: 'Ari',
avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
},
comments: [{ from: 'Nate', text: 'I am so jealous' }]
},
]
<Content activities={activities} />
これでビューを更新してもなにも表示されません。正しく動作させるためにはコンポーネントに対してちょっとした更新が必要です。JSXはJavascriptなのでJavascriptの関数を利用することができます。
map
関数をつかってすべてのアクティビティを表示してみましょう。
class Content extends React.Component {
render() {
const {activities} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
{activities.map((activity) => {
return (
<div className="item">
<div className="avatar">
<img src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
);
})}
</div>
)
}
}
これで複数のアクティビティを渡すことができるようになりました。しかし、このコンポーネントは比較的複雑になりすぎています(要はmapの内部のコンテンツが多いので可読性が悪い)。これはReact風ではありません。
ActivityItem
ここでアクティビティアイテムを導入します。これによって、複雑さを緩和して、責任範囲を明確化し、テスタビリティを向上させることができます。
class Content extends React.Component {
render() {
const {activities} = this.props; // ES6 destructuring
return (
<div className="content">
<div className="line"></div>
{/* Timeline item */}
{activities.map((activity) => (
<ActivityItem
activity={activity} />
))}
</div>
)
}
}
これで、理解しやすいだけでなく、より簡単に両方のコンポーネントをテストできるようになりました。新しいActivityItemコンポーネントは、Activityをコピペするだけで作れますね。
class ActivityItem extends React.Component {
render() {
const {activity} = this.props; // ES6 destructuring
return (
<div className="item">
<div className="avatar">
<img src={activity.user.avatar} />
{activity.user.name}
</div>
<span className="time">
{activity.timestamp}
</span>
<p>{activity.text}</p>
<div className="commentCount">
{activity.comments.length}
</div>
</div>
)
}
}
ここまででコンポーネントをデータ駆動させる方法を学びました。Day6からはステートフルなコンポーネントを作っていきます。