Reactの続きです。前回はreact-helmetでheadタグの中身を動的に打ち替えました。今回はHTMLのテキスト部分を外部JSONデータからJSXに読み込みます。インラインのタグを認識するためにdangerouslySetInnerHTML
を、JSX内でループするためにmap()
を使いました。それではいきましょう!
【目次】
- 前回のおさらい
- JSONファイルを作成(テキスト部分)
- Appコンポーネントでheadタグのテキスト読み込み
- HeaderコンポーネントにJSONデータを読み込む
- FooterコンポーネントにJSONデータを読み込む
- MainコンポーネントにJSONデータを読み込む
- Innerコンポーネントを作成
- MainコンポーネントでInnerコンポーネントを読み込み
- InnerコンポーネントにJSONデータを読み込む(map()でループ)
- おまけ:text型式だとインラインのタグが認識されない
- 最後に
※参考:前回記事
【React】react-helmetでheadタグの中身を動的に打ち替える(Reactとメタ言語の比較-2) - クモのようにコツコツと
※参考:Reactを習得するためにやったことまとめ
qiita.com
前回のおさらい
Appコンポーネントを作成し、そこにreact-helmetをインポート。headタグの中身を動的にに打ち替えた!
※参考:【React】react-helmetでheadタグの中身を動的に打ち替える(Reactとメタ言語の比較-2) - クモのようにコツコツと
このheadタグのtitle、descriptionはheaderタグのh1、pタグとも共通するテキスト。そのため、以前、メタ言語スターターターキットのEJSでやったように外部JSONファイルからテキストを読み込みたい。
※参考:【メタ言語】フロントエンド開発スターターキットを作った(EJS、Sass(SCSS)、TypeScript) - クモのようにコツコツと
JSONファイルを作成(テキスト部分)
以前、メタ言語スターターキットのEJSでやったように、テキスト部分を外部のjsonファイルにする。
ここで疑問に思ったのは、jsonファイルは「public」フォルダか「src」フォルダ、どちらに置くべきか。
You may create subdirectories inside src. For faster rebuilds, only files inside src are processed by webpack. You need to put any JS and CSS files inside src, otherwise webpack won’t see th
JSファイルとCSSファイルをsrc内に配置する必要があります。そうしないと、webpackはそれらを認識しません。
webpackでバンドルして欲しいファイルは「public」フォルダではなく「src」でいいんだな。
※参考:Folder Structure | Create React App
「src」フォルダに「data.json」を作成。テキスト部分のデータを入れる。
{ "data": { "header": { "title":"Reactとメタ言語の比較", "text": "以前作ったメタ言語スターターキットの内容をReact環境で再現してみる" }, "main": { "title":"タイトルです", "text": "テキストです。テキストです。テキストですったらテキストです。" }, "inner": [ { "title": "CSS(文字色)", "text": "CSSでタイトルの文字色変更。" }, { "title":"JS(文字列)", "text": "JSでテキストの文字列追加「<span class='inner__text--hello'></span>」" } ], "footer": { "text":"©️ react-from-meta-lang" } } }
header
やfooter
などを少し打ち替えたが、基本的にはEJSの時と同じ内容。
Appコンポーネントでheadタグのテキスト読み込み
前回、react-helmetでtitleとdescriptionを設定したAppコンポーネント。ここに先ほど作成した「data.json」のテキストデータを読み込む。
まず「data.json」をインポート
import React from 'react'; import {Helmet} from "react-helmet"; import Header from './Header'; import Main from './Main'; import Footer from './Footer'; import Data from './data.json'; // 追加
変数titleText
、descriptionText
を設定
const titleText = Data.data.header.title; const descriptionText = Data.data.header.text;
前回はここにテキストを直で書いたが、data.json
のheader
キーのテキストを取得する。
Helmet
の設定部分。前回と変更なし
function App() { return ( <div className="App"> <Helmet title={ titleText } meta={[ { name: 'description', content: descriptionText } ]} /> <Header /> <Main /> <Footer /> </div> ); }
Appコンポーネントをエクスポート(変更なし)
export default App;
ローカル環境でアプリ起動
$ npm start
この調子で他のコンポーネントのJSX部分にもJSONデータを読み込む。
HeaderコンポーネントにJSONデータを読み込む
続いてHeaderコンポーネント(Header.tsx)。ここのテキストはhead
タグと共通
「data.json」をインポート
import React from 'react'; import Data from './data.json'; // 追加
変数title
、text
を設定
const title = Data.data.header.title; const text = Data.data.header.text;
パスは先ほどのheadタグと共通
JSXに読み込み
function Header() { return ( <header className="header"> <h1 className="header__title">{ title }</h1> <p className="header__text" dangerouslySetInnerHTML={{ __html: text }}></p> </header> ); }
h1
タグにtitle
を読み込むp
タグににtext
を読み込む(dangerouslySetInnerHTML
)
詳細は後述するが本文のp
タグにはインラインで入れ子のタグが入り得る。テキストとして読み込むとそこがタグにならなかった。
そのため、dangerouslySetInnerHTML
を使ってinnerHTML
形式で読み込むことにした。
※参考:DOM 要素 – React
dangerously
ってすごい名前だがw下記の理由のため
dangerouslySetInnerHTML は、ブラウザ DOM における innerHTML の React での代替です。一般に、コードから HTML を設定することは、誤ってあなたのユーザをクロスサイトスクリプティング (XSS) 攻撃に晒してしまいやすいため、危険です。そのため、React では直接 HTML を設定することはできますが、それは危険であることを自覚するために dangerouslySetInnerHTML と入力し __html というキーを持つオブジェクトを渡す必要があります。
御意。
Headerコンポーネントをエクスポート(変更なし)
export default Header;
FooterコンポーネントにJSONデータを読み込む
本文のMainコンポーネントはちょっとややこしいので、先にシンプルなFooterコンポーネント(Footer.tsx)から。
「data.json」をインポート
import React from 'react'; import Data from './data.json'; // 追加
変数text
でJSONデータからフッター部分のテキストを取得
const text = Data.data.footer.text;
text
をp
タグで読み込む(innerHTML
形式)
function Footer() { return ( <footer className="footer"> <p className="footer__text" dangerouslySetInnerHTML={{ __html: text }}></p> </footer> ); }
Footerコンポーネントをエクスポート(変更なし)
export default Footer;
Headerコンポーネントと同じ要領〜♪
ここまでやってブラウザを確認。
問題なさそう!見た目は変わらないがテキストはJSONデータが読まれている。
MainコンポーネントにJSONデータを読み込む
次は本文部分であるMainコンポーネント(Main.tsx)
「data.json」をインポート
import React from 'react'; import './hello.js'; import Data from './data.json'; // 追加
変数title
、text
でJSONデータを取得
const title = Data.data.main.title; const text = Data.data.main.text;
h1
タグとリード文のテキストをJSONデータから取得
function Main() { return ( <main> <section className="main"> <h1 className="main__title">{ title }</h1> <p className="main__title" dangerouslySetInnerHTML={{ __html: text }}></p> <section className="inner"> <h2 className="inner__title">CSS(文字色)</h2> <p className="inner__text">CSSでタイトルの文字色変更。</p> </section> <section className="inner"> <h2 className="inner__title">JS(文字列)</h2> <p className="inner__text">JSでテキストの文字列追加→「<span className='inner__text--hello'></span>」</p> </section> </section> </main> ); }
h1
タグにtitle
を読み込む.main__title
にtext
を読み込む
h2
タグ階層のsection
タグはまだ直のテキストのまま。
Mainコンポーネントをエクスポート(変更なし)
export default Main;
Innerコンポーネントを作成
メタ言語スターターキットのEJSの時はh2
階層のsection
タグは_inner
モジュールに切り出していた。
<main> <section class="main"> <h1 class="main__title"><%- main.title %></h1> <p class="main__text"><%- main.text %></p> <%- include('_inner', {inner: inner}); %> </section> </main>
Reactでも同じことをしたい。まずInnerコンポーネント(Inner.tsx)を作成する。
Mainコンポーネントで読み込んでた「hello.js」をここで読み込む
import React from 'react'; import './hello';
Innerコンポーネント作成
function Inner() { return ( <div className="inner"> <section className="inner__sec"> <h2 className="inner__title">CSS(文字色)</h2> <p className="inner__text">CSSでタイトルの文字色変更。</p> </section> <section className="inner__sec"> <h2 className="inner__title">JS(文字列)</h2> <p className="inner__text">JSでテキストの文字列追加→「<span className='inner__text--hello'></span>」</p> </section> </div> ); }
Mainコンポーネントのh2
タグ階層部分のsection
をここに入れる。
Innerコンポーネントをエクスポート
export default Inner;
MainコンポーネントでInnerコンポーネントを読み込み
Mainコンポーネント(Main.tsx)でInnerコンポーネントを読み込む
Innerコンポーネントをインポート
import React from 'react'; import Inner from './Inner'; import Data from './data.json';
また、「hello.js」はここでは不要になったので削除する。
先ほどのh2
タグ階層のsection
部分にInnerコンポーネントを配置する。
function Main() { return ( <main> <section className="main"> <h1 className="main__title">{ title }</h1> <p className="main__title" dangerouslySetInnerHTML={{ __html: text }}></p> <Inner /> </section> </main> ); }
スリムになった♪
ブラウザ確認
問題なさそう!
InnerコンポーネントにJSONデータを読み込む(map()でループ)
メタ言語スターターキットのEJSではh2
タグ階層のsection
タグを「_innerモジュール」に分けてfor文でループしていた。
<% for (var i = 0; i < inner.length; i++) { %> <section class="inner"> <h2 class="inner__title"><%- inner[i].title %></h2> <p class="inner__text"><%- inner[i].text %></p> </section> <% } %>
※参考:【メタ言語】フロントエンド開発スターターキットを作った(EJS、Sass(SCSS)、TypeScript) - クモのようにコツコツと
Reactでも同じことをしたい。
まず「data.json」をインポート
import React from 'react'; import './hello'; import Data from './data.json';
変数innerJson
で本文を取得
const innerJson = Data.data.inner;
inner
キーまでを取得するので値は配列になる。
innerJson
をmap()
でループする
function Inner() { return ( <div className="inner"> {innerJson.map((innerJson, index) => <section key={ index }> <h2 className="inner__title">{ innerJson.title }</h2> <p className="inner__text" dangerouslySetInnerHTML={{ __html: innerJson.text }}></p> </section> )} </div> ); }
以前こちらでReactのいろいろなループ方法を試したが、やはりmap()
を使ったループはJSXの中に直接書けるのがメリットに感じた!
※参考:【React】ループの書き方(for文エラー回避、配列、map()) - クモのようにコツコツと
こちらの記事を書かれたJJさんからいただいたリプ
筆者です、jsx内の{}はjsの評価をしてくれますがあくまでexpression(式)の評価のためstatement(文)はsyntax errorになると思います。そのためforを書く場合は{}の外で行いその結果の変数を展開する書き方になり少し煩雑になります。
— JJ (@konojunya) 2021年2月7日
以上の理由から頻出するのはmapなどが多いと僕は推測してます👀
ブラウザ確認
よっしゃ!問題なさそう!
おまけ:text型式だとインラインのタグが認識されない
ちなみにp
をinnerHTML
型式ではなくテキスト型式で読み込むと…
function Inner() { return ( <div className="inner"> {innerJson.map((innerJson, index) => <section key={ index }> <h2 className="inner__title">{ innerJson.title }</h2> <p className="inner__text">{ innerJson.text }</p> </section> )} </div> ); }
「Hello.js」でエラーが起こる。
TypeError: Cannot set property 'innerHTML' of null
プロパティにinnerHTML
が設定できませんと。プロパティ(セレクター)が認識されていない。
「hello.js」インポートを無効にしてみると…
// import './hello';
おっと、、タグがテキストとして表示された!
なるほど、これは認識されないわけだ。
ということでインラインのタグが入り得る本文部分はdangerouslySetInnerHTML
でinnerHTML
型式で統一した。
※参考:DOM 要素 – React
ソース(GitHub)※今回のコミットまで
※参考:GitHub - ryo-i/react-from-meta-lang at b5806001337be58355f4843de94666397ab52857
プレビュー(GitHub Pages)
※参考:Reactとメタ言語の比較
最後に
ということで、メタ言語スターターキットのEJSの時と同様にJSONデータからテキストを読み込むことができました。webpackでバンドルするファイルは「public」ではなく「src」フォルダでいいことがわかったのでjsonデータは「src」フォルダに追加しました。
また、本文のインラインのタグはtext型式だと認識されなかったため、dangerouslySetInnerHTML
でinnerHTML
型式で読み込む必要があるとわかりました。
h2
階層のsection
もEJSの時と同様にループすることができました。JSX内ではfor文を直接書くとエラーになるたため、JSX内で書けるmap()
を使うのがやはり楽に感じました。
次回はメタ言語スターターキットのSass(SCSS)部分をCSS in JSに置き換えてみたく思います。スタイルがコンポーネントに直接書いていくのでclass名がどうなるか興味津々です♪
それではまた!
※参考:Reactを習得するためにやったことまとめ
qiita.com