関連記事
- Reactのスタート
- Reactのプロジェクト
- Reactで挨拶
- ReactでTrelloみたいなToDoリスト<1> props
- ReactでTrelloみたいなToDoリスト<2> state
- ReactでTrelloみたいなToDoリスト<3> prop-types
- ReactでTrelloみたいなToDoリスト<4> immutability-helper
stateについて
コンポーネント間データの受け渡しに使ったpropsは変更不可(immutable)だった。
変更可能(mutable)なstateについて学んでみよう。
Reactのコンポーネントはthis.stateを用いて、色んなデータを持ちられる。
this.stateは、this.setState()を呼び出して変えられる。
stateが変えたらトリガーが発動されて、該当コンポーネントとその子コンポーネントが再びレンダリングされる。
Reactは仮想DOMを使っているので、この作業が早い。
toggleされるカード
diff --git a/src/components/Card.js b/src/components/Card.js
index 61629cf..3905582 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -1,26 +1,38 @@
import React, { Component } from 'react';
import CheckList from './CheckList';
class Card extends Component {
+ constructor(...args) {
+ super(...args);
+ this.state = {
+ showDetails: false,
+ };
+ }
+
render() {
- return (
- <div className="card">
- <div className="card__title">{this.props.title}</div>
+ let cardDetails;
+ if (this.state.showDetails) {
+ cardDetails = (
<div className="card__details">
{this.props.description}
<CheckList cardId={this.props.id} tasks={this.props.tasks} />
</div>
+ );
+ }
+
+ return (
+ <div className="card">
+ <div
+ className="card__title"
+ onClick={() => this.setState({ showDetails: !this.state.showDetails })}
+ role="presentation"
+ >
+ {this.props.title}
+ </div>
+ {cardDetails}
</div>
);
}
}
本のサンプルではroleを指定していないが、この状態ではeslintエラーが出る。
src/components/Card.js|25 col 9 error| Static HTML elements with event handlers require a role. (jsx-a11y/no-static-element-interactions)
button、link、checkboxのような行動要素についてはroleを明記しないといけない。
詳細は下記のリンクで確認しよう。
HTMLのroleについて
イベントハンドラの作成
onClickというイベントについてインライン関数で作成したが、これを別途の関数で取り出してみよう。
diff --git a/src/components/Card.js b/src/components/Card.js
index 3905582..e13542e 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -9,6 +9,10 @@ class Card extends Component {
};
}
+ toggleDetails() {
+ this.setState({ showDetails: !this.state.showDetails });
+ }
+
render() {
let cardDetails;
if (this.state.showDetails) {
@@ -24,7 +28,7 @@ class Card extends Component {
<div className="card">
<div
className="card__title"
- onClick={() => this.setState({ showDetails: !this.state.showDetails })}
+ onClick={this.toggleDetails.bind(this)}
role="presentation"
>
{this.props.title}
this.toggleDetailsではなくthis.toggleDetails.bind(this)の理由
またeslintエラーが出る。
以前と同じくpropsに関するエラーは無視する。
しかし、エラーの中ではこんなエラーもあった。
src/components/Card.js|31 col 11 error| JSX props should not use .bind() (react/jsx-no-bind)
jsxのpropsではbindを使うな!とね。
これを直してみよう。
diff --git a/src/components/Card.js b/src/components/Card.js
index 3905582..5f891ac 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -7,6 +7,11 @@ class Card extends Component {
this.state = {
showDetails: false,
};
+ this.toggleDetails = this.toggleDetails.bind(this);
+ }
+
+ toggleDetails() {
+ this.setState({ showDetails: !this.state.showDetails });
}
render() {
@@ -24,7 +29,7 @@ class Card extends Component {
<div className="card">
<div
className="card__title"
- onClick={() => this.setState({ showDetails: !this.state.showDetails })}
+ onClick={this.toggleDetails}
role="presentation"
>
{this.props.title}
指摘された通りにjsxのpropsでbindを使わずにconstructorで使ったら、エラーが消えた。
なぜeslintではjsxのpropsにbindを使うとエラーになるのか?
カードが開いているかを確認
diff --git a/src/components/Card.js b/src/components/Card.js
index 5f891ac..8b3671e 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -28,7 +28,7 @@ class Card extends Component {
return (
<div className="card">
<div
- className="card__title"
+ className={this.state.showDetails ? 'card__title card__title--is-open' : 'card__title'}
onClick={this.toggleDetails}
role="presentation"
>
動的HTMLレンダリング
ReactはXSS攻撃を防ぐためにjsxでHTMLタグをレンダリングすることを禁止している。
しかし、HTMLレンダリングが必要な場合もある。
Markdown表記もそのような場合だ。
テストのためにcardsListのデータにMarkdown表記で入れて確認してみよう。
diff --git a/src/index.js b/src/index.js
index eeb27fe..c7f23ee 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,14 +8,14 @@ const cardsList = [
{
id: 1,
title: 'Read the Book',
- description: 'I should read the whole book',
+ description: 'I should read the **whole** book',
status: 'in-progress',
tasks: [],
},
{
id: 1,
title: 'Write some code',
- description: 'Code along with the samples in the book',
+ description: 'Code along with the samples in the book. The complete source can be found at [github](https://github.com/pro-react)',
status: 'todo',
tasks: [
{
Markdownを使うために関連パッケージを追加して、Cardコンポーネントから使うように修正する。
% yarn add marked
本のサンプルでは「import marked from 'marked'」で出ているがmarkedというmoduleがないよ!と怒られる。
「import Marked from 'marked'」の誤字なので修正しよう。
diff --git a/src/components/Card.js b/src/components/Card.js
index 8b3671e..48ad868 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import Marked from 'marked';
import CheckList from './CheckList';
class Card extends Component {
@@ -19,7 +20,7 @@ class Card extends Component {
if (this.state.showDetails) {
cardDetails = (
<div className="card__details">
- {this.props.description}
+ {Marked(this.props.description)}
<CheckList cardId={this.props.id} tasks={this.props.tasks} />
</div>
);
Reactはjsxの中でHTMLタグをレンダリングすることを禁止しているので、タグがテキストとして扱われる。
しかし、dangerouslySetInnerHTMLを使えば、HTMLタグのレンダリングが可能になる。
diff --git a/src/components/Card.js b/src/components/Card.js
index 8b3671e..4c2d3c1 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import Marked from 'marked';
import CheckList from './CheckList';
class Card extends Component {
@@ -19,7 +20,7 @@ class Card extends Component {
if (this.state.showDetails) {
cardDetails = (
<div className="card__details">
- {this.props.description}
+ <span dangerouslySetInnerHTML={{ __html: Marked(this.props.description) }} />
<CheckList cardId={this.props.id} tasks={this.props.tasks} />
</div>
);
インラインスタイルを使ってカードの色を指定
データに色を追加する。
diff --git a/src/index.js b/src/index.js
index eeb27fe..dd395b8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,14 +8,16 @@ const cardsList = [
{
id: 1,
title: 'Read the Book',
description: 'I should read the **whole** book',
+ color: '#BD8D31',
status: 'in-progress',
tasks: [],
},
{
id: 1,
title: 'Write some code',
description: 'Code along with the samples in the book',
+ color: '#3A7E28',
status: 'todo',
tasks: [
{
propsでCardコンポーネントに色も伝えるように修正する。
diff --git a/src/components/List.js b/src/components/List.js
index 238333e..a3bf1cd 100644
--- a/src/components/List.js
+++ b/src/components/List.js
@@ -8,6 +8,7 @@ class List extends Component {
id={card.id}
title={card.title}
description={card.description}
+ color={card.color}
tasks={card.tasks}
/>
);
インラインスタイルを持つオブジェクトを生成して、このオブジェクトをインラインで使うdivタグを生成する。
diff --git a/src/components/Card.js b/src/components/Card.js
index 4c2d3c1..5e30838 100644
--- a/src/components/Card.js
+++ b/src/components/Card.js
@@ -26,8 +26,19 @@ class Card extends Component {
);
}
+ const sideColor = {
+ position: 'absolute',
+ zIndex: -1,
+ top: 0,
+ bottom: 0,
+ left: 0,
+ width: 7,
+ backgroundColor: this.props.color
+ };
+
return (
<div className="card">
+ <div style={sideColor} />
<div
className={this.state.showDetails ? 'card__title card__title--is-open' : 'card__title'}
onClick={this.toggleDetails}
カードに入力フィールドを追加
diff --git a/src/components/CheckList.js b/src/components/CheckList.js
index 5ffe6ba..3b878a7 100644
--- a/src/components/CheckList.js
+++ b/src/components/CheckList.js
@@ -14,13 +14,14 @@ class CheckList extends Component {
return (
<div className="checklist">
<ul>{tasks}</ul>
+ <input
+ type="text"
+ className="checklist--add-task"
+ placeholder="Type then hit Enter to add a task"
+ />
</div>
);
}
}
export default CheckList;
Keys
ブラウザのdeveloper toolsで確認すると、Consoleで以下のようなWarningが表示される。
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `List`. See https://fb.me/react-warning-keys for more information.
arrayやiterator中の子ノードはuniqueなkey属性を持たなければならない。
識別子(uniqueなkey)があれば、特定のノードの探索が早くなる。
Reactにてレンダリングのコストは低くない。
そんなコストを少しでも減らすためには識別子が必要なのだ。
識別子を入れてみよう。
diff --git a/src/components/CheckList.js b/src/components/CheckList.js
index 3b878a7..991d734 100644
--- a/src/components/CheckList.js
+++ b/src/components/CheckList.js
@@ -4,10 +4,10 @@ import PropTypes from 'prop-types';
class CheckList extends Component {
render() {
const tasks = this.props.tasks.map((task) => (
- <li className="checklist__task">
+ <li key={task.id} className="checklist__task">
<input type="checkbox" defaultChecked={task.done} />
{task.name}{' '}
<span className="checklist__task--remove" />
</li>
));
diff --git a/src/components/List.js b/src/components/List.js
index a3bf1cd..38b60f9 100644
--- a/src/components/List.js
+++ b/src/components/List.js
@@ -5,6 +5,7 @@ class List extends Component {
render() {
const cards = this.props.cards.map((card) => {
return (<Card
+ key={card.id}
id={card.id}
title={card.title}
description={card.description}
developer toolsにてWarningが消えた。
よく使うarrayのコールバック関数
forEach
array.forEach(callback [,that])
array: 配列オブジェクト
callback: 個々の要素を処理するための関数
that: 関数callbackの中でthisが示すオブジェクト
戻り値: 個々の要素
map
array.map(callback [,that])
array: 配列オブジェクト
callback: 個々の要素を加工するための関数
that: 関数callbackの中でthisが示すオブジェクト
戻り値: 配列
some
array.some(callback [,that])
array: 配列オブジェクト
callback: 個々の要素を判定するための関数
that: 関数callbackの中でthisが示すオブジェクト
戻り値: 1つでも条件に合致した場合(=関数callbackが一度でもtrueを返した場合)はtrue、そうではない場合はfalse
filter
array.filter(callback [,that])
array: 配列オブジェクト
callback: 個々の要素を判定するための関数
that: 関数callbackの中でthisが示すオブジェクト
戻り値: 条件に合致した要素だけを取り出した配列