クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【React】Nextスターターキットを作った-2. コンポーネント編(Next + TypeScript + CSS in JS)

Nextの続きです。前回はNextスターターキットの全体設定編でした。今回はコンポーネントやモジュールの設定をまとめていきます。Reactアプリ作成でよく使う基本的な機能を組み入れました。それではいきましょう!

【目次】

※参考:前回記事
【React】Nextスターターキットを作った-1. 全体設定編(Next + TypeScript + CSS in JS) - クモのようにコツコツと

※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com

前回のおさらい

以前作成したReactスターターキットをNextスターターキットで作り直した。前回は全体設定編。

https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210501/20210501124640.jpg

Reactスターターキットのときはうまくいかなかったページごとの固有なOGPも設定できた!今回はコンポーネントやモジュールの設定。

※参考:前回記事
【React】Nextスターターキットを作った-1. 全体設定編(Next + TypeScript + CSS in JS) - クモのようにコツコツと

作ったもの

ソースコード

※参考:GitHub - ryo-i/next-app-started

プレビュー

※参考:Nextアプリスターターキット


トップページ
f:id:idr_zz:20210501124640j:plain

ヘッダー、フッターのコンポーネント、テキスト「こんにちは、ねくすと」を表示するモジュールなどを今回設定している。 next-app-started.vercel.app


Aboutページ
f:id:idr_zz:20210501124656j:plain

※参考:このアプリについて | Nextアプリスターターキット

作者のプロフィール部分のコンポーネントを今回設定している。 next-app-started.vercel.app

ヘッダーコンポーネント

全ページのヘッダーに共通で表示するコンポーネント

ヘッダーテキスト

「/data/data.json」にヘッダーに表示するテキストを追加

{
   // 中略
    "header": {
        "title":"Nextアプリスターターキット",
        "text": "Next.js + TypeScript + CSS in JS環境"
    },
   // 中略
}

「/components」フォルダに「Header.tsx」を追加し、ヘッダーテキストをインポート。

import Data from '../data/data.json';

テキストを変数titletextで取得

const title = Data.header.title;
const text = Data.header.text;

ヘッダーCSS

ヘッダーのCSSを設定する。styled-componentspageSize、CSS変数cssVariablesをインポート

import styled from 'styled-components';
import { pageSize } from '../styles/mixin';
import cssVariables from '../styles/variables.json';

pageSizecssVariablesについては前回記事を参照

※参考:【React】Nextスターターキットを作った-1. 全体設定編(Next + TypeScript + CSS in JS) - クモのようにコツコツと

HeaderTagコンポーネントのCSSを設定

// Style
const HeaderTag = styled.header`
  text-align: center;
  background: ${variable.bgColor_g};
  .wrapper {
    ${pageSize}
    padding: 30px;
  }
  h2 {
    font-size: 2em;
  }
  nav span, nav a {
    padding-right: 0.5em;
  }
`;

ヘッダーJSX

ヘッダー部分のJSXを設定する。

Next.jsのLinkをインポート

import Link from 'next/link';

Headerコンポーネント作成

// Component
function Header() {
  return (
    <HeaderTag>
      <div className="wrapper">
        <h2>{ title }</h2>
        <p dangerouslySetInnerHTML={{ __html: text }}></p>
        <nav>
          <span>MENU:</span>
          <Link href="/"><a>Home</a></Link>
          <Link href="/about"><a>About</a></Link>
        </nav>
      </div>
    </HeaderTag>
  );
}
  • HeaderTagコンポーネントに先ほどのCSS設定が適用される
  • h2タグでtitle、pタグでtextを読み込む
  • navタグの中でLinkコンポーネントを設定

最後にHeaderをエクスポート

export default Header;

フッターコンポーネント

こちらも全ページに共通で表示する

フッターテキスト

「/data/data.json」でフッターテキストを設定

{
    // 中略
    "footer": {
        "text": "©️ イイダリョウ"
    }
}

「/components」フォルダに「Footer.tsx」を追加し、フッターテキストをインポート

import Data from '../data/data.json';

変数textでフッターテキストを取得

const text = Data.footer.text;

フッターCSS設定

styled-componentspageSizeをインポート

import styled from 'styled-components';
import { pageSize } from '../styles/mixin';

FooterTagコンポーネントのCSSを設定

// Style
const FooterTag = styled.footer`
  ${pageSize}
  padding: 30px;
  text-align: center;
  p {
    margin: 0;
  }
`;

フッターJSX

Footerコンポーネント作成

// Component
function Footer() {
  return (
    <FooterTag>
        <p dangerouslySetInnerHTML={{ __html: text }}></p>
    </FooterTag>
  );
}
  • FooterTagコンポーネントに先ほどのCSS設定が適用される
  • pタグでtextを読み込む

最後にFooterをエクスポート

export default Footer;

hello()モジュール

hello()モジュールはトップページにテキスト「こんにちは、ねくすと」を表示するだけのモジュール
https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210501/20210501124640.jpg

data.json

「/public」フォルダに「data.json」を追加

{
    "message": {
        "text": "こんにちは、ねくすと。",
        "selector": ".hello"
    }
}
  • textは表示するテキスト
  • selectorはテキストを読み込むセレクタ

今回、このファイルは仮想外部APIのレスポンスという想定でfetch APIで読み込みたいと思う。そのため、静的なファイル置き場である「/public」フォルダに追加した。

async/await(エラーハンドリングはtry-catch)

「/modules/hello」フォルダを作成し「hello.ts」を追加する。

変数urlで先ほどの「hello.json」をインポート

const url = 'data/hello.json';

hello()モジュール作成

async function hello () {
    try {
        const res = await fetch(url);
        const hello = await res.json();
        // fetch()がOKな場合の処理
      } catch(err) {
        // エラー時の処理
    }
};
  • async/awaithello()関数を作成
  • tryfetch()OKな場合の処理を書く
    変数resawaitfetch()を実行、引数はurl
    変数helloawaitresのレスポンスをjson()で取得
  • catch()にエラー時の処理を書く

fetch()はついthen()で書きたくなっちゃうがなるべくasync/await構文に慣れたい

※参考:【JS】async/await構文で書いたFetch APIからJSONデータを読み込む - クモのようにコツコツと

async/awaitのエラーハンドリングはいろんな書き方があるが、よく使われるのはtry-catchが多そう

※参考:async/awaitを利用したコードのエラーハンドリング - Qiita
※参考:try...catch - JavaScript | MDN


最後にhelloモジュールをエクスポート

export { hello };

レスポンスOK時の処理

tryの中にレスポンスOK時の処理を書いていく。

まずは変数を設定

        const text: string = hello.message.text;
        const selector: string = hello.message.selector;
        const dom: HTMLButtonElement = document.querySelector(selector) as HTMLButtonElement;
  • 変数textでテキストtextを取得
  • 変数selectorでセレクタselectorを取得
  • 変数domでページ上のDOMを取得、引数はselector

TypeScriptの型定義についてはこちらを参照

※参考:【React】ピュアなTypeScriptモジュールを追加してみる(Reactとメタ言語の比較-6) - クモのようにコツコツと


処理を追加

        dom.innerHTML = text;
        console.log('text-> ' + text);
  • domのテキストをtextにする
  • textをコンソールに表示する

エラー時の表示を設定

catchの中にエラー時の処理を追加

async function hello () {
    try {
        // 中略
      } catch(err) {
        console.log('err!');
        console.log(err);
    }
};
  • catch()を実行、引数はerr
  • コンソールでテキスト「err!'」とエラー内容errを表示

「hello.json」のファイル名を「hello-.json」に打ち替えたところAPIエラーになってエラーがコンソールに表示された! f:id:idr_zz:20210502085806j:plain


このままだとAPIエラーしかエラーにならない。値の不一致などもエラーにしたい。throw文を使うとユーザー定義のエラーを設定できそう。

throw 文は、ユーザー定義の例外を発生させます。現在の関数の実行は停止し (throw の後の文は実行されません)、コールスタック内の最初の catch ブロックに制御を移します。

※参考:throw - JavaScript | MDN

async function hello () {
    try {
        // 中略
        if (!text) {
            throw new Error('テキストが見つからないYo!');
        } else if (!selector) {
            throw new Error('セレクタ名が見つからないYo!');
        } else if (!dom) {
            throw new Error('DOMが見つからないYo!');
        // 中略
    }
};
  • もしtextがなければ「テキストが見つからないYo!」エラー
  • もしselectorがなければ「セレクタ名が見つからないYo!」エラー
  • もしdomがなければ「DOMが見つからないYo!」エラー

textselectordomの値を打ち替えたりしたところ、ちゃんと上記のエラーがコンソールに表示された!
f:id:idr_zz:20210502084809j:plain

Innerコンポーネント

Inner()コンポーネントのテキスト

トップページに表示するInner()コンポーネントのテキストを設定する。「/data/data.json」に追記

    "inner": [
        {
        "title": "CSS(文字色)",
        "text": "CSSでタイトルの文字色変更。"
        },
        {
        "title": "JS(文字列)",
        "text": "JSでテキストの文字列追加「<span class='hello'></span>」"
        }
    ],

Inner()コンポーネント

次にInner()コンポーネントを作成する。「/components」フォルダに「Inner.tsx」を追加。

フックuseEffecthelloモジュール、data.jsonをインポート

import React, { useEffect }  from 'react';
import { hello } from '../modules/hello/hello';
import Data from '../data/data.json';

変数innerJsonData.innerを取得

const innerJson = Data.inner;

Inner()コンポーネントを作成

// Component
function Inner() {
  // 中略
}

フックuseEffect()でDOM読み込み後にhello()を実行

  useEffect(() => {
    hello();
  });

フックのライフサイクルについては下記を参照

※参考:【React】フックとjson-serverをFetch APIで連携(Reactライフサイクルの理解) - クモのようにコツコツと


JSX部分

  return (
    <>
      {
        innerJson.length >= 1
          ? innerJson.map((innerJson, index) =>
            <section key={ index }>
              <h2>{ innerJson.title }</h2>
              <p dangerouslySetInnerHTML={{ __html: innerJson.text }}></p>
            </section>
          )
          : <section>
            <h2>内容が無いよう</h2>
            <p>へんじがない、ただのしかばねのようだ。</p>
          </section>
      }
    </>
  );

innerJsonの数が1以上だったら h2タグにtitle、pタグにtextを入れる処理をmap()でループする。 さもなくばh2タグに「内容が無いよう」、pタグに「へんじがない、ただのしかばねのようだ。」と表示する

Reactスターターキットではmap()のループのみだった。

※参考:react-app-started/Inner.tsx at main · ryo-i/react-app-started · GitHub

今回は三項演算子の分岐処理も追加した。制御構造(分岐やループ)はいろいろ試したがJSXの中で動くmap()や参考演算子がやはり手軽だった。

※参考:【React】条件分岐の書き方(if文エラー回避、論理演算子、三項演算子) - クモのようにコツコツと
※参考:【React】ループの書き方(for文エラー回避、配列、map()) - クモのようにコツコツと


最後にInnerコンポーネントをエクスポート

export default Inner;

Profileコンポーネント

ProfileコンポーネントはAboutページで読み込む。こちらはベタガキのJSXを返す

// Component
function Profile() {
  return (
    <section>
      <h2>イイダリョウ(作者)</h2>
      <p>フロントエンドエンジニア。神奈川に住まう四十路のオジキ。 DTP→Webデザイナーから転向し今に至る。<br />
      引き続きコツコツの日々。ブログも書いてます。Webづくり やりたい時が 始め時!</p>
      <ul>
          <li><a href="https://twitter.com/idr_zz">Twitter</a></li>
          <li><a href="https://www.i-ryo.com">ブログ</a></li>
          <li><a href="https://qiita.com/i-ryo">Qiita</a></li>
          <li><a href="https://github.com/ryo-i">GitHub</a></li>
      </ul>
    </section>
  );
}

Profileコンポーネントをエクスポート

export default Profile;

最後に

ということで今回はコンポーネントとモジュールの設定部分をまとめました。今回得た知見

  • fech APIのasync/await構文のおさらい
  • try-catchを使ったエラーハンドリング
  • throw文でAPIエラー以外のユーザー定義エラーも追加できる

次回はこれまで作ったコンポーネント使ってNext.jsのページファイルを作る部分をまとめます。

それではまた!


続き買いました!ページファイル編です♪

※参考:www.i-ryo.com


※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com