🌱

(基礎編)jQuery使いのエンジニアがReactとNext.jsでWebサイト制作するために最低限押さえておきたいコード

2023/01/23に公開約9,600字2件のコメント

なぜ書いたか

筆者もWebサイト制作をそこそこ長くやってきておりいまは業務でVueを書いたりちょっとReactを書いたりSvelteを書いたりしていますが、2年前くらいまではReactやES6の構文すら書いたことがありませんでした。

WordPressでのサイト制作が多く、機能が少ないサイト制作会社ではjQueryで充分なことも多く、恥ずかしながら業務時間外での学習や外部の情報を追うこともしていなかったため、開発系の技術スタックに慣れるのにかなり時間がかかりました。

まずはよく使うコードを見て解説しながら答えの一つを示し、よく出てくるコードをざっくり理解して書けるようにすることで、実務でReactを取り入れる取っ掛かりになればいいなぁという思いでこの記事を書いています。

続編は多分今月中に書きます。
こちらは基礎編です。

対象者

  • 普段jQueryでWebサイトを制作している
  • 生のJSはあんまり書いていない
  • Reactは書いたことがない

書かないこと

  • 環境構築
  • 設計の話

コンポーネントを作る

Reactを書くというのはコンポーネントをめちゃくちゃいっぱい書くということとほとんど同じなので、まずは簡単な見出しコンポーネントを作ってみましょう。

components/Head01.tsx
const Head01: React.FC<{ text: string }> = ({ text }) => {
	return <h2>{text}</h2>
}

関数コンポーネントが今の主流なので、いったんクラスコンポーネントのことは忘れていいです。ある程度分かってきたらまた見てみてください。
TypeScriptが入るとちょっと最初はわかりにくいと思うのでそこをはずしてみてみます。

components/Head01.tsx
const Head01 = ({ text }) => {
	return <h2>{text}</h2>
}

こうです。
Head01という関数をconstで宣言しています。
関数コンポーネントは大文字始まりで命名します。

もうちょっと見ていきましょう。
({text})の部分ですが、こちらは分割代入を使用しています。

関数に引数を渡すときは(変数名)のようにするのはご存じかと思います。
この変数の中身がオブジェクトだった場合、{}中身を変数に分割して代入することができます。

Reactの関数コンポーネントは

<Head01 text="見出し"/>

このように書いたとき、propsを

props = {
  text: "見出し"
}

このような形で属性をプロパティ、その値をこのプロパティの値として持つオブジェクトをpropsとして引数に受け取ります。

つまり、

const Head01 = (props) => {
	const text = props.text
	return <h2>{text}</h2>
}
const Head01 = (props) => {
	const { text } = props
	return <h2>{text}</h2>
}
const Head01 = ({ text }) => {
	return <h2>{text}</h2>
}

この3つは同じです。
一番下が一番記述が短いのでこれで書かれることが多いです。

では次に型の部分を見ましょう。
さっき消した: React.FC<{ text: string }>の部分です。

React.FC<>の部分はReactが用意している型で、関数コンポーネントのpropsにどんなデータを渡す?というのをTypeScriptに教えるためのものです。
ここは覚えましょう。

今回{text: string}と書いてありますが、これはprops.textに文字列を入れられるようにしますよ という記述です。

この型の部分はなくても動きますが、なかったらそのコンポーネントにどんな情報を渡せるのか、いちいち定義元のファイルを見に行かないといけなかったり、想定していないデータを入れてしまってバグを生え散らかしてしまうことになってしまったりするので基本的につけるようにしましょう。

この辺も実際に型を書かずにやってみるとありがたさがわかるかと思います。

TypeScriptの記述は見慣れるまで難しく思いますが、マネして書いているうちに「これがなかったら無理だ…」になってくるので頑張って書きましょう。
勉強にお勧めなのはサバイバルTypeScriptです。

さて、ここで作ったHead01コンポーネントですが、いろんなページで使いまわしたいですね。
やってみましょう。

コンポーネントをexportしてimportする

components/Head01.tsx
export const Head01: React.FC<{ text: string }> = ({ text }) => {
	return <h2>{text}</h2>
}

