20210111のReactに関する記事は12件です。

Reactのスタイリング(classNameやclassNamesの使い方)

修正

221/1/12 12:30頃にclassnamesの記述とコードブロックの修正を加えました。それ以前は誤った記述になっていたので、現状を参考にしてください。

classNameとは

htmlではclassを使ってスタイリングをしますが、Reactが使っているjsxではclassNameを使ったスタイリングを行います。

classNameの使い方

classNameの使い方は簡単で、classと全く同じです。

export default function App() {
  return (
      <div className={App}>
        <h1>Hello CodeSandbox</h1>
      </div>
  );
}

今回はAppを入れましたが、変数などを入れることも可能です。

classNames

動的なクラスの使い分けをする場合や複数のクラス名をつける場合はclassNamesというnpmパッケージを使うと便利です。
https://www.npmjs.com/package/classnames#usage-with-reactjs

基本的な使い方

今回はfoobarClassNamesで定義しています。オブジェクトの値がfalseだとそのkeyとなるclass名は付与されません
よって今回はfooのみがクラス名として与えられています。

import React from "react";
import classNames from "classnames";

export default function App() {
  const foobarClassNames = { foo: true, bar: false };

  return (
    <div className={classNames(foobarClassNames)}>
      <h1>Hello CodeSandbox</h1>
    </div>
  )
}

複数のクラス名を使う

classNamesを使うことで複数のクラス名を手軽に使えます。

import React, { useState } from "react";
import classnames from "classnames";

export default function App() {

  return (
    <>
      <div className={classnames("login", "isActive")}>
        <h1>Hello CodeSandbox</h1>
      </div>
    </>
  );
}

loginとisActiveが付与されています。

動的にクラス名を切り替える。

下記の画像ではuseStateによって定義されたstatus(progressかcreared)によってstatusClassNamesの値が切り替わる例です。
常にpazzleクラスは持っています。

import React, { useState } from "react";
import classnames from "classnames";

export default function App() {
  const [status, setStatus] = useState("progress");
  const statusClassNames = {
    pazzle: true,
    progress: status === "progress",
    creared: status === "creared"
  };

  return (
    <>
      <div className={classnames(statusClassNames)}>
        <h1>Hello CodeSandbox</h1>
      </div>
    </>
  );
}


classNamesを使うことで、Reactにおける様々なスタイリングを行うことが出来ます。

参考

・React公式ドキュメント(CSS とスタイルの使用)
https://ja.reactjs.org/docs/faq-styling.html

・classnamesパッケージドキュメント
https://www.npmjs.com/package/classnames


フリーランスでフロントエンドエンジニアをしています。
お仕事のご相談こちらまで
gunners6518@gmail.com

技術ブログ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 環境構築

cd

でディレクトリを作りたい階層までアクセス

% npx create-react-app ~ディレクトリ名~

注意 : ディレクトリ名は全て小文字

インストールできたらエディタで開く(VScode)

VScode内のターミナルから

% npm start

で確認

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsでサクッとPWA対応

はじめに

next-pwaを使って、Next.jsのチュートリアルで作ったブログをサクッとPWA対応していきます!

参考:next-pwa

作成したリポジトリ:https://github.com/NozomuTsuruta/next-pwa-sample

そもそもPWAとは

PWAはプログレッシブウェブアプリ(Progressive web apps)の略で、ウェブアプリをネイティブアプリのように使えるようにする仕組みのことです。
今回は以下を実装していきたいと思います。

  • ダウンロードみたいなのができる(ホーム画面に追加)
    Qiitaにも実装されているみたいですね!
    image.png

  • オフラインで使える(キャッシュ)

next-pwa

Next.jsで簡単にPWAを実装できるようにするライブラリです。serviceWorkerで実装するキャッシュとかを裏でやってくれます。
他で有名なものにnext-offlineがありますが、next-pwaの方が更新頻度が高く、個人的に使いやすかったので、next-pwaを使っていきたいと思います。

実際にやってみる

今回のディレクトリ構成はこんな感じです↓(Next.jsのチュートリアルで作ったのをsrcに移動しただけです)
image.png

導入

以下のコマンドでインストールします。

## npm
npm install next-pwa

## yarn
yarn add next-pwa

アイコンをサクッと作る

以下のサイトで必要なアイコンをサクッと作っちゃいましょう!
様々なファビコンを一括生成。favicon generator
image.png

manifest.jsonというファイルも作成されるので一緒にpublicディレクトリに追加します。

manifest.jsonへんしゅ

manifest.json上で説明した、ダウンロードみたいものをできるようにするために必要です。

ダウンロードしたものは下のような感じになっていると思うので自分の好みに変更します。

