2020年10月1日

ReactによるWebアプリ開発を体系的に学べるコース「Epic React」を購入!

購入したコース




内容
  • React Fundamentals
  • React Hooks
  • Advanced React Hooks
  • Advanced React Patterns
  • React Performance
  • Testing React Apps
  • React Suspense
  • Build an Epic React App
  • Epic React Expert Interviews



購入の動機


以前からEgghead.ioの動画ブログでKent C. DoddsさんのReactやJavascriptに関する解説を読んだり観たりしていて、とても参考になっていたため。

今回のコースはそのKentさんが何年にもわたって数々の有料セミナーなどで教えてきた経験を元に作成された、渾身の作ということなので、まず期待はずれになることはないだろうと確信が持てました。


インストラクター



元PayPalのフロントエンド開発者で、Reactのテスト用ライブラリとして人気の「React Testing Library」の作者でもあります。

ブログを読むと、初めてソフトウェア開発者として職を得たきっかけ、Facebookからのオファーを断ってPayPalに入った経緯、PayPalでの仕事内容、AngularからReactに軸足を移した経緯や、人に教えることへの情熱などがよくわかります。



Egghead.ioでおすすめのコース


Kentさんのコースに興味を持たれた方は、まずはEgghead.ioで下の動画レッスンなどを視聴してみてはいかがでしょうか。英語のリスニング練習にも最適です! 😀

React Tutorial for Beginners

Collection - Testing JavaScript with Jest





 

2020年7月30日

Parcelでasync/awaitを使うと「regeneratorRuntime is not defined」エラーが出る場合の対処法

Parcel を使ってJavaScriptアプリケーションを書いているときに、async/awaitを使おうとすると下のエラーが出ました。

Uncaught ReferenceError: regeneratorRuntime is not defined

調べたところ、次のサイトに解決策が載っていました。





対処法1


ブラウザを比較的新しいものだけに限定して構わないのであれば、次の方法が簡単です。

package.jsonに以下を追記します。

  "browserslist": [
    "since 2017-06"
  ]
これでasync/awaitを使ってもエラーが出なくなります。



対処法2


もう一つの対処法としては、「regenerator-runtime」パッケージをインストールするというものがあります。

> npm install regenerator-runtime

でインストールしておき、index.jsなどから

import 'regenerator-runtime/runtime';

として読み込みます。

バンドルサイズが25KBほど増えてしまうようですが、こちらの方法でもエラーが出なくなります。古いブラウザもサポートする必要がある場合はこちらを使うことになると思います。






 .


2020年6月16日

データベース不要! ReactとGoogle Drive APIで任意のデータを保存する

前回はReactアプリにGoogle認証を組み込む方法を試しました。

今回はそのコードを使いながら、さらにGoogle Drive APIを使って任意のデータをログインしているユーザーのGoogle Driveに保存することに挑戦してみました。


完全にブラウザのみで動作するReactアプリケーションなので、バックエンドサーバは不要です。Netlifyなど静的サイトのホスティングサービスを使ってデプロイすることが可能です。


Screen shot on a PC

Screen shot on a phone


  • Google Drive内の所定のフォルダからファイル一覧を表示。
  • ファイル名がクリックされるとその内容(テキストデータ)を表示。
  • テキストを入力・編集して保存ボタンを押し、ファイル名を入力するとGoogle Driveにテキストデータを保存。

ということを行っています。



2つのContextプロバイダー


認証状態とアプリケーション固有の状態を分けて2つのContextプロバイダーで別々に共有するようにします。

import React from 'react';
import { FileList } from './FileList';
import { FileContent } from './FileContent';
import { AuthProvider } from './auth-state';
import { AppStateProvider } from './app-state';
import { Header } from './Header';
import './App.css';

function App() {
return (
<AuthProvider>
<AppStateProvider>
<div className="App">
<Header />
<FileList />
<FileContent />
</div>
</AppStateProvider>
</AuthProvider>
);
}
export default App;



Google Drive APIの有効化とAPI Keyの作成


下のドキュメントにしたがってGoogle Drive APIを有効化し、API Keyを作成しておいてください。

https://developers.google.com/drive/api/v3/quickstart/js



Google DriveへのCRUD処理


次にGoogle Driveにアクセスする処理を「google-api.js」に記述します。

google-api.js
---
---

前回作成した認証関係の関数に加えて、

 getFiles ファイル一覧取得  
 getFileContent     ファイル内容取得
 uploadFile ファイル保存
 deleteFile ファイル削除

などの関数をエクスポートしています。



アップロード処理がなかなか動かず苦労しましたが、最終的には下のサイトの情報にしたがってMultipart形式でPOST(更新のときはPATCH)リクエストを送ることで解決しました。