こうします。exportを追加しました。
exportするとほかのページでこのHead01コンポーネントをimportできるようになります。
まぁ関数なので当たり前ですね。
ここが重要で、関数コンポーネントというのは、JSXを返すただの関数です。

試しにTOPページでこのコンポーネントを表示してみましょう。
Next.jsを使っている場合、pages/index.tsxがあると思うのでそちらでHead01をimportします。

pages/index.tsx
import { Head01 } from '../components/Head01'
export const Home = () => {
	return <Head01 text="見出し" />
}
export default Home

このように使います。

Webサイト制作だとPHPやWordPressのコードを書くことが多いと思いますが、PHPでいうとinclude、get_template_partあたりがJSでいうimportになります。

propsとchildren

さて、先ほどまで
<Head01 text="見出し" />このようなコードを書いてきましたが、これはHTMLにちょっと似ていますね。textのところはHTMLの属性っぽいです。

これはJSXというもので、HTMLっぽいけど実はReact.createElementというReact要素を作るための関数になっています。

タグっぽい見た目になっていますが、最終的には以下みたいにReact.createElementで置き換えられます。

React.createElement(
  Head01,
  {text: '見出し'}, // ここがprops
  '' //children
)

つまり、<Head01 text="見出し" />というのはHead01という名前の関数にtextというプロパティを持ったオブジェクトを引数propsとして渡しているのと同じです。

関数であるHead01の属性はpropsとして渡されます。
簡単に言うとHTMLの属性の部分がpropsになっていると思って大体OKです。

Head01というHTMLタグは存在しませんが、何回も同じHTMLのセットを書くのはクソめんどくさいのそれに名前を付けて再利用できるようにしているイメージです。最高ですね。

さて、HTMLっぽいということは、

<Head01 text="見出し">
  <p>ここにテキスト</p>
</Head01>

こんな感じで書けそうです。
実際書けます。
このHead01というJSXの内側にあるものはchildrenという特別な名前で渡されます。
<p>ここにテキスト</p>の部分がchildrenにあたります。

Head01.tsx
const Head01: React.FC<{ text: string, children: React.ReactNode }> = ({ text, children }) => {
	return (
		<h2>{ text }</h2>
		<div>{ children }</div>
	)
}

こんな感じでコンポーネントを作っていくのですが、実は上のコードにはミスがあります。
JSXは直下に要素を一つしか返せないので、h2とdivを何かのタグで囲わなくてはなりません。

でもわざわざ不要なラッパーで囲みたくないですよね。
そういう時に使うのがReact.Fragmentです。
実際使ってみましょう。

React.Fragmentと<></>

Head01.tsx
const Head01: React.FC<{ text: string, children: React.ReactNode }> = ({ text, children }) => {
	return (
		<>
			<h2>{ text }</h2>
			<div>{ children }</div>
		</>
	)
}
const Head01: React.FC<{ text: string, children: React.ReactNode }> = ({ text, children }) => {
	return (
		<React.Fragment>
			<h2>{ text }</h2>
			<div>{ children }</div>
		</React.Fragment>
	)
}

このように書けます。上と下は同じです。

いちいちReact.Fragmentと書くのはめんどくさいのでだいたい<></>のほうを使いますが、コンポーネントを配列から表示する場合で、key属性を渡したいときなどにReact.Fragmentを使用したりします。

配列からコンポーネントを表示する

かなりよくやります。
例えば記事の一覧をAPIで引っ張ってきてその記事数部分ループを回して記事コンポーネントを
表示したりします。

APIから記事を引っ張るところはいったんおいておいて、配列から記事コンポーネントを表示してみましょう。

Post01.tsx
import NextLink from 'next/link'
const Post01: React.FC<{ title: string, href: string }> = ({ title, href }) => {
	return (
		<NextLink href={`${href}`}>
			{ title }
		</NextLink>
	)
}

titleとhrefを受け取るPost01コンポーネントを作ってみました。
React.FCで型を定義しているのでどんなデータを渡せばいいのかすぐにわかります。

href={`${href}`}に注目してみましょう。
ここにはテンプレートリテラルという記法が使われています。

古い書き方では"/blog/"+slugのようにプラス記号で文字列連結していましたが、バッククォートで囲む書き方のほうがスマートです。
href={`/blog/${slug}`}
こんな感じ。

基本的にこっちをつかえばいいと思いますが、特に連結せず変数を渡したいだけであれば
href={href}こんな感じでもOKです。
今回はそれでOKですね。

次にNextLinkを見てみます。これはHTMLでいうaタグのようなものです。
リンクに使用します。

これでhrefにリンクを渡せばそこへのリンクが張られたPost01コンポーネントが表示されます。

ではこいつを配列の要素の数だけループして表示してみましょう。
WordPressでいう

<?php 
if ( have_posts() ) {
	while ( have_posts() ) {
		the_post(); 
		// 投稿
	}
}
?>

これをやります。

map

Reactを書いているとmapが頻繁に登場するので先に簡単に解説します。

const postsResponse = [{title: "記事1", slug: "slug1"}, {title: "記事2", slug: "slug2"}]
const posts = postsResponse.map((post) => {
	return ({
		title: post.title,
		href: `/posts/${post.slug}`,
	})
})

こんな感じで使用します。
関数を返すというところを覚えておいてください。

配列.map(★どんなデータを返すかを示す関数)
配列.map((①ループのn個めをどんな名前で受け取るか) => {②どんなデータを返すか})

こんな感じです。

今回①がpost②が

return ({
	title: post.title,
	href: `/posts/${post.slug}`,
})

これです。
postsResponseの記事の配列から、「titleはそのままでhrefが記事詳細ページへのパスになっているオブジェクト」の配列を作成しました。

Post01コンポーネントがtitleとhrefを受け取るコンポーネントだったからです。
ここで作ったpostsの配列を使ってPost01コンポーネントを表示してみたくなりましたが、その前に先ほどの

const posts = postsResponse.map((post) => {
	return ({
		title: post.title,
		href: `/posts/${post.slug}`,
	})
})

こちらのコードでまだちょっと解説できるポイントと、もうちょっとよくできるポイントがあります。

まず、returnの部分のまるかっこが気になったかもしれません。返したいのはオブジェクトだから{}の部分だけでもいいかも?と思いますが、それはできません。

めちゃくちゃ雑な説明をすると、returnの部分を{}にしてしまうと、2つの値を返しているのか、それともオブジェクトの{}なのかがわからなくなってしまうからです。
オブジェクトを返す場合は()で囲う必要があります。覚えておきましょう。

次にpost.の連続です。何回も書くのはめんどくさいですね。
ここにも分割代入を使ってみましょう。

const posts = postsResponse.map(({ title, slug }) => {
	return ({
		title: title,
		href: `/posts/${slug}`,
	})
})

こうです。

postの部分はオブジェクトだったので、プロパティのtitleとslugを分割代入で得ることができました。

これで終わりかと思いきや実はもう一つ改善ポイントがあります。

const posts = postsResponse.map(({ title, slug }) => {
	return ({
		title,
		href: `/posts/${slug}`,
	})
})

こうです。

実はtitle: titleのように、オブジェクトのプロパティ名と値に使用する変数名が同じ場合は省略することができます。
いきなり形が変わってビビることがありますが、これを覚えておけば混乱せずに済みますね。

さてこれでようやくほしい配列が得られました。
ここで思い出してほしいことが一つあります。

関数コンポーネントとはそもそも何だったでしょうか?
それはJSXを返すただの関数でした。

ではmapは何を返す関数でしたか?
そう、★どんなデータを返すかを示す関数を返す(returnする)関数でしたね。

つまり、mapの★の部分で「関数」コンポーネントをreturnしてあげれば記事一覧(記事を表示するコンポーネントの配列)を表示できるということです。
ピンときましたか?やってみましょう。

mapでPost01コンポーネントを配列の数だけ表示する

pages/index.tsx
import NextLink from 'next/link'
const Home = () => {
	const Post01: React.FC<{ title: string, href: string }> = ({ title, href }) => {
		return (
			<NextLink href={`${href}`}>
				{ title }
			</NextLink>
		)
	}
	const postsResponse = [{title: "記事1", slug: "slug1"}, {title: "記事2", slug: "slug2"}]
	const posts = postsResponse.map(({ title, slug }) => {
		return ({
			title,
			href: `/posts/${slug}`,
		})
	})
	const Posts = posts.map(({title, href}) => {
		return (<Post01 title={title} href={href} key={href} />)
	})
	return (
	<>
		{Posts}
	</>
	)
}
export default Home

こうなります。1か所見慣れないpropsがあるので見てみましょう。
keyです。

Reactコンポーネントで配列を作る場合は一意なキーを指定しなければなりません。
今回はhrefが被ることはなさそうなのでkey={href}としましたが、実際はCMSからidが来ることが多いのでそちらを使用するとよいでしょう。

さてこのコードですが、修正したい部分があるのではないでしょうか?
そう

const Posts = posts.map(({title, href}) => {
	return (<Post01 title={title} href={href} key={href} />)
})

ここです。

title={title} href={href}の部分、プロパティと値が同じですね。
ここは省略できます。

const Posts = posts.map((post) => {
	return (<Post01 {...post} key={href} />)
})

こうです。

スプレッド演算子

また変な記法が出てきましたが、めちゃくちゃ便利な演算子なので覚えておきましょう。
めちゃくちゃよく使います。

先ほどのコードを見比べればわかりますが、差分がどこにあるかというと

{title, href}
post
title={title} href={href}
{...post}

この2つです。

スプレッド演算子...を使うとオブジェクトの中身を展開できそうです。
簡単に言うと波かっこが外れます。

これでなぜコンポーネントにtitleとhrefのpropsを渡せるのかを見てみましょう。

まず関数コンポーネントの属性の部分は関数の引数propsに入ってくるんでした。
今回postは

{
  title: "記事タイトル",
  href: "記事スラッグ"
}

のようなオブジェクトだったので、propsは

props = {
  {...post}
}

このように入ってくることになります。

スプレッド演算子は波かっこをはずしてくれるので、

props = {
  title: "記事タイトル",
  href: "記事スラッグ"
}

こうなります。

これはpropsにtitleとhrefを渡すときと同じですね。

というわけでこのようにうまいこと展開されて関数コンポーネントに値が渡るわけです。
僕はこの考え方が一番しっくりきましたが、JSXは内部的にReact.createElementというもので置き換えられるので、以下の形を見てみればさらに理解できるかと思います。

React.createElement(
  Post01,
  { title: '記事タイトル', href: "記事スラッグ" }, // ここがprops 
  '' //children
)

このようにして配列から記事コンポーネントを表示することができました。
実際はulで囲ったりliで囲ったりして余白も当てたいと思うので、その辺のタグも含めてコンポーネント化しておくとよいでしょう。

次回の記事では実際にAPIからデータを持ってきてgetStaticPropsgetStaticPathsで記事一覧と詳細を表示してみようと思います。
この2つの関数はNext.jsが記事詳細ページを静的に生成するための関数で、SEOが重要なサイト制作ではよく使う関数です。
ここを押さえておけばだいたいのことはできるでしょう。

ここまで基礎的な内容がほとんどだったので、ここでわからなかった人はまずJavaScriptの本を一冊買って読んでみて、そのあとReactの優しい本を一冊読んだりQiitaの記事を読んでみたりしてから公式ドキュメントを一読するとよいかと思います。

jQueryになれてしまうとES6周りの記述に慣れないかもしれませんが、書いているうちに楽しくなってくるのであきらめずに頑張りましょう!

Discussion

良記事ありがとうございます!

ZennやQiitaではMarkdownのコードブロックに以下のような記法を用いることでコードをハイライトしてくれます!

```tsx
const hoge = "fuga "
```
const hoge = "fuga "

もし気が向いたら記事に取り入れてもらえると...!

ありがとうございます!

ハイライトつかないな〜と思いながら投稿してたのですが、バッククオートの後に勘違いで半スペを入れてしまっていたようでした…

そのうち修正します。
ご指摘ありがとうございます!

ログインするとコメントできます