public/manifest.json
{
    "name":             "",
    "short_name":       "",
    "description":      "",
    "start_url":        "/",
    "display":          "standalone",
    "orientation":      "any",
    "background_color": "#fff",
    "theme_color":      "#fff",
    "orientation":      "any",
    "icons": [
        {
            "src": "/android-chrome-36x36.png",
            "sizes": "36x36",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-48x48.png",
            "sizes": "48x48",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-72x72.png",
            "sizes": "72x72",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-96x96.png",
            "sizes": "96x96",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-128x128.png",
            "sizes": "128x128",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-144x144.png",
            "sizes": "144x144",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-152x152.png",
            "sizes": "152x152",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-256x256.png",
            "sizes": "256x256",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-384x384.png",
            "sizes": "384x384",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}

それぞれの説明↓

name アプリ名
short_name アプリ名(略)
icons アイコン
start_url 開始URL
display 表示の仕方
orientation 画面の向き
background_color 背景カラー
theme_color テーマカラー
description 説明
dir 字の方向
lang 言語

参考:Web App Manifest(日本語訳)

ちなみにこんな感じに編集しました↓

public/manifest.json
{
    "name":             "myapp",
    "short_name":       "myapp",
    "description":      "this is my app",
    "start_url":        "/",
    "display":          "standalone",
    "orientation":      "portrate-primary",
    "background_color": "#fff",
    "theme_color":      "#fff",
    "dir": "ltr",
    "icons": [
        {
            "src": "/android-chrome-36x36.png",
            "sizes": "36x36",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-48x48.png",
            "sizes": "48x48",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-72x72.png",
            "sizes": "72x72",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-96x96.png",
            "sizes": "96x96",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-128x128.png",
            "sizes": "128x128",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-144x144.png",
            "sizes": "144x144",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-152x152.png",
            "sizes": "152x152",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-256x256.png",
            "sizes": "256x256",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-384x384.png",
            "sizes": "384x384",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}

_document.tsxの追加

src/pagesディレクトリに_document.tsxを作成し、以下のようにHeadのなかでアイコン、manifest.jsonなどを読み込んでいきます。
必要最低限のサイズのアイコンを読み込んでいるので、お好みで追加してください。WindowsやSafariに対応しなくても良い時も好みで減らしてみてください。

src/pages/_document.tsx
import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
  DocumentInitialProps,
} from "next/document";

class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    return await Document.getInitialProps(ctx);
  }

  render() {
    return (
      <Html lang="ja-JP" dir="ltr">
        <Head>
          {/* windows */}
          <meta
            name="msapplication-square70x70logo"
            content="/site-tile-70x70.png"
          />
          <meta
            name="msapplication-square150x150logo"
            content="/site-tile-150x150.png"
          />
          <meta
            name="msapplication-wide310x150logo"
            content="/site-tile-310x150.png"
          />
          <meta
            name="msapplication-square310x310logo"
            content="/site-tile-310x310.png"
          />
          <meta name="msapplication-TileColor" content="#000" />
          {/* safari */}
          <meta name="apple-mobile-web-app-capable" content="yes" />
          <meta name="apple-mobile-web-app-status-bar-style" content="#000" />
          <meta name="apple-mobile-web-app-title" content="myapp" />
          <link
            rel="apple-touch-icon"
            sizes="180x180"
            href="/apple-touch-icon-180x180.png"
          />
          {/* 一般 */}
          <meta name="application-name" content="myapp" />
          <meta name="theme-color" content="#000" />
          <meta name="description" content="this is myapp" />
          <link rel="icon" sizes="192x192" href="/icon-192x192.png" />
          <link rel="icon" href="/favicon.ico" />
          <link rel="manifest" href="/manifest.json" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

next.config.jsの作成

ルートディレクトリにnext.config.jsを作成し、以下のように記述します。

node_modulesのnext-pwaにcache.jsというファイルがあり、そこにキャッシュの設定が書かれているのでyarn buildするとキャッシュの設定が登録されます。(デフォルトではyarn devではキャッシュされません)
カスタマイズしたい場合は下のnext.config.jsのコメントみたいな感じで、runtimeCachingの配列に書きます。

next.config.js
const withPWA = require("next-pwa");

module.exports = withPWA({
  pwa: {
    dest: "public", // swの出力ディレクトリ
    // runtimeCaching: []
  },
});

ここまでで実装が完了しました。

実際に実行してみる

以下のコマンドを実行します。

## npm
npm build 
npm start

## yarn
yarn build 
yarn start

localhostで開き、デベロッパーツールを見るとServiceWorkerが登録されており、CacheStorageにも追加されていることがわかります。
一回読み込んでしまえば、オフラインにして再読み込みしてもちゃんと読み込まれます。これでオフラインで動作する確認はできました!
image.png

さらに、右上に+みたいなのが表示されていると思います。
それを押すと下のような画面が出てきます。
image.png

インストールを押すと、下のように追加され、開くとアプリのようにページが開かれます!
もちろんこれもオフラインで動作します。
image.png

image.png

これにて簡単なPWAの実装は完了です。お疲れ様でした!

ちなみにデプロイしたサイトでiPhoneのホームに追加をやるとこんな感じになります↓
image.png
開く↓
image.png

ほぼネイティブアプリじゃん?

作成したリポジトリ:https://github.com/NozomuTsuruta/next-pwa-sample
vercelでデプロイしたサイト:https://next-pwa-sample.vercel.app/

最後に

ここまで読んでくださりありがとうございます!少しでもPWAの理解のお役に立てたら嬉しいです。
ご意見・ご感想お待ちしております!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンド開発者のための便利なオンラインツール11選

本記事は、Chidume Nnamdi氏による「11 Useful Online Tools for Frontend Developers」(2020年2月11日公開)の和訳を、著者の許可を得て掲載しているものです。

フロントエンド開発者のための便利なオンラインツール11選

私が愛用するオンライン開発ツール

Image for post

インターネット上には、フロントエンド開発者の生活を楽にしてくれる素晴らしいツールが沢山あります。この記事では、私が開発の仕事で頻繁に使っている11のツールについて簡単に説明します。

1. CanIUse

Web APIが特定のブラウザで互換性があるのか、モバイルブラウザで使用できるのか、分からないことはありませんか?このオンラインツールを使えば、ブラウザの互換性についてWeb APIを簡単にテストできます。

Can I use―HTML5、CSS3などの対応表。
「Can I use」は、デスクトップとモバイルにおけるフロントエンドWebテクノロジーを支援する、最新のブラウザ対応表を提供します。
caniuse.com

Web Share APIのnavigator.share(...)に対応しているブラウザとバージョンを知りたいとします。

Image for post

結果を見てください。navigator.share(...)に対応しているブラウザとバージョンがすべて一覧になっています。

2. Minify

アプリコードのバンドルサイズを縮小するために、ここで空白文字やデッドコードなどを削除します。その結果、アプリのバンドルサイズが大幅に縮小され、ブラウザでの読み込み時間が短縮されます。

このオンラインツールminify.comで、Webアプリのコードを縮小できます。

3. Bit.dev

Bit.devは素晴らしいコンポーネントハブです。私は、さまざまなプロジェクトで再利用可能なコンポーネントをホストし、文書化し、管理するため、これを使っています。コードの再利用を増やし、開発を迅速化し、チームの共同作業を最適化するのに良い方法です。

また、設計システムをゼロから構築するのにも良い代替手段です(設計システムに必要なものが基本的にすべて揃っているため)。Bit.devは、コンポーネントの分離と公開を担うオープンソースツールBitと完全に連携します。

Bit.devは、React、React with TypeScript、Angular、Vueなどに対応しています。

Image for post

例:Bit.devで共有Reactコンポーネントを検索する

Bit―再利用可能なコードコンポーネントをチームで共有。
チームでより迅速に構築するため、プロジェクトとアプリケーション間で、再利用可能なコンポーネントを簡単に共有します。共同開発。
bit.dev

4. Unminify

このツールは、minifyと逆のことをします。

Unminify
このツールは、見苦しいJavaScript、CSS、HTMLコードを縮小、再フォーマット、再インデントして、再び読めるようにします。
unminify.com

このツールで、縮小されたJavaScriptコードを解凍し、明瞭にし、整えることで、再び読めるようにできます。

5. Stackblitz

これは誰もが知っているツールです。Stackblitzは、世界で最も人気で、最も使用されているIDEのVisual Studio CodeをWeb上で使用する機能を提供してくれます。

StackBlitz―Webアプリ用オンラインコードエディタ。Visual Studio Code搭載。
stackblitz.com

Stackblitzは、Angular、React、Vue、Vanilla、RxJS、TypeScriptのプロジェクトをワンクリックで素早く、スキャフォールドしてくれます。

Stackblitzは、現在のJavaScriptフレームワークのコードや機能をブラウザから試してみたい時に特に便利です。Angularの記事を読んでいて、試してみたいコードを見つけたとします。ブラウザを最小化し、数行だけで新規Angularプロジェクトをスキャフォールドできます。

速くて簡単です。

他にも素晴らしいオンラインIDEはありますが、Stackblitzの勝因は、誰もが使い慣れていて、誰もが好きなツールVisual Studio Codeフィールを使用したことだと思います。

6. JWT.io

JSON Web Token(JWT)を使用してアプリを保護している時、またはユーザがバックエンドで保護されたリソースにアクセスできるようにしている時。

ルートやリソースにアクセスする必要があるかを判断する1つの方法は、トークンの有効期限を確認することです。JWTをデコードしてペイロードを確認したいことがありますが、jwt.ioでそれがまさにできるようになります。

JWT.IO
JSON Web Token(JWT)は、2者間で転送されるクレームを表現するための、コンパクトでURL safeな方法です。
jwt.io

このオンラインツールを使用すると、トークンをプラグインしてペイロードを確認できます。 トークンを貼り付けると、jwt.ioはトークンをデコードし、そのペイロードを表示します。

7. BundlePhobia

node_modulesのサイズがわからないことや、マシンにインストールされるpakckage.jsonのサイズを知りたいことはありませんか?BundlePhobiaで分かります。

BundlePhobia
Bundlephobiaは、フロントエンドバンドルにnpmパッケージを追加した時の、パフォーマンスへの影響を調べるのに役立ちます。
bundlephobia.com

このツールで、package.jsonファイルをアップロードすると、package.jsonからインストールされる依存関係のサイズを表示します。

8. Babel REPL

Babelは無料のオープンソースJavaScriptトランスコンパイラで、最新のECMAScriptコードを古いプレーンなES5 JavaScriptに変換するために使用されます。

このツールは、Babeljsチームが設定したオンラインWebアプリで、ES6+コードをES5にトランスパイルできます。

Babel―次世代JavaScript用コンパイラ。
babeljs.io

これにより、ECMAScriptに最近追加された機能や、追加段階にあるいくつかの機能を試すことができます。コードを整え、ファイルサイズを制限し、トランスパイルの時間を節約できます。

9. Prettier Playground

Prettierは、意見の分かれるJavaScriptコードフォーマッタです。コードを解析し、JavaScriptのベストコーディングプラクティスに沿った形で、一貫したスタイルを適用します。

このツールは、開発環境で広く使用されていますが、オンラインでもコードを整えられます。

Prettier
prettier.io

10. Postman

このツールは、APIエンドポイント(GET、POST、DELETE、OPTIONS、PUT)のクイックテストに役立ちます。

私も使用したことがありますが、11選に入れる価値があります。

11. JSLint

JavaScriptの静的解析ツールでは、JSLintがトップに立っています。JSLintのオンラインバージョンを使用すると、ブラウザでJavaScriptのコードやファイルをリントできます。

https://jslint.comに行って試してみてください。

JSLint―The JavaScriptコード品質管理ツール。
JSLintはJavaScriptコード品質管理ツールです。このファイルでWebブラウザからJSLintを実行できます。ソースを受け取ることができます。
jslint.com

まとめ

他にも沢山ありますが、ここで紹介したのは私が愛用しているものです。

ご質問や、追加・修正・削除すべきものがある場合は、コメント、メール、DMなどで遠慮なくお知らせください。

ありがとう!!!

もっと詳しく知りたい方は

Reactコンポーネントを公開する方法
どのリポジトリからでもReactコンポーネントを素早く公開する方法
blog.bitsrc.io

2020年版 便利なReactコンポーネント10選
さまざまな場合のさまざまなReactコンポーネント
blog.bitsrc.io

フロントエンド開発者におすすめのツール11選
愛用しているフロントエンドツール一覧
blog.bitsrc.io

注)コメント欄で@standard-software様より助言をいただきましたので、コメントもご参照いただければ幸いです。

翻訳協力

Original Author: Chidume Nnamdi@ngArchangel
Original Article: 11 Useful Online Tools for Frontend Developers
Thank you for letting us share your knowledge!

この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。
選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen

ご意見・ご感想をお待ちしております

今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
皆様のメッセージをお待ちしております。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React hooks】噛み砕いて解説してみた~useEffect編~

前書き

16.8vで追加された機能であるreact hooksを理解を深めるために体系的にまとめました。

以下、本題です

useEffectとは

関数コンポーネント内で「副作用」を実行できます。

そもそも副作用ってなに?

簡単に説明すると「関数の外に影響を与えてしまう関数」のことを指すことが多いです。例えば

  • console.log
  • DOMの手動変更
  • APIでのデータの取得

などのことです。「Reactにおける「副作用」とは?」の記事がわかりやすかったので一読して見ると良いかもしれません。

useEffectは実際に何ができるのか

噛み砕いて説明すると副作用(何らかの関数)を特定のタイミングで実行することができます。基本的な形をみていきましょう。

import React, { useState, useEffect } from 'react'

const Home = () => { 
  const [name, setName] = useState('')

  // ここで実際に使われています。
  useEffect(() => {
    console.log({name})
  })

  const handleName = (e) => {
    setName(e.target.value)
  }
  return(
    <>
      <h1>{name}</h1>
      <input onChange={handleName}></input>
    </>
  )
}

export default Home

注目すべきところは

useEffect(() => {
  console.log({name})
})

の部分です。useEffect関数内でこのように副作用を宣言してあげることで、レンダー時に副作用(関数)が実行されます。

以降から、どのタイミングで副作用が呼ばれるか確認していきましょう!

【1】レンダー後に実行される副作用を宣言

もっともスタンダードな形です。下記のように定義することで、副作用がレンダー(レイアウトと描画)の後に毎回実行されます。

useEffect(() => {
  console.log({name})
})

【2】アンマウント時に実行される副作用を宣言

クリーンアップ関数を実行したいときはuseEffectreturn内に書いてあげれば、アンマウント時にクリーンアップしてくれます。(公式ドキュメントからコピペしました。)

useEffect(() => {
  return () => {
    // Clean up the subscription
    subscription.unsubscribe();
  }
})

クリーンアップにはイベントリスナーの削除等によく使われます。

【3】特定の条件の時に副作用を実行したい場合

この場合、useEffectの第二引数に配列の形で、何らかの値(変数等)を入れてあげてください。この値が変更された時だけ、副作用が実行されます。

下記の例では、nameの更新時に副作用(console.log)を実行するようになっています。

useEffect(() => {
  console.log({name})
},
[name]
)

【4】更新時には副作用を実行したくない場合

useEffectの第二引数に空の配列を渡してあげれば、更新時には副作用は実行されません。

useEffect(() => {
  console.log({name})
},
[]
)

以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

緊急事態宣言下だし、人狼ゲームを実装してみた【Docker・JavaScript】

動機

春先の緊急事態宣言下ではZoom飲みが大流行したせいか、オンラインの人狼が重くなって困りました。また発令されたし、みんなが自分で人狼ゲームを起動できたら幸せなのでは?というノリで実装&公開したので紹介します。

紹介

以下のような簡単なUIのチャット人狼です。ホストの一人がDockerでゲームを立ち上げたら、ほかの人はブラウザ(スマホでもOK)からアクセスするだけで簡単に遊べます。

image.png

遊び方(ホスト)

Dockerコンテナの起動

まず以下のコマンドでDockerコンテナを起動します。初回はGitHub Container Registryからイメージがpullされます。

docker container run -it --rm -p 3000:3000 ghcr.io/dr666m1/werewolf

起動後、画面の指示に従ってゲームの設定を行ってください。
image.png

ポートの公開

3000番ポートでゲームが起動しているので、パブリックIPアドレスのある環境(GCEなど)ならみんなにIPアドレスを伝えるだけです。注意点としては、http://...:3000のようにポート番号を明示する必要があるかもしれないことと、ファイアウォールの設定を確認しておくことです。

パブリックIPアドレスのない環境ではngrokを利用するのが便利です。初期設定が完了するとngrok http 3000というコマンドで3000番ポートを公開できるようになります。コマンド後にURLが表示されるので、それをみんなに伝えてください。無料枠での制限はこちらご確認ください。

遊び方(全員)

ホストから教えてもらったURLにブラウザでアクセスしましょう。以下のような画面が表示されたら名前を決めて入場してください。

image.png

その後は普通の人狼ゲームです。役職は現状「市民」「人狼」「占い師」「霊媒師」「狩人」のみ1と標準的なので、迷うことはないと思います。

実装

Node.jsで実装しています。GitHubに全てのコードが置いてあるので興味があればご覧ください。似たようなものを作る人の参考に、利用したパッケージを簡単に紹介します。全部npm installで使えるはずです。

Next.js

Next.jsReactでUIを作成するためのフレームワークです。SSG(static generation)やSSR(server-side rendering)を利用できるのが特徴です。今回はnext exportで静的なHTMLファイルを出力するところまでNext.jsで実装しました2。GitHubリポジトリのclientディレクトリ以下が関連ファイルです。

bulma

bulmaはCSSフレームワークです。DOMのクラスを設定するだけで見た目をいい感じに整えてくれます(例えば以下はnotificationクラス)。bulmaのおかげで、今回CSSはほとんど書いていません3
image.png

Next.jsの枠組みで利用するには、_app.jsの冒頭に一行追記するだけです。

_app.js
import "bulma/css/bulma.css" // 追記

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

socket.io

socket.ioはウェブソケットを扱うパッケージで、チャットの実装に利用しました。wsというパッケージもあるのですが、socket.ioの方が多機能なので今回はそちらを使っています(例:ウェブソケットが使えない状況でロングポーリングに切り替えられる、roomnamespaceという概念でクライアントを整理できる)。ちょっと今回の実装では活かしきれなかった部分も多いのですが。

後書き

JavaScriptは全くの初心者なので、バグがあったらすみません。Zoom飲みとかにご活用ください。noteにも稀に投稿しているのでよければご覧ください(直近の記事)。


  1. 要望があれば「狂人」とかいろいろ追加するかもしれません。 

  2. custom serverを利用すればNext.jsの枠組み内で実装を完了できそうでしたが、UIに集中したくて分離しました。Dockerfileを見ての通り、多段階ビルドになっています 

  3. JavaScriptの中で多少スタイルをいじった程度です。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React hooks】噛み砕いて解説してみた~useState編~

前書き

16.8vで追加された機能であるreact hooksを理解を深めるために体系的にまとめました。

以下、本題です

useStateとは

useStateを使うことで任意State変数の宣言ができます。もっとわかりやすく表現すると、関数コール内で値を保持することができます。具体的にみた方がわかりやすいと思うので、コードで見てみましょう。

import React, { useState } from 'react'

const hoge = () => {
  // 下記のコードで`State`宣言されています。
  const [name, setName] = useState('')
}

基本形はこんなイメージです。以降から詳しく解説していきます!

useStateを使ってState宣言

基本形は下記の形です。(説明のために、必要な部分だけ切り取っています。)

const [name, setName] = useState('')
  • name:任意の変数名を宣言ができます。ここではnameにしていますが、好きな名称を設定することができます。
  • setNameState変数(name)を更新するための関数を宣言しています。
  • useState(''):引数でState変数の初期値を設定することができます。例えば、useState('佐藤')みたいに設定することもできます。

State変数を読み出す。

関数コンポーネント内で下記のように呼び出すことができます。

import React, { useState } from 'react'

const hoge = () => {
  const [name, setName] = useState('')

  return(
    // 下記のように使うことができます。
    <p>私の名前は{name}です</p>
  )
}

classコンポーネントの時のようにthis.state.nameみたいにかく必要はありません。ハッピー!!

State変数を更新する

useStateで宣言した変数を更新するための関数(ここではsetState)を使ってstateを更新します。

import React, { useState } from 'react'

const hoge = () => { 
  const [name, setName] = useState('')

  const handleName = (e) => {
    setName(e.target.value)
  }

  return(
    <>
      <h1>{name}</h1>
      <input onChange={handleName}></input>
    </>
  )
}

export default hoge

少しだけ複雑になってしまっているので順番に解説していきます。

  • handleName:この関数の中でstateを更新するsetNameが更新されています。
  • onChange:このイベントリスナー内で実際にhandleNameを経由して値を更新しています。

useStateの全体像

【1】useStateをImport

import React, { useState } from 'react'

【2】useStateを使って、変数,更新関数を宣言

const [name, setName] = useState('')

nameが変数、setNameが更新関数ですね。

【3】変数を読み出し、更新

import React, { useState } from 'react'

const hoge = () => { 
  const [name, setName] = useState('')

  const handleName = (e) => {
    setName(e.target.value)
  }

  return(
    <>
      <h1>{name}</h1>
      <input onChange={handleName}></input>
    </>
  )
}

export default hoge

以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでポートフォリオ制作したらReact-Routerでハマったことなど

デザイナーのみつるです。
Reactの勉強も兼ねて、SPAでポートフォリオサイトを作成してみました。

シンプルなSPAなのですが、一筋縄ではいかず、特にReact-Routerで苦労したので、
解決策も載せつつ、まとめてみました。

Create React Appで環境構築

サーバー周りがまだ苦手なので、コマンド数回で環境構築をしてくれるCreate React Appを使いました。

https://create-react-app.dev/docs/getting-started

Webpackなどの裏側の設定が隠される仕組みなので、頻繁にカスタマイズやメンテナンスをする大規模サイトには向かないとは思われますが、シンプルなSPAを作るのにはとても便利でした。
デフォルトでCSS Modulesが使えるのも良かったです。

苦労1:CSS Modulesの中でNest機能が使えない

しかし、CSS Modulesの中でNest機能がOFFになってるようで、これをONにするのにはejectする(Webpackなどの設定ファイルを出現させる)しかなかったのが、ちょっと残念でした。
方法はこちらにまとめました。
https://qiita.com/tnk-mitsuru/items/0dfadd5f2a237b701843

React Routerでページ遷移を実装

これまでページごとにHTMLを書いていたので、「ReactはSPAのフレームワークなので、そもそもページ遷移という仕組みが存在しない」という事実に「なるほど〜」と思いました。

とはいえ、リンクのような機能はReact Routerという仕組みで実装できるんですね。
https://reactrouter.com/web/guides/quick-start

使い方はこちらを参考にしました。
https://qiita.com/ShinKano/items/541050c36e08e78a5176

苦労2:本番公開すると、<BrowserRouter>の中が真っ白になる

しかし、いざビルドして本番公開すると、真っ白のページが表示されてしまいました。
<BrowserRouter>の代わりに<HashRouter>を使うと解決するという記事があったのですが、URLの末尾に#がつくのが、どうもダサ...受け付けられなくて、

https://tnk-mitsuru.github.io/portfolio/#
じゃなくて、↓こうしたいんです!
https://tnk-mitsuru.github.io/portfolio/

ここは粘って解決しました。参考になったのはこちら。
https://elsammit-beginnerblg.hatenablog.com/entry/2020/10/22/224710
ただ、公式サイトの説明
https://reactrouter.com/web/api/BrowserRouter
も読み進めて、basenameのところはもう少しシンプルにこんなコードにしても大丈夫でした。

<BrowserRouter basename='/portfolio'>
   <Switch>
      <Route exact path='/' component={Home}/>
      <Route exact path='/WebDesign' component={WebDesign}/>
      <Route exact path='/ConceptArt' component={ConceptArt}/>
   </Switch>
</BrowserRouter>

Package.jsonのhomepageのところはこんな記述です。

  "homepage": "https://tnk-mitsuru.github.io/portfolio/",

苦労3:子ページのURLを直打ちすると404になる

そして今度は、子ページのURL(https://tnk-mitsuru.github.io/portfolio/WebDesignなど)を直打ちすると404になる問題に直面しました。

原因については、この記事によくまとまっていました。
https://dev-daikichi.hatenablog.com/entry/2019/04/17/144159
SPAはURLがルートしかない(その他のページは存在しない)ので、URLを直打ちしすると404になるんですね。

しかし、他の記事でもそうだったのですが、解決方法が難しそうでピンチ。諦めかけましたが、シンプルでわかりやすい対処法がありました。
https://github.com/rafgraph/spa-github-pages#usage-instructions
のBasic instructionsをそのままやったところ、うまくいきました。

苦労4:カレントのとき非リンクにできない

Routerの悩みは尽きません。
ヘッダーでは<NavLink>を使ってカレント判定はしてるのですが、aタグでなく、spanタグなどに変えるようなことはできませんでした。
https://reactrouter.com/web/api/NavLink
を見ると、できそうな雰囲気はあるものの、まだ私の理解力がなくて、今回は断念><

パッケージの依存問題

苦労5 画像ギャラリーなどのパッケージはReact17にはまだ未対応?

デザイナーポートフォリオの目玉とも言える、画像ギャラリーですが、以下のライブラリを使わせていただきました。APIのオプションも充実していて、とても便利でした。

https://react-slideshow.herokuapp.com/
http://neptunian.github.io/react-photo-gallery/

画像ギャラリーを自前で実装するとかなり大変なので、ライブラリを提供してくださっているエンジニアの方には本当に感謝しかありません。

しかし、Reacr 16.xから17.xへのメジャーアップデートがけっこう最近(2020年10月)だったせいか、パッケージが古いよとの警告がでてきました。

エラー.png

他のライブラリを検討したものの、上手く表示されず、結局、この2つを使うことにしました。課題は私にあって、自前でギャラリーを実装できる技術力が必要なんだと思います。

まとめ

その他細かい苦労はありましたが、大きくはこの5つでした。
こう書くと、Reactつらいみたいな印象になりますが、実際すごく便利でした。

コンポーネントの使い回しやライブラリでかなり効率的に実装できましたし、何よりコードがきれいになるので、あとで見返しても解読しやすい(デバッグしやすい)のは、大きなメリットだと思いました。
CSSもコンポーネント単位で書けたので、変数名をいちいち考えなくて良かったのは、とても快適でした!

一応、制作時間を測っていたのですが、デザイン業務も含めて3週間ほど、プログラミングのみだと2週間ほどでした。(ビジュアルデザインはプログラミングしながら検討しました)
デザインのプロセスについては、こちらにまとめています。
https://note.com/artist_mitsuru/n/n1efa4f4e7acc

シンプルなSPAですが、自分でプログラミングして思ったのは、
Webサイトが「ふつうに動く」ためには、表に出ない、裏の裏にある不具合との戦いがあるんだなぁという実感でした。ネジ1本がズレただけで大事故になる「宇宙船」開発に近いのかも。エンジニアさんすごい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでポートフォリオ制作して、苦労したことTOP5

デザイナーのみつるです。
Reactの勉強も兼ねて、SPAでポートフォリオサイトを作成してみました。

シンプルなSPAなのですが、一筋縄ではいかず、そこそこ苦労したので、
解決策も載せつつ、まとめてみました。

Create React Appで環境構築

サーバー周りがまだ苦手なので、コマンド数回で環境構築をしてくれるCreate React Appを使いました。

https://create-react-app.dev/docs/getting-started

Webpackなどの裏側の設定が隠される仕組みなので、頻繁にカスタマイズやメンテナンスをする大規模サイトには向かないとは思われますが、シンプルなSPAを作るのにはとても便利でした。
デフォルトでCSS Modulesが使えるのも良かったです。

苦労1:CSS Modulesの中でNest機能が使えない

しかし、CSS Modulesの中でNest機能がOFFになってるようで、これをONにするのにはejectする(Webpackなどの設定ファイルを出現させる)しかなかったのが、ちょっと残念でした。
方法はこちらにまとめました。
https://qiita.com/tnk-mitsuru/items/0dfadd5f2a237b701843

React Routerでページ遷移を実装

これまでページごとにHTMLを書いていたので、「ReactはSPAのフレームワークなので、そもそもページ遷移という仕組みが存在しない」という事実に「なるほど〜」と思いました。

とはいえ、リンクのような機能はReact Routerという仕組みで実装できるんですね。
https://reactrouter.com/web/guides/quick-start

使い方はこちらを参考にしました。
https://qiita.com/ShinKano/items/541050c36e08e78a5176

苦労2:本番公開すると、<BrowserRouter>の中が真っ白になる

しかし、いざビルドして本番公開すると、真っ白のページが表示されてしまいました。
<BrowserRouter>の代わりに<HashRouter>を使うと解決するという記事があったのですが、URLの末尾に#がつくのが、どうもダサ...受け付けられなくて、

https://tnk-mitsuru.github.io/portfolio/#
じゃなくて、↓こうしたいんです!
https://tnk-mitsuru.github.io/portfolio/

ここは粘って解決しました。参考になったのはこちら。
https://elsammit-beginnerblg.hatenablog.com/entry/2020/10/22/224710
ただ、公式サイトの説明
https://reactrouter.com/web/api/BrowserRouter
も読み進めて、basenameのところはもう少しシンプルにこんなコードにしても大丈夫でした。

<BrowserRouter basename='/portfolio'>
   <Switch>
      <Route exact path='/' component={Home}/>
      <Route exact path='/WebDesign' component={WebDesign}/>
      <Route exact path='/ConceptArt' component={ConceptArt}/>
   </Switch>
</BrowserRouter>

Package.jsonのhomepageのところはこんな記述です。

  "homepage": "https://tnk-mitsuru.github.io/portfolio/",

苦労3:子ページのURLを直打ちすると404になる

そして今度は、子ページのURL(https://tnk-mitsuru.github.io/portfolio/WebDesignなど)を直打ちすると404になる問題に直面しました。

原因については、この記事によくまとまっていました。
https://dev-daikichi.hatenablog.com/entry/2019/04/17/144159
SPAはURLがルートしかない(その他のページは存在しない)ので、URLを直打ちしすると404になるんですね。

しかし、他の記事でもそうだったのですが、解決方法が難しそうでピンチ。諦めかけましたが、シンプルでわかりやすい対処法がありました。
https://github.com/rafgraph/spa-github-pages#usage-instructions
のBasic instructionsをそのままやったところ、うまくいきました。

苦労4:カレントのとき非リンクにできない

Routerの悩みは尽きません。
ヘッダーでは<NavLink>を使ってカレント判定はしてるのですが、aタグでなく、spanタグなどに変えるようなことはできませんでした。
https://reactrouter.com/web/api/NavLink
を見ると、できそうな雰囲気はあるものの、まだ私の理解力がなくて、今回は断念><

パッケージの依存問題

苦労5 画像ギャラリーなどのパッケージはReact17にはまだ未対応?

デザイナーポートフォリオの目玉とも言える、画像ギャラリーですが、以下のライブラリを使わせていただきました。APIのオプションも充実していて、とても便利でした。

https://react-slideshow.herokuapp.com/
http://neptunian.github.io/react-photo-gallery/

画像ギャラリーを自前で実装するとかなり大変なので、ライブラリを提供してくださっているエンジニアの方には本当に感謝しかありません。

しかし、Reacr 16.xから17.xへのメジャーアップデートがけっこう最近(2020年10月)だったせいか、パッケージが古いよとの警告がでてきました。

エラー.png

他のライブラリを検討したものの、上手く表示されず、結局、この2つを使うことにしました。課題は私にあって、自前でギャラリーを実装できる技術力が必要なんだと思います。

まとめ

その他細かい苦労はありましたが、大きくはこの5つでした。
こう書くと、Reactつらいみたいな印象になりますが、実際すごく便利でした。

コンポーネントの使い回しやライブラリでかなり効率的に実装できましたし、何よりコードがきれいになるので、あとで見返しても解読しやすい(デバッグしやすい)のは、大きなメリットだと思いました。
CSSもコンポーネント単位で書けたので、変数名をいちいち考えなくて良かったのは、とても快適でした!

一応、制作時間を測っていたのですが、デザイン業務も含めて3週間ほど、プログラミングのみだと2週間ほどでした。(ビジュアルデザインはプログラミングしながら検討しました)
デザインのプロセスについては、こちらにまとめています。
https://note.com/artist_mitsuru/n/n1efa4f4e7acc

シンプルなSPAですが、自分でプログラミングして思ったのは、
Webサイトが「ふつうに動く」ためには、表に出ない、裏の裏にある不具合との戦いがあるんだなぁという実感でした。ネジ1本がズレただけで大事故になる「宇宙船」開発に近いのかも。エンジニアさんすごい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React で QRコード表示機能を実装

QRコードを表示する画面を作ったので、そのときのまとめです。
reactでQRコードを生成するライブラリは色々あったので、
hookの書き方に対応しているもので、
古めのコードリーダーでも比較的読み取り易いQRコードを生成してくるものを探しました。

検討したライブラリ

react-qrcodes
https://www.npmjs.com/package/react-qrcodes
導入もシンプルで古い機器での読み取りも問題なかったので今回はこれを使ってみることにしました。

react-qrbtf
https://www.npmjs.com/package/react-qrbtf
色を付けたり、コード部分を四角ではなく丸で表現できたり、楽しいのです。
(iPhoneのカメラでは読み取り可能でした)
QRcode_SP — 3.png

実装

まずは、パッケージをインストール

npm i react-qrcodes

QRコードを表示する画面を作っていきます。

qrcode.js
import React from 'react';
import { useQRCode } from 'react-qrcodes';

function App() {
  const [inputRef] = useQRCode({
    text: 'https://qiita.com/hujuu',
    options: {
      level: 'H', //誤り訂正レベル
      margin: 3, //QRコードの周りの空白マージン
      scale: 1, 
      width: 200,
    },
  });

  return <canvas ref={inputRef} />;
};

export default App;

パラメータについて

誤り訂正レベル
誤り訂正レベルは、「どの程度の汚れまでに対応するか」を表しています。
スマホからQRコードを出そうと思ったので、画面のゴミや光の反射など考慮してhighにしてみました。

参考資料:https://www.qrcode.com/about/error_correction.html

マージン
QRコードはコードの周りに一定の空白区間がある程度必要です。
空白無く、他のテキストやデザインが描画されていると読み取れない場合があります。

参考資料:https://www.qrcode.com/howto/code.html

完成

react-qrcodesで生成したコードはこんな感じになりました。
QRcode_SP.png (289.4 kB)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Redux Hooks公式ドキュメント翻訳(useSelector編)

Reactアプリケーションの状態管理のためのOSSライブラリである、React Reduxのバージョン7.1.0で追加された、Hooks APIの公式ドキュメントを翻訳していきます。

  1. React Redux Hooks公式ドキュメント翻訳(概要編)
  2. React Redux Hooks公式ドキュメント翻訳(useSelector編)

2021/1/11公開。原文リンクは以下。
・公式ドキュメント(React Redux Hooks):https://react-redux.js.org/api/hooks

今回の記事はuseSelectorAPI編です。
スクリーンショット 2021-01-10 4.38.13.png

useSelector()

const result: any = useSelector(selector: Function, equalityFn?: Function)

useSelectorを使うことでReduxのstoreのstateにデータを登録することが可能になります。

Note: useSelectorは純粋関数である必要があります。なぜなら、複数回、任意のタイミングで実行される可能性があるからです。

useSelectorは概念的にはconnect関数に対して与えた引数mapToPropsに相当します。利点としては、Reduxのstore state全体をただの引数として扱える点です。前のコンポーネントのレンダリング後から、その参照先が普遍である限り、useSelctorはキャッシュしたselectorの値を返すので、関数コンポーネントのレンダリングをいつでも実行することができます。useSelectorはさらに、Redux storeを登録したり、actionがdispatchされるときにいつでもselectorを呼び出せます。

しかしながら、useSelectorとmapState関数のそれぞれで呼び出したselectorにはいくつか違いがあります。

・selectorはオブジェクトに限らず全ての値を返すことができます。selectorの返り値はuseSelectorの返り値として扱われます。
・actionがdispatchされたとき、useSelectorは前回の返り値と今の返り値を比較します。それらが異なる場合、コンポーネントは再レンダリングを強いられます。同じ場合は、そのコンポーネントは再レンダリングされません。
・useSelector関数は引数ownPropsを受け取りませんが、propsはクロージャを通す(以下の例を見てください)またはカリー化した関数を使うことで使用することができます。
・保存されたselctor(memorized selector)を扱う場合は特別な注意が必要です。(詳細は以下の例を見てください。)
・useSelectorはデフォルトでは等価比較に厳密な===を使います。曖昧比較は使いません。(次のセクションで詳細を説明します。)

Note: propsを扱う場合は、エラーを引き起こす特別なケースがいくつかあります。詳細は使用上の注意を参照してください。

1つの関数コンポーネントでuseSelectorは複数回呼び出されることがあります。useSelectorを呼び出す際には毎回、Reduxのstoreに登録されます。React Reduxバージョン7からuseSelectorは追加されたため、同一のコンポーネントでuseSelectorの複数回の呼び出しを引き起こすようなdispatchされたactionは、一回の再レンダリングのみで済みます。

同一性の比較とアップデート

関数コンポーネントのレンダリングの際、引数に与えられたselector関数が呼ばれ、その結果がuseSelectorの返り値となります。(前回のコンポーネントのレンダリングと結果が同じ場合、キャッシュされた結果はselectorの再レンダリング無しで返されます。)

しかしながら、もし、selectorの結果が明らかに前の結果と異なる場合、Reduxのstoreにactionがdispatchされた時に、useSelectorは一回の再レンダリングのみを実施させます。Reduxバージョン7.1.0-alpha.5の時点では、デフォルトの比較は厳密比較(===)です。これは再レンダリングが必要かどうか決定する、mapState関数の結果を曖昧比較でチェックするconnect関数と異なる点です。これはuseSelectorを扱う上でいくつかの示唆を与えます。

mapStateでは全ての個々のフィールドは組み合わされたオブジェクトとして返されました。そこでは返されたオブジェクトが新しい参照をしたものかどうかは問題ではありませんでした。つまり、connect関数は単に個々のフィールドを比較しているだけです。useSelectorは、常に新しいオブジェクトを返すため、デフォルトでは毎回再レンダリングを強制します。もし、複数の値をstoreから取得したい場合は、以下のようにできます。

・useSelctorを複数回呼び出し、それぞれで単一のフィールドを返すようにする。
・1つのオブジェクトで保存されたselctor(memorized selector)を作るライブラリを使うようにする。しかし、1つの値が変更された場合は新しいオブジェクトを一つだけ返すようにする。
・同一性を比較する関数として、useSelctorの引数に、React ReduxのshallowEqual関数を使う。(以下例)

import { shallowEqual, useSelector } from 'react-redux'

// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)

useSelectorの使用例

基本的な使い方

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector(state => state.counter)
  return <div>{counter}</div>
}

抜き出したいものを決めるために、クロージャを使ってpropsを扱う

import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = props => {
  const todo = useSelector(state => state.todos[props.id])
  return <div>{todo.text}</div>
}

保存されたselector(memoizing selector)を使う

上記のようにインライン要素のselectorを扱う場合にuseSelectorを用いる際は、コンポーネントがレンダリングされる毎回で新しいselectorのインスタンスが作られます。これはselectorがなんのstateも持っていない場合の挙動です。しかしながら、例えばreselectライブラリのcreateSelector関数によって作られる、保存されたselector(memoizing selector)は内部にstateを持ちません。そして、それゆえに扱う際には注意が必要です。以下にmemoizing selectorの典型的な使用例を示します。

selectorがstateのみに依存しない場合、同じselectorのインスタンスがそれぞれのレンダリングで使われるようにするために、コンポーネントの外で宣言されることを確認してください。

import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumOfDoneTodos = createSelector(
  state => state.todos,
  todos => todos.filter(todo => todo.isDone).length
)

export const DoneTodosCounter = () => {
  const numOfDoneTodos = useSelector(selectNumOfDoneTodos)
  return <div>{numOfDoneTodos}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <DoneTodosCounter />
    </>
  )
}

selectorがコンポーネントのpropsに依存している場合も同様です。しかし、単一のコンポーネントで単一のインスタンスのみが使用可能なので注意してください。

import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumOfTodosWithIsDoneValue = createSelector(
  state => state.todos,
  (_, isDone) => isDone,
  (todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
)

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const numOfTodosWithIsDoneValue = useSelector(state =>
    selectNumOfTodosWithIsDoneValue(state, isDone)
  )

  return <div>{numOfTodosWithIsDoneValue}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <TodoCounterForIsDoneValue isDone={true} />
    </>
  )
}

selectorが複数のコンポーネントのインスタンスで使われており、それらのコンポーネントのpropsに依存している場合、それぞれのコンポーネントのインスタンスがそれらのselectorのインスタンスを取得していることを確認してください。(なぜこれが必要なのかについてより詳細な説明は、ここを参照してください。)

import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const makeNumOfTodosWithIsDoneSelector = () =>
  createSelector(
    state => state.todos,
    (_, isDone) => isDone,
    (todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
  )

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNumOfTodosWithIsDone = useMemo(
    makeNumOfTodosWithIsDoneSelector,
    []
  )

  const numOfTodosWithIsDoneValue = useSelector(state =>
    selectNumOfTodosWithIsDone(state, isDone)
  )

  return <div>{numOfTodosWithIsDoneValue}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <TodoCounterForIsDoneValue isDone={true} />
      <span>Number of unfinished todos:</span>
      <TodoCounterForIsDoneValue isDone={false} />
    </>
  )
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webpack / react / typescriptを使ったフロント環境をgithub-pagesに公開するまで

目的

自分の携わっているサービスでreactに触れる機会があり、そこで得た知見を少しずつまとめようと思いました。どんなものを作成していたのか後で振り返りやすいようにgithub-pagesで公開して、web上から触れる設定を作ることを考えています。

この記事では、そのためのベースとするWebpack/React/TypeScript を設定して行きます。
記事のゴールとしては、React/TypeScriptを利用して書いたコードを、webpackでコンパイルしたものを出力してgithub-pagesに公開できることを目指します。

環境/前提条件

今回利用する主要なツールのバージョンは下記の通りです。
筆者はmacOSでvagrant+dockerの環境で作業しています。
vagrant+dockerの環境構築に関してはこちら

ツール名 バージョンなど
react 17.0.1
typescript 4.1.3
webpack 5.12.2

すでにnpmのインストールまでが済んでいることが前提となります。
筆者のnpm/nodeのバージョンはそれぞれ 6.14.10 / v14.15.4 を利用しました。

流れ

  • npm initでpackage.jsonを作成する
  • npmでwebpack/React/TypeScriptをインストール
  • webpack用の設定
  • TypeScript用の設定
  • index.htmlとReactを使ったsrcファイルの作成
  • webpack-dev-serverの起動とwebpackのbuildの実行
  • github-pagesへの公開

ディレクトリ構成

今回作成するファイル群のディレクトリ構成は下記となります。

root --- docs ※1
      |   ├- index.html
      |   └- index.js
      ├- src
      |   └- typescripts ※2
      |       └- main.tsx
      ├- node_modules
      ├- package.json
      ├- package-lock.json
      ├- index.html ※3
      ├- tsconfig.json
      ├- webpack.common.js
      ├- webpack.dev.js
      ├- webpack.prod.js

※1 webpackのファイルの出力先、本来ならdistとするがgithub-pagesではサブディレクトリとして利用できるのがdocsだけだったので、docsで設定
※2 今後に自分が利用する際にこの階層にstylesheetsなども配置するためこの構成としています。
※3 このファイルはindex.htmlのテンプレートファイルです。webpackから出力されたindex.htmlはこれを基に作られます。

内容

npm initでpackage.jsonを作成する

npmでwebpack/React/TypeScriptをインストール

下記のコマンドで必要なツール/プラグインをインストールします。

npm i -D webpack webpack-cli clean-webpack-plugin webpack-dev-server html-webpack-plugin typescript ts-loader

オプションの-D--save-devのエイリアスらしく、開発環境内へのインストールとして扱われます。
インストールする内容に関して簡易的な説明としては下記となります。

ツール名 バージョンなど
webpack webpackの本体です。作成したReact/TypeScriptのコードをまとめるモジュールバンドラです。
webpack-cli webpackの操作をするためのコマンドセットです。
clean-webpack-plugin webpackをbuildして出力ファイルを作った際に古いファイルや使ってないファイルを削除するプラグインです。
webpack-dev-server ローカル上でwebpackを使って開発をする際に利用する開発環境向けのwebサーバーです。
html-webpack-plugin webpackでのbuild時にindexとなるhtmlファイルも出力するためのプラグインです。これがない場合には出力ファイルはjsのみとなりページとして表示できません
typescript TypeScriptの本体です。
ts-loader webpackでtypescriptをコンパイルするために必要なloaderです。

次に下記のコマンドでインストールを実行します。

npm i -S react react-dom @types/react @types/react-dom
ツール名 バージョンなど
react Reactの本体です。
react-dom ReactでのDOM操作に利用され基本的にはreactとペアでインストールを行います。
@types/react、@types/react-dom 上記2件のTypeScriptの型定義ファイルです。

webpack用の設定

まず、webpack用のjsファイルを作成します。
ローカル上ではwebpack-dev-serverを行い、本番サーバーやgithub-pagesではコンパイル後のファイルだけあればいいので、それぞれの環境によって設定が異なります。そのため、下記の三つにファイルを分割して作成をします。

ツール名 バージョンなど
webpack.common.js 共通設定の記載用のファイル
webpack.dev.js 開発環境用のファイル、webpack-dev-serverの設定が入ります。
webpack.prod.js 本番環境用のファイル、productionという設定を入れるだけですが、デフォルトの動きでコードの圧縮などを行うようになります。

下記ではそれぞれのファイルの内容の説明をした後に

webpack.common.js

下記の内容で作成します。

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlWebpackPlugin({
  template: "./index.html",
  filename: "index.html"
});

module.exports = {
  entry: './src/typescripts/index.tsx',
  plugins: [
    new CleanWebpackPlugin(),
    htmlPlugin,
  ],
  module: {
    rules: [
      {
        test: /\.tsx?$|\.ts?$/,
        use: [{
          loader: 'ts-loader',
        }],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
    ],
  },
  resolve: {
    modules: [
      'node_modules',
      path.resolve('./src/typescripts'),
    ],
    alias: {
      tsRoot: path.resolve('./src/typescripts'),
    },
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  output: {
    path: path.resolve(__dirname, './docs'),
    filename: 'index.js',
  },
};
設定項目
entry エントリーポイントの設定です。ここで設定したファイルを起点にwebpackがコンパイルをします
plugins 追加するプラグインです。
modules コンパイルする対象のファイルごとにコンパイルに利用するローダーを設定します。
resolve コンパイルの対象の設定などを行います。今回の作業の中では利用しませんが、エイリアスの設定も行って、React/Typescriptの中で絶対パスのような記載をするために利用します。
output コンパイルしたファイルの出力先の設定

webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    host: '0.0.0.0',
    port: 8000,
    disableHostCheck: true,
    historyApiFallback: true,
    contentBase: './docs'
  }
});

webpack.prod.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});