またファイルの内容を取得する方法も最初は分からず試行錯誤しました。こちらは下のURLにGETリクエストを送ることでダウンロード出来ました。

https://www.googleapis.com/drive/v3/files/${fileId}?alt=media&source=downloadUrl

もちろんリクエストヘッダーにはGoogle認証で得られたアクセストークンを付加する必要があります。


ファイルの保存先フォルダは現状では APP_FOLDER という定数で定義していますが、これはユーザーが任意のフォルダ名を指定出来るようにした方が便利ですね。


以上、今回のサンプルを作成してみて、バックエンドサーバ無しでもブラウザ上のJavaScriptだけでかなり柔軟にGoogle Driveへのデータ保存が出来るということが分かりました。


単純なテキストではなく例えばJSONを保存すれば、ある意味簡易的なデータベースとしても使えるかも知れませんね。何よりアプリの開発者側で保存先のデータベースやストレージを用意する必要が無いのが魅力的です。



今回作成したアプリケーションのソースコードはこちらにあります。






 

2020年6月14日

ReactのSPAアプリケーションでContextとHooksを使ってGoogle認証を実装する

ReactのSPAアプリケーションにおいて、ContextとHooksを使うことでGoogle認証の処理をラップして利用するというサンプルを作成しました。


Google Cloud Consoleでアプリケーションを登録する




OAuth consent screenを設定する  







OAuth client IDを作成する








Create React AppでReactアプリケーションを作成する


npx create-react-app project-name
cd project-name


public/index.html を編集する


WebアプリケーションでGoogle認証を利用するための最もシンプルなサンプルコードがこちらにあります。

Google Sign-In for Websites  |  Google Developers 

<html lang="en">
  <head>
    <meta name="google-signin-scope" content="profile email">
    <meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
    <script src="https://apis.google.com/js/platform.js" async defer></script>
  </head>
  <body>
    <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
    <script>
      function onSignIn(googleUser) {
        // Useful data for your client-side scripts:
        var profile = googleUser.getBasicProfile();
        console.log("ID: " + profile.getId()); // Don't send this directly to your server!
        console.log('Full Name: ' + profile.getName());
        console.log('Given Name: ' + profile.getGivenName());
        console.log('Family Name: ' + profile.getFamilyName());
        console.log("Image URL: " + profile.getImageUrl());
        console.log("Email: " + profile.getEmail());
        // The ID token you need to pass to your backend:
        var id_token = googleUser.getAuthResponse().id_token;
        console.log("ID Token: " + id_token);
      }
    </script>
  </body>
</html>

これを参考に public/index.htmlを編集します。

上のサンプルコードの中でReactアプリで必要になるのはGoogleのスクリプトを読み込んでいる行だけなので、その部分をコピーしてheadタグ内に挿入します。

    <script src="https://apis.google.com/js/platform.js"></script>

Googleのサンプルコードでは「async defer」という属性が付いていますが、Create React Appの場合これがあるとスクリプトが実行されるタイミングの関係で上手く動かないので削除しています。



.env.localファイルを作成する


次にプロジェクトのルートフォルダに「.env.local」 ファイルを作成して値を設定しておきましょう。

REACT_APP_CLIENTID=41702000-xxxxxxxxxxxxx.apps.googleusercontent.com

この環境変数の名前は、Create React Appの規約上「REACT_APP_」から始まる必要があるので注意してください。

ClientIdを環境変数に持たせておくことによって、本番用、ステージング用など、ビルド環境に応じて柔軟に変更することが出来るようになります。


src/google-auth.jsファイルを作成する


Google APIを実際に呼び出す際に使う関数をこのファイルにまとめておきます。

init(), signIn(), signOut()の3つの関数をエクスポートしています。
------


src/auth-state.jsファイルを作成する


上のgoogle-auth.jsに定義した関数をReactアプリ内で使うためのContextプロバイダーとフックを提供するファイルになります。

内部的にはuseReducerを使って認証状態を管理していますが、それはファイル内に隠蔽して外部へはAuthProviderコンポーネントとuseAuthStateフックをエクスポートしています。

useAuthStateフックからの戻り値は、認証状態を表す state オブジェクトとサインイン、サインアウト処理を呼び出すための関数を提供する actions オブジェクトの2要素配列となっています。

---
---


ここまで準備が出来れば、あとは

1. AuthProviderコンポーネントでアプリをラップする。
2. 任意のコンポーネント内でuseAuthStateフックを使って認証機能にアクセスする。

という形になります。


App.js
function App() {
  return (
    <AuthProvider>
      <div className="App">
        <header className="App-header">
          <h1>Google Auth React Examle</h1>
          <SignInOutButton />
        </header>
        <div className="App-main">
          <UserInfo />
        </div>
      </div>
    </AuthProvider>
  );
}


SignInOutButton.js
 
import React from 'react'; import { useAuthState } from './auth-state'; 
export function SignInOutButton() {
  const [state, actions] = useAuthState();
  const { isSignedIn } = state;
  const { signIn, signOut } = actions;
  if (isSignedIn === undefined) {
    return null;
  }
  return (
    <div>
      {isSignedIn ? (
        <button onClick={signOut}>Sign Out</button>
      ) : (
        <button onClick={signIn}>Sign In</button>
      )}
    </div>
  );
}




今回作成したアプリケーションのソースコードはこちらにあります。





以上、ReactのSPAアプリケーションにおいて、ContextとHooksを使ってGoogle認証の処理をラップするサンプルでした。



次回は、認証した後さらにGoogle Drive APIを使ってアプリケーションのデータを(バックエンドサーバー無しで)ブラウザから直接Google Driveに保存するということに挑戦したいと思います。





 

2020年5月23日

Macの音声読み上げ機能を言語別にキー一発で呼び出す方法

読み上げ機能は目に優しい


最近、ブラウザでニュースを読む時にMacの音声読み上げ機能を使うと、「目を閉じていてもニュースが頭に入ってくる」ことに気付きました。


これは慣れるとかなり便利。なんと言っても目の疲れが大幅に軽減できることが最大の利点です。







もちろんMacの標準状態でも、ブラウザで文章を選択して「Edit」メニュー → 「Start Speaking」を選べば読み上げ機能を使うことは可能です。

アクセシビリティの設定で任意のショートカットキーを設定することも出来ます。



もし一つの言語しか読み上げさせる必要がないのであれば、これで大丈夫です。


ただ、複数の言語を使い分けたい場合は、これだと読み上げに使用する言語(音声)をその都度選ぶことが出来ません。

日本語の音声を設定している状態で英語の文章を読み上げさせても、カナカナ読みの変な発音でしか読まれません。逆に英語の音声が選択されている状態で日本語を読み上げさせようとしても、上手く発音してくれません。

つまり、選択されている文章の言語によって読み上げに使う音声を切り替える必要があるわけです。


これが出来るように今回設定した方法を、以下にメモしておきます。


1. 言語別にAutomatorでサービスを作成


Automatorを起動して、新しい Quick Action を作成します。




Actionsから System → Speak Text を選んで、ドラッグアンドドロップで追加します。




読み上げに使う言語に対応した音声を選んで、後から分かる名前を付けて保存します。


これを、使いたい言語の数だけ繰り返します。



2. キーボードショートカットを設定


システム設定から「Keyboard」を選ぶと、「Services」のカテゴリ内に先ほどAutomatorで作ったQuick Actionが表示されます。




それぞれの言語に対応したQuick ActionのチェックをONにして、好きなショートカットキーを設定すれば完了です。

私の場合は、

Ctrl + Cmd + w  → 日本語読み上げ
Ctrl + Cmd + e  → 英語読み上げ

としています。

なるべく片手ですぐ押さえられる組み合わせが良いと思います。


ブラウザを開いて何らかの文章を選択状態にすると、Servicesメニューに上で作成したQuick Actionが追加されているのが分かります。






この設定で、Google Chrome、Safari, Firefox のいずれのブラウザでも文章を選択してキー一発で言語別に読み上げ機能を使うことが出来るようになりました!







 

2020年5月7日

Firebase Hostingにデプロイした静的サイトにFirebase Analyticsを追加する(Next.js)

まずは、Firebaseの管理画面から、該当のプロジェクトでAnalyticsを有効化します。


Google アナリティクスを使ってみる






ここではNext.jsからエクスポートした静的サイトを例にします。

pages/index.js など、全てのページで共通に含まれる部分で、下のようにHeadコンポーネントを使ってscriptタグを埋め込みます。

要は下の赤字部分の3行のscriptタグがHTMLのhead部分に含まれるようにすれば良いということですね。

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
        <script src="/__/firebase/7.14.2/firebase-app.js"></script>
        <script src="/__/firebase/7.14.2/firebase-analytics.js"></script>
        <script src="/__/firebase/init.js"></script>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
      </section>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              <Link href="/posts/[id]" as={`/posts/${id}`}>
                <a>{title}</a>
              </Link>
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}


次に、下のドキュメントの通りにイベントを送信する必要があります。

イベントをロギングする

このために、「send-ga-event.js」 と 「use-send-screenview.js」という2つのファイルを作成しました。


send-ga-event.js

export function sendGAEvent(eventName, params) {
  const firebase = window && window.firebase;
  if (!firebase) return;
  try {
    firebase.analytics().logEvent(eventName, params);
  } catch (error) {
    console.error(error);
  }
}


use-send-screenview.js

import { useEffect } from 'react';
import { sendGAEvent } from './send-ga-event';
export function useSendScreenView(screenName) {
  return useEffect(() => {
    const params = { screen_name: screenName };
    sendGAEvent('screen_view', params);
  }, []);
}

上で定義したuseSendScreenView()という名前のフックを pages/index.jsから呼び出すことでAnalyticsにイベントを送信します。


pages/index.js

import { useSendScreenView } from '../utils/use-send-screenview';
export default function Home({ allPostsData }) {
  useSendScreenView('home');
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
        {process.env.NODE_ENV === 'production' && (
          <>
            <script src="/__/firebase/7.14.2/firebase-app.js"></script>
            <script src="/__/firebase/7.14.2/firebase-analytics.js"></script>
            <script src="/__/firebase/init.js"></script>
          </>
        )}
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>

(以下省略...)


これらの変更を行ったあと、

npm run build && npm run export
firebase deploy 

でデプロイしてサイトを更新します。

更新後のページを開くと、数秒でAnalyticsの画面にイベントが表示されるのが確認出来ます。








  

Next.jsで静的サイトを出力してFirebase Hostingでホスティングする

まずは公式のドキュメントにしたがってNext.jsのアプリケーションを作成しましょう。

Create a Next.js App | Learn Next.js 


package.jsonファイルのscriptsセクションに下記を追加しておきます。

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export"
  },


npm run build && npm run export を実行して outフォルダに結果が出力されるのを確認します。



次にFirebase CLIを初期化します。


firebase login

firebase init



カーソルキーで「Hosting」を選択してスペースキーを押し、Enterで確定です。

Firebaseの管理画面でまだ「プロジェクト」を作成していない場合は、プロジェクトIDと名称を入力して新規作成します。既に使われているIDを入れてしまうとエラーになるので、ユニークなIDを入力しましょう。

アップロード対象のフォルダを聞かれるので、デフォルトの「public」ではなく「out」を指定します。


firebase deploy を実行して、「Deploy complete!」と表示されれば完了です!


慣れれば3分もかからずに出来てしまいますね。


バックエンド処理にCloud Functionsを使ったりするような場合も、下のドキュメントの通り設定すれば決して難しくはありません。

本当に便利な世の中になったものです。^^)


あ、特にFirebase Hostingでなくても良いのであれば、もちろんNext.jsの開発元であるVercel(旧Zeit)社のサービスを使うのが一番簡単でオススメです。



こちらであれば、静的サイトでなくSSRありのサイトであっても、またバックエンドのAPIが必要な場合でも、何も考えずにデプロイしてしまえばあとはサービス側で上手くやってくれるみたいなので、こちらも素晴らしいと思います。







 

2020年5月4日

ReactでSVGファイルをコンポーネントとしてレンダリングする

egghead.ioのレッスンを観ていて良さそうなものがあったので、メモしておきます。


Add SVGs as React Components with Create React App 2.0
https://egghead.io/lessons/react-add-svgs-as-react-components-with-create-react-app-2-0

Create React App のドキュメントにも記述があります。
https://create-react-app.dev/docs/adding-images-fonts-and-files


Create React Appで作ったReactアプリケーションでは画像ファイルを下のようにインポートしてコンポーネント内で参照することが出来ます。

import logo from './logo.png';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

もちろん画像の形式がSVGであっても同様です。

import logo from './logo.svg';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

ただ、SVG形式の場合はインポートの仕方を下のように変えるとReactコンポーネントとしても使えるようになるそうです。

import { ReactComponent as Logo } from './logo.svg';
function App() {
  return (
    <div className="App">
<header className="App-header">
<Logo className="App-logo" />
</header>
</div>);
}


このようにすると、HTMLとしてレンダリングされる際のタグが imgタグではなく、svgタグになります。

svgタグになってうれしい点は、SVG内の個々の要素(図形)をCSSでコントロール出来るようになることです。


例えば、線の色や太さを変えたり、アニメーションを付け加えたりすることが可能になります。


下の例では、Reactロゴのpathエレメントの部分のみ点線にして、さらに点線のオフセットをアニメーションで動かしています。

.App-logo path {
  stroke: palegoldenrod;
  stroke-width: 10px;
  fill: none;
  stroke-dasharray: 35px 15px;
  animation: orbit 1s infinite linear;
} @keyframes orbit {
  to {
    stroke-dashoffset: 50px;
  }
}

実行するとこんな感じになります。