本稿では、Reactで簡単なリストをつくってみます。コンポーネントはクラスで定め、複数データをリストにするというサンプルです。また、gumi Inc. Advent Calendar 2018の12月2日「ReactのdangerouslySetInnerHTML使ってみた」を受けて、マークダウンのテキストをライブラリで変換してページに差し込んでみます。
See the Pen React: Formatting text with markdown by Fumio Nonaka (@FumioNonaka) on CodePen.
create-react-appでReactのひな形アプリケーションをつくる
まず、Reactアプリケーションのジェネレータcreate-react-app
で、my-app
という名前のひな形アプリケーションをつくります。create-react-app
のインストールやひな形のつくりかたについては、「React: Visual Studio Codeで開発環境を整える」をお読みください。アプリケーション名のフォルダがつくられ、依存関係を含めた必要なファイルがつぎのように納められます。
コメントリストをつくる
ひな形のsrc/App.js
をつぎのように書き替えましょう。3つのコンポーネントをクラスで定めています。いずれのクラスも、備えているのはrender()
メソッドだけです。メソッドの戻り値が、HTMLページに差し込まれるテンプレートになります。テンプレートの要素に属性のかたちで与えた値を取り出すのがプロパティprops
です。各コンポーネントのコードは少ないので、モジュール分けせずひとつのJavaScript(JS)ファイルとします。
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="comment-box">
<h1>世界の金言</h1>
<CommentList />
</div>
);
}
}
class CommentList extends Component {
render() {
return (
<div>
<Comment author="ヘンリー・キッシンジャー">チャンスは__貯金__できない。</Comment>
<Comment author="マーク・トウェイン">禁煙なんてたやすい。私は*何千回*もやった。</Comment>
</div>
);
}
}
class Comment extends Component {
render() {
return (
<div>
<h2>
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
}
export default App;
ブラウザでアプリケーションのページを開くと、テンプレートにしたがって要素の構造がつくられ、コメントのリストとして表示されます。
マークダウンのライブラリを使う
コメントのテキストにはアスタリスク(*
)やアンダースコア(_
)が入っています。これらをマークダウンで表示しようということです。ライブラリとしてはremarkableを使うことにします。README.md
にしたがって、npm
でインストールしてください。
$ npm install remarkable --save
Remarkable()
コンストラクタでつくったインスタンスにrender()
メソッドでマークダウンテキストを渡せば、HTMLのフォーマットに変換されたテキストが返されます。コンポーネント(Comment
)をつぎのように書き替えましょう。
import Remarkable from 'remarkable';
class Comment extends Component {
markDown = new Remarkable();
render() {
return (
<div>
<span>
{this.markDown.render(this.props.children.toString())}
</span>
</div>
);
}
}
ただし、このままではタグがテキストとして示されてしまいます。
dangerouslySetInnerHTMLプロパティによりHTMLのコードを差し込む
生のHTMLコードが差し込めてしまうと、「クロスサイトスクリプティング」(XSS)による攻撃を受けるかもしれません(「クロスサイトスクリプティング対策 ホンキのキホン」参照)。そのため、ReactはHTMLのタグは、そのままでは加えられないようにしたのです。
テキストをHTMLとして差し込むためには、dangerouslySetInnerHTML
プロパティを用いなければなりません。与えるのはオブジェクトで、プロパティ__html
にHTMLコードを値として定めます(「ReactのdangerouslySetInnerHTML使ってみた」参照)。コンポーネント(Comment
)はさらにつぎのように書き替えましょう。こうすれば、要素にはRemarkableのrender()
メソッドから返されたマークダウンテキストが、HTMLとして描かれます。
class Comment extends Component {
rawMarkup() {
const markDown = new Remarkable();
const rawMarkup = markDown.render(this.props.children.toString());
return { __html: rawMarkup };
}
render() {
return (
<div>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
}
配列データからテキストを取り出して差し込む
コメントとして表示するデータは、つぎのように配列にしてアプリケーション(App
)のプロパティ(data
)にもたせましょう。それを子コンポーネント(CommentList
)の属性に与えます。そのデータは、Array.map()
メソッドで取り出され、さらにその子のコンポーネント(Comment
)のテンプレートがつくられるという流れです。アプリケーションのページの見た目は変わりません。
class App extends Component {
data = [
{id: 1, author: "ヘンリー・キッシンジャー", text: "チャンスは__貯金__できない。"},
{id: 2, author: "マーク・トウェイン", text: "禁煙なんてたやすい。私は*何千回*もやった。"}
];
render() {
return (
<div className="comment-box">
<h1>世界の金言</h1>
<CommentList data={this.data} />
</div>
);
}
}
class CommentList extends Component {
render() {
const commentNodes = this.props.data.map((comment) => {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div>
{commentNodes}
</div>
);
}
}
ここまで書き終えたsrc/App.js
のコードは、以下にまとめたとおりです。冒頭のCodePenのサンプルでも中身はお確かめいただけます。ただし、ライブラリの読み込み方とその参照の仕方が、create-react-app
でつくったアプリケーションと少し違いますので、その点だけお気をつけください。
import React, { Component } from 'react';
import Remarkable from 'remarkable';
import './App.css';
class App extends Component {
data = [
{id: 1, author: "ヘンリー・キッシンジャー", text: "チャンスは__貯金__できない。"},
{id: 2, author: "マーク・トウェイン", text: "禁煙なんてたやすい。私は*何千回*もやった。"}
];
render() {
return (
<div className="comment-box">
<h1>世界の金言</h1>
<CommentList data={this.data} />
</div>
);
}
}
class CommentList extends Component {
render() {
const commentNodes = this.props.data.map((comment) => {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div>
{commentNodes}
</div>
);
}
}
class Comment extends Component {
rawMarkup() {
const markDown = new Remarkable();
const rawMarkup = markDown.render(this.props.children.toString());
return { __html: rawMarkup };
}
render() {
return (
<div>
<h2>
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
}
export default App;