package.jsonにscriptを追加する

webpack-dev-serverの起動とbuildのために下記のscriptsをpackage.jsonに追記します。

watchとbuildを追記

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "watch": "webpack serve --open --config webpack.dev.js",
  "build": "webpack --config webpack.prod.js"
},

TypeScript用の設定

下記のファイルを作成します。
デフォルトの構成まではコマンドラインからも作成可能です。

outDirやBaseUrl,pathsなどをこれまでの他の設定に合わせて修正します。

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es6",      /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "jsx": "react",       /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "sourceMap": true,    /* Generates corresponding '.map' file. */
    "outDir": "docs",  /* Redirect output structure to the directory. */

    /* Strict Type-Checking Options */
    "strict": true,        /* Enable all strict type-checking options. */
    "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */

    /* Module Resolution Options */
    "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",            /* Base directory to resolve non-absolute module names. */
    "paths": {                  /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
      "tsRoot/*": ["./src/typescripts/*"],
      "tsRoot": ["./src/typescripts"],
    },
    "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

    /* Advanced Options */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

index.htmlとReactを使ったsrcファイルの作成

下記のファイルをそれぞれ作成します。

  • root > index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Front Component Sample ver1.0</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
  </head>

  <body>
    <div id='app'></div>
  </body>
</html>
  • root > typescripts > index.tsx
/** imports */
import * as React from 'react'
import * as ReactDOM from 'react-dom'

/** DOMへのrender */
// <div id='app'/> のコンポーネントが必要なことに注意
ReactDOM.render(
  <>render test</>,
  document.getElementById('app')
);

index.htmlファイルの <div id='app'></div> の部分にreactで書いた「render test」の文字を表示するだけのファイルです。
このファイルを使ってwebpack-dev-serverとwebpackのbuildの確認をします。

webpack-dev-serverの起動とwebpackのbuildの実行

webpack-dev-server

rootディレクトリにcdコマンドで移動して下記のコマンドを実行します

npm run watch

package.jsonに登録されたscriptが実行され、webpack-dev-serverが立ち上がります。
ブラウザで http://localhost:8000/index.htmlにアクセスして「render testと表示されるか確認して表示されればOKです。

webpackのbuild

rootディレクトリにcdコマンドで移動して下記のコマンドを実行します

mkdir docs
npm run build

root以下のdocsディレクトリにindex.htmlとindex.jsが作られていればOKです。

github-pagesへの公開

作成した内容をgithubにpushした後に、github-pagesの設定をします。

リポジトリにアクセスして下記の画面に遷移します。

settings > options

下の方にGithub Pagesの項目があるのでそこのSourceで対象のブランチとフォルダをdocsに設定します。
更新後にGithub Pagesの項目を見るとURLが発行されているので、アクセスして画面が表示されればOKです。

github-pagesにアクセスするまでに多少時間がかかるようなので注意してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む