タイトルが強弁すぎますね、ごめんなさい😂
すすさんの記事のリリースツイートを見て「おっこれはワイもReact記事書くムーブかな???」となったので書いてみます。
本項のゴール
- Reactのコンポーネントの作り方を理解する
- propsを渡して動的にUIを作ってみる(Stateは気力があれば別記事で書こうと思います)
諸注意
間違ってる箇所とかバッドプラクティスがあるかもしれません
コメントで指摘していただければと思います。
そもそもReactってなに?
フロントのUIライブラリの話になるとVueと比較される事が多いので高機能なのを想像しがちですが
React自体、及びReact-DOMは HTMLのレンダリングをするだけのライブラリです。
そもそもReact自体はHTML周りの機能すら持ってません。その辺りは色々あってReact-DOMに分割されました。
この話についてはGoogleで検索するといくらでも出てくるので本稿では省略します。
HTMLをそのまま書いていくのとどう違う?
HTMLを直書きして document.querySelector('div#hoge').~~~
のようにJSでターゲットを指定して操作するの(いわゆるDOM操作)も悪くはないですが、UI部品が増えると似たようなコードが増え、非常にメンテナンスのしづらい煩雑なコードになっていきます。
またUIを小分けにせずHTMLに全てベタ書きすると
どこからがNavの部分でどこからがメインビューなのかわかりづらくなります。
これを解決するのがReactです。
Reactだと何が良いのか
Reactを使うことによって何がどう変わるのか、という話ですが結論的には以下のようになります。
- JSの中にHTMLを混ぜたような文が書ける(ビューに出す要素とJSで操作する部分を纏めて書ける)
- UIパーツをコンポーネントという単位で小分けしながら作れる
あんまり話が長くなっても面白くないので実際に作りましょう。
実際に作る-環境を建てる
注意事項です。
nodeとnpmはインストール済みである前提で解説します。
まだされていない方はnodenv等でインストールをしておいてください。
nodeのバージョンは偶数系が無難です。(Firebaseに興味があるとかでなければv10以降がおすすめ)
ES6の構文については先に理解をしておいてください。
主に const
let
()=>
import, export
が多用されます。これらが何なのか知らない方はMDNドキュメントを先に参照していただくか、Googleで検索していただくことをおすすめします。
$ npx create-react-app sample
このコマンドを叩くと ./sample/
の中にReactのアプリの開発に必要なものがすべてインストールされます。あるオプションを入れるとTypeScriptを使うようにすることもできますが今回は省略します。
$ cd sample/
$ npm start
package.json
のあるディレクトリで npm start
を叩くことで開発サーバが立ちあがり、自動的にブラウザにlocalhostのビューが出てきます。こんなの。
とりあえずこれで問題なく動くことは確認できました。
このテンプレートをそのまま加工してもいいですが、とりあえず今回はネイティブな状態に戻すため src/
にあるものを全て消して、index.js
を新規作成してください。
おそらくエラーが出ますが一旦無視で構いません。
実際に作る-HTMLをレンダリングしてみる
先程作ったindex.js
に追記してみてください。
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return (
<div>
<h1>Hello Mr.React!</h1>
</div>
);
};
ReactDOM.render(<App />, document.querySelector('div#root'));
順を追って説明します。
まず2行目まででReact、そしてReact-DOMを呼び出して使えるようにします。
ちなみに2行目のReact-DOMは使わない限りはimportしなくて大丈夫です。
const App = () => {
return (
<div>
<h1>Hello Mr.React!</h1>
</div>
);
};
Reactではコンポーネントを作る際に 大文字から始まる関数オブジェクト を用いてオブジェクトの体型を表現します。
大文字からという一文は絶対守ってください。小文字だと蹴られます。
また、Reactアプリケーションの開発においてはよくHTML(XML)をそのまま書けるようにJSXという特殊なフォーマットを使います。
これはbabelという変換器を使ってブラウザも理解できるJSの構文に変換をしています。
(一般的にJSXを使う場合は拡張子を .jsx
にして明示します。index.js
は例外)
そしてこのJSXにも制限があり、復数の要素をルートに横並びさせることができません。
必ず1つの要素の下に居る必要があります。回避する方法もありますがこちらは後述しますので一旦忘れてください。
このAppというコンポーネントは、単純に <div>
の中に<h1>~~~</h1>
を包んだものを返しているだけです。
変化したりとかもさせていません。
ちなみにクラスの構文でも表現できますが個人的には関数オブジェクトでの宣言がおすすめです。(理由とかはまた機会があれば)
ReactDOM.render(<App />, document.querySelector('div#root'));
そして、そのAppのコンポーネントをReactDOM.render
に渡すことで指定したターゲット( public/index.html
にある)にレンダリングされる、という流れになっています。
実際に追記されるとこのように出ます。
これだけしか出ませんが、正常です。次に進みましょう。
ここまでで覚えておくこと
- コンポーネントは関数もしくはクラスで宣言して作る
- コンポーネント命名は頭文字が大文字である必要あり(小文字だとHTMLと解釈される)
- コンポーネントを使うときは
<ComponentName />
のようにタグっぽい文法で書く - HTML(XML)を記述して
return
することでレンダリングされる
実際に作る-表を作ってみる(+コンポーネントを分ける)
多分ネイティブなReactを触る上で一番恩恵デカイのが表の生成だと思うのでそれで試してみようと思います。
まずは表を表現するためのコンポーネントを新しいファイルに分けて生成します。
今回はtable.jsx
というファイル名で作ります。
const Head = () => {
// theadの構成を作る
};
const Body = () => {
// tbodyの構成を作る
};
export default function Table(){
return(
<table>
<Head />
<Body />
</table>
)
};
index.js
でコンポーネントを受け取れないと困るので、テーブル本体を返すコンポーネントのみexport
修飾子で渡せるようにしておきます。 default
は気にすんな
そしてindex.js
で受け取りをして、レンダリングをさせます。
import React from 'react';
import ReactDOM from 'react-dom';
// `table.jsx` からTableをimportする
import Table from './table';
const App = () => {
return (
<div>
<h1>Hello Mr.React!</h1>
<Table />
</div>
);
};
ReactDOM.render(<App />, document.querySelector('div#root'));
受け取り側のコードが書けたので、表の実装に入ります。
しかし、Table
コンポーネントの中に全ての構成を突っ込むとコードがかなり長くなってしまうので、Reactの得意分野を活かしてHead
とBody
の2つのコンポーネントに分けてそれぞれ別々にレンダリングしようと思います。
Headを作る
それでは Head
から作っていこう、と言いたいところですがストップです。
Table
コンポーネントのところでヘッダー行の名前や数量を配列で表現することで動的に変更できる、といった構造に出来ればかなり改変がしやすくなるような気がします。長年の勘がそう言っています。
そのためには、先程作ったTable
コンポーネントに少しだけ変更を加えます。
export default function Table(){
// 配列を入れた変数を追加
const headList = ['ID', 'Name', 'isOtaku'];
// `<Head>` の横に`list={headList}` と追記
return(
<table>
<Head list={headList}/>
<Body />
</table>
)
};
追記した <Head list={}>
ですが、これはpropsと呼ばれており、コンポーネントに対してデータを渡すことができる仕組みです。
今回の場合list
というpropsの中にheadList
という配列を渡しています。
これを Head
のコンポーネントの中で取得する際は以下のコードのようになります。
// 引数に `props` が入ってくる必要がある
const Head = (props) => {
// `props.~~~` でpropsの中身を参照できる
const array = props.headList;
};
試しにconsole.log(array);
と書いて保存してみてください。
こんな感じでコンポーネント側に配列が渡されていることが確認できます。
あとはArray.map
で回しながら<th></th>
の中に内包してやれば head
は完成です。
Head
の完成形としてはこんな感じになりました。
const Head = (props) => {
// `props.~~~` でpropsの中身を参照できる
console.log(props.list);
return (
<thead>
<tr>
{ props.list.map((item) => {
return (
<th>
{ item }
</th>
);
}) }
</tr>
</thead>
);
};
少し難解ですが()
の中の{}
で括った部分はJSのコードとして評価されるので、これを利用してmap
で回しながら<th>
を生成し、その中に配列の要素を挿入することでヘッダー行が出来上がりです。
Bodyを作る
続いてBody
コンポーネントも作っていきます。
こちらも同様に変数を渡すことで動的に生成させようと思います。そのためには配列の中にオブジェクトを入れるのが最適かなあという感じなので、Table
の中身をこうしてみます。
export default function Table(props){
const headList = ['ID', 'Name', 'isOtaku'];
// tbodyを構成するためのオブジェクトの集まりを作る
const bodyElements = [
{
id: 1,
name: 'Sister Cleaire',
isOtaku: false
},
{
id: 2,
name: 'Yashiro Kidsuku',
isOtaku: true
},
{
id: 3,
name: 'Otogibara Era',
isOtaku: true
}
];
return(
<table>
<Head list={headList}/>
<Body list={bodyElements} />
</table>
)
};
ここでやっていることも同じで、オブジェクトの入った配列をprops経由で渡しているだけです。
あとはBody
コンポーネント内でループなどの実装をすればOKです。
const Body = (props) => {
// `<tr>`を纏めて生成するための
// ローカル内のコンポーネント
const Tds = (props) => {
return(
<>
<td>
{props.object.id}
</td>
<td>
{props.object.name}
</td>
<td>
{props.object.isOtaku.toString()}
</td>
</>
);
};
return(
<tbody>
{ props.list.map((item) => {
return(
<tr>
<Tds object={item} />
</tr>
);
}) }
</tbody>
)
};
少しややこしいですが落ち着いて、まずはreturn
内部から入りましょう。
return(
<tbody>
{ props.list.map((item) => {
return(
<tr>
<Tds object={item} />
</tr>
);
}) }
</tbody>
)
<tbody>
の中で配列をmap
を使ってループさせ、<Tds>
をレンダリングしています。
このときに配列の中に入っていたオブジェクトが一つずつprops.object
として入ります。
このことを抑えてTds
を見てみましょう。
const Tds = (props) => {
return(
<>
<td>
{props.object.id}
</td>
<td>
{props.object.name}
</td>
<td>
{props.object.isOtaku.toString()}
</td>
</>
);
};
Tds
は複数の<td>
タグを返すことから命名しています。
復数の <td>
タグの中で props.object
で渡されたオブジェクトの値を参照しています。
また、isOtaku
だけ真理値型なので文字列に明示的に変換しています。
ここで「おい待てや」ってなった方は優秀です。最初のあたりで説明したことを思い出してください。
そしてこのJSXにも制限があり、復数の要素をルートに横並びさせることができません。
そう、横並びした<td>
は通常だと返せません。そう。通常は。
ここで登場するのが <>
</>
というカオナシのようなやつです。
これはReact.Fragment
と呼ばれるコンポーネントの糖衣構文で、これに包むとレンダリングの際に横並びした状態でレンダリングしてくれます。
今回はこれを利用することで横並びする要素をそのまま分離しています。
これでレンダリングするものが完成です。
保存していただいて、表がきちんと出来ていればOKです。
また、配列を減らしたり増やしたりして行が増減するのを確認してみてください。
ここまでで覚えておくこと
- コンポーネントを小分けにしつつ、propsで値をバケツリレーして作ると煩雑化が防げる
- propsを使う際はコンポーネントの引数を入れることを忘れないようにする(必要なければ入れなくてOK)
おわりに
今回はReactのさわりのさわりだけ解説してみましたがいかがでしょうか。
Reactは自分でコンポーネントを作るだけでなく、npmからパッケージを引っ張ってimportすることでそのまま利用することもできます。
その時の話はここで纏めてありますので、そちらもよかったらぜひご覧いただければと思います。
ちなみに今回のサンプルで作ったソースコードは以下に全て放り込んでおいたので、上手く行かなかった場合は見比べてみてください。
https://github.com/huequica/qiita_react_sample
それでは今回はこの辺で。