20201028のReactに関する記事は7件です。

【React×TS】ジェネリックなコンポーネントの型定義をしようとしたら高カインド型にぶつかった

ジェネリックなコンポーネントの型注釈

ジェネリックじゃないコンポーネントなら、こんなふうに、const宣言に型を書くだけで、引数と戻り値の型まで推論してくれる。しかもchildrenまで推論してくれているので宣言しなければいけない箇所が減る。

const Comp: React.FC<{value: string}> = ({value, children}) => {
    return <div></div>
}

しかし、コンポーネントにジェネリクスを付けた途端にそのような宣言は出来なくなる。
型変数Tを宣言したあとでしかTを使うことが出来ないからだ。
引数と戻り値にわざわざ型注釈を付けて、覚えるまではReactの型定義を見て正しい型名を探さないといけない。美しくないし。

interface Props<T extends string>{ // Props<"apple" | "banana">というふうに使える
    values: Record<T, string>,     // 例: { apple: "りんご", banana: "バナナ" }
    initial: T
}
const Comp = <T extends string>(prop: PropsWithChildren<Props<T>>): ReactElement => {
    return <div></div>
}

const App = () => <Comp values={{apple: "Apple"}} initial="apple" />

高カインド型の導入

そこで、活用できるのがHigher Kinded Type(高カインド型)である。React.FC<型>としていたところを、GenericFC<カインド>にすることで、型引数を受け取れるようにできるはずだ。やはりScalaをやっておいてよかった。
TypeScriptで高カインド型(Higher kinded types)--Qiita
を参考にして実装してみたが…

generic-fc.d.ts
interface HKT<T> {} // globalで宣言する

export type GenericFC<K extends keyof HKT<any>> = <T>(
     ...args: Parameters<React.FC<HKT<T>[K]>>
) => ReturnType<React.FC<HKT<T>[K]>>;
comp.tsx
import { GenericFC } from "./generic-fc"

declare module "./generic-fc" {
    interface HKT<T> {
        props: Props<T> // ここで"props"カインドを定義
    }
}

interface Props<T extends string>{
    values: Record<T, string>,
    initial: T
}

const Comp: GenericFC<"props"> = (props) => { //ここでカインドを使っている
    return <div></div>
}

const App = <Comp values={{apple: "Apple"}} initial="apple" />

(シンタックスハイライトがおかしくなるので2つに切りました。)
Record<T, string>型のTはオブジェクトのキーなのでstring | number | symbol型でないとダメで、今回はT extends stringとしているが…
型エラー
HKT<T extends string>としないと、このように型の制約を満たさなくなってしまうが、もしそうしてしまうと、他で定義するすべての同様のジェネリクスに影響してしまう。

どうにかしてこの個別のProp型だけに型制約を付けるかと考えるたすえ、このようにすると成功した。

generic-fc.d.ts
interface HKT<T> {}

type GenericFC<K extends keyof HKT<any>> = <T>(
    ...args: Parameters<React.FC<HKT<T>[K]>>
) => ReturnType<React.FC<HKT<T>[K]>>;
comp.tsx
import { GenericFC } from "./generic-fc"

declare module "./generic-fc" {
    interface HKT<T> {
        props: T extends string ? Props<T> : never
    }
}

interface Props<T extends string>{
    values: Record<T, string>,
    initial: T
}

const Comp: GenericFC<"props"> = (props) => {
    return <div></div>
}

const App = <Comp values={{apple: "Apple"}} initial="apple" />

const ShouldFail = Comp<number>({}) //これがちゃんとエラーになる

T extends string ? Props<T> : neverのところは Conditional Typesになっていて、T extends stringのときのみHKT<T>["prop"]からProp<T>得られるが、そうでないときにはnever型となり、意図どおりの型エラーが起きてくれる。

めでたしめでたし。

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

gatsby(react)にNoto Sans JPを入れてMaterial-UIのthemeでfont-familyを上書き指定する

メモです。

参考:
https://www.npmjs.com/package/fontsource-noto-sans-jp
https://mrtry.hatenablog.jp/entry/2018/05/07/193920

インストール

npm install fontsource-noto-sans-jp
もしくは
yarn add fontsource-noto-sans-jp

でNoto Sans JPをインストールする。

インポート

gatsby-browser.js

import { ThemeProvider } from 'styled-components'
import { MuiThemeProvider } from '@material-ui/core/styles'
import { theme } from './src/utility/theme'
import "fontsource-noto-sans-jp"
import "fontsource-noto-sans-jp/500.css"
import "fontsource-noto-sans-jp/900-normal.css"

などする。

Material-UIのthemeのfont-familyを上書き指定

import { createMuiTheme } from '@material-ui/core/styles'

// fontFamilyを上書き
export const theme = createMuiTheme({
  typography: {
    fontFamily: ['Noto Sans JP', 'sans-serif'].join(','),
  },
})

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

Reactを実際に動かしてみる

create-react-appを導入するところまではやったので、これからReactを使って、どうやってwebページを表示させるのかのアウトプットやります

本番環境で公開するフォルダの作成

ターミナル
npm run build

これでアプリケーション内にbuildフォルダが作成されました。

その後

ターミナル
npm start

を入力するとブラウザが立ち上がる

スクリーンショット 2020-10-28 10.51.46.png

Edit src/App.js and save to reload
と書いてある通り
srcフォルダの中にApp.jsというファイルがあると思います。

App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

このファイルの中のLearn Reactの部分がブラウザで確認できる青文字で下線が引かれている部分に当たります
ここを別の文字列に変えればその文字列が同様にブラウザで確認ができる様になります

実際にこのページのビューを作っているファイルというのはindex.htmlというファイルです

ではなぜApp.jsの内容がwebページで表示されているのかは、

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

このindex.jsファイルの<App />の部分で App.jsを呼び出して、それを
document.getElementById('root')の部分でrootというidに入れ込みますよと記述してあるんです。
そしてのそのrootというidはどのことを指しているのかというと、、、

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!-- <link href="https://fonts.googleapis.com/css2?family=Open+Sans+Condensed:wght@300&display=swap" rel="stylesheet"> -->
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>My Favorite Things</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

ここに<div id="root"></div>とあります
そう、こいつのことです!!

流れとしては
index.html :「rootを表示するで」
index.js :「rootはApp.jsに任せるわ」
App.js :「これ見したる」

ちょっと違うけど変数を使って、どんどんと代入されていっているイメージですかね
こんな感じでwebページに表示させるところまでは完了!!
create-react-appを使うとこんなに簡単にReactでアプリケーションの土台が作れるんですね〜
☆☆ SU・GO・I ☆☆






それよかJSの知識不足がすごいな、、、
少し勉強したからReactも出来っかなとか楽観的に考えていたけどそんなこともないんかね
React触っていればJSの復習も出来るからって考えで良いのか、それ以前にJSをもっと勉強するべきか、、、

うーん、わからん!!
どっちが良いのか誰かエロい人教えて下さい!!(>人<;)

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

React + Amplify環境構築手順

React + Amplify環境構築手順

ReactでCognito認証を作成したため、本記事にまとめました。

Amplifyは、AWSが提供するWebアプリの開発を支援するためのフレームワークで、Cognito認証などがAPIとして提供されているもので、
他にもホスティングの機能などもありますが、今回は使用しないため、Cognito認証に特化して手順を説明します。

WebアプリのフレームワークはReact(Typescript)とする。

[前提条件]
create-react-appでReact実行環境は作成済みの状態とする。

1. Amplifyのインストール

$ npm install aws-amplify aws-amplify-react @aws-amplify/ui-react

2. index.tsxにAmplifyの認証情報追加

import Amplify from 'aws-amplify';

Amplify.configure({
  Auth: {
      region: 'ap-northeast-1',
      userPoolId: 'ユーザープールID',
      userPoolWebClientId: 'アプリクライアントID'
  }
});

ユーザプールIDとアプリクライアントIDは、AWSコンソールのCognitoの画面から取得できる。

AWSのサービスを直接Webアプリから制御する場合は、IDプールの設定も必要だが、API Gatewayに対するREST APIでのみ制御するので、一旦IDプールの指定は不要とする。

3. 認証

aws-amplifyのAuthオブジェクトの各関数をコールすることで、サインインやサインアウトはできた。

import { Auth } from 'aws-amplify';

//サインイン
Auth.signIn(userSigninInfo.name, userSigninInfo.password)

//サインアウト
Auth.signOut()

4.トークン取得

AuthでcurrentSessionで取得できた。

import { Auth } from 'aws-amplify';

    useEffect(() => {
        Auth.currentSession()
            .then((data: any) => {
                setIdToken(data.getIdToken().getJwtToken());
            }).finally(() => {
                setLoading(true);
            });
    }, []);

上記取得したトークンをAPI GatewayのREST APIコール時にヘッダに付与すると、API GatewayでCognitoで認可していた場合、正常に動作する。

[参考URL]

・Amplify使い方1
https://qiita.com/herohit-tool/items/7a7e7bf3cbfa5b7ddff6

・Amplify使い方2
https://qiita.com/Engineer_Grotle/items/fa37a3924d1e66082889

・トークン取得
https://dev.classmethod.jp/articles/swagger-ui-with-amplify-react/

・Amplify公式
https://docs.amplify.aws/ui/auth/authenticator/q/framework/react#custom-form-fields

・AmplifyをHook化
https://qiita.com/G-awa/items/99cb84c62fcd113943a6

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

React とか いろんなところ で役に立つ三項演算子

React とか いろんなところ で役に立つ三項演算子

三項演算子とは以下のようなもの

isflag ? console.log("いいよ") : console.log("ダメだよ");

if 文だと

if (isflag) {
  console.log("いいよ");
} else {
  console.log("ダメだよ");
}

何が役に立つの?

三項演算子は、非常にスマートに書ける。
しかし、見にくい

コードが見にくくなってしまう。

という問題があるらしい

使いどころ アロー関数

js には、アロー関数というのがある。

() => console.log("アロー関数");

map や Object.key などでよく私は、使います。
ここで、if 文を書くとします。

const a = [1, 2, 3, 4, 5];
a.map((data) => {
  if (data == 2) {
    console.log(data);
  }
});

// ====log=====
// 2

2 が表示されます。
こういった書き方よりもこう書いたほうがコードが見やすいと思います。

a.map((data) => (data == 2 ? console.log(data) : null));

まとめ

コードは書いていると、長くなります。
長いコードをスクロールするのも大変だと思っています。

条件式をしたとしても表示するかしないか、出力するかどうか
などで数行分書くのは、大変です。

React などでは、状態によって表示するかどうかを決める時があります。そういったとき IF 文なのか三項演算子なのかは、好みによります。

私は、単純な処理なら三項演算子を使います。
好みです!!

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

【Netlify × Gatsby.js事件簿】企業HP/LPで一部Netlify機能は使わないほうが幸せになるかもしれない

なにがあったのか?

インターン先のLPサイト(ランディングページ)の開発を引き継ぎ、追加ページや機能の開発をGatsby.jsとNetlifyを使って開発をしています。
サイトに必要な機能に応じて、Netlifyの一部サービスを活用していましたが、うまくいかず。
開発中に起きた数々の事件の備忘録として今回の記事を残します。

開発に携わったLPサイト(https://www.synq-platform.com/)

Netlify × Gatsby.js事件簿

【File1】動かない!Form事件
【File2】まさかの!?プラグイン事件
【File3】ブラックボックス!ログなしエラー事件

登場人物の紹介

Netlify

image.png
https://www.netlify.com/

  • 静的コンテンツのホスティングサービス
  • 少ない費用でもいろんなことができる
  • Githubと連携しているので、はじめてのデプロイも簡単!

Gatsby.js

image.png
https://www.gatsbyjs.com/

  • Reactベースの静的サイトジェネレーター
  • Reactで作成したソースから、静的なHTML/CSS/Javascriptのサイトを生成できる
  • 生成されたサイトは閲覧までの時間が高速である特徴を持っている

【File1】動かない!Form事件

NetlifyにはFormという機能があります。

無料版でも以下のことができます
- 指定したGitのブランチがデプロイされたら、すぐに自動でビルドしてくれる
- 任意のバージョンへ即時ロールバック可能
- 月あたり100投稿、10MBまで可能
- フォームに送信されたデータがあれば、Slackや指定メールに通知が飛ぶ
https://www.netlify.com/products/forms/

特徴に惹かれ、いざ開発すると…

【case1】設定したThank youページが突然表示されなくなった

Netlify Formはactionでフォーム送信後に表示するページを設定することができます。

Form
<form name="contact" method="POST" data-netlify="true" action="/thank-you">
  <p>
    <label>Your Name: <input type="text" name="name" /></label> 
  </p>
  <p>
    <label>Message: <textarea name="message"></textarea></label>
  </p>
  <p>
    <button type="submit">Send</button>
  </p>
</form>

https://www.gatsbyjs.com/docs/building-a-contact-form/

はじめは、作成したthank youページへ飛ぶことができたけれど、stagingをmasterへpushしたところ、Netlifyのデフォルトthankページしか表示されない状態になりました。
URLを直接たたけばthank youページは表示できますが、フォームの送信ボタンを押しても表示することはできませんでした…。(原因は調査中)

【case2】POST先が404

作成したフォームに入力し送信すると、なぜか404ページにPOSTされるようになっていました。
どうやらNetlifyのおまじないをしても、Netlify Formであると認識されていない状態に。

◆Netlifyのおまじない

Gatsby.js
- <form method="post" action="#">
+ <form method="post" netlify-honeypot="bot-field" data-netlify="true" name="contact">
+   <input type="hidden" name="bot-field" />
+   <input type="hidden" name="form-name" value="contact" />
  ...

https://www.gatsbyjs.com/docs/building-a-contact-form/

フォームの送信ができるようになっても、データが取得できておらず、空っぽに…
image.png

原因は、File2で紹介するまさかのプラグイン。

【File2】まさかの!?プラグイン事件

Gatsbyではnpm などでインストールしたものをimportすれば簡単に利用できますが、プラグインがうまくかみ合わないことがあります。

【case1】怪しくないやつが、実は犯人

File1-case2で紹介したまさかのプラグイン、犯人はreact-sliderEmbedded YouTube
react-sliderは写真が横に流れるアニメーションを実現するプラグインで、
Embedded YouTubeはyoutubeに投稿している動画をサイト上で再生できるプラグイン。

これは関係ないだろう!なんてものは無い。

懐疑は発明の父である。
Doubt is the father of invention.
『Galileo Galilei ガリレオ・ガリレイ』

【case2】突然のレイアウト崩壊

material-uiで<Link>をimportして作成していたとき、検証画面・localhostでは正常に動いていたCSSがstagingへpushした瞬間、縦スクロールページから横スクロールページに。

TOP Page
  └ Download Page
  └ Contact Page  ←ここで別のmaterial-uiを利用していた

上記の図に書いてある通り、原因はTOP以外のページでmaterial-uiを使用していたことでした。

【File3】ブラックボックス!ログなしエラー事件

最大の事件ですが、Netlify系の不具合は明確なエラーが表示されません。

どういうことか?というと、
これまでの事件はエラーも出ていないのになぜかうまくいかないの連続でした。
不具合を解決しようにも、同じような事例が見つかりにくく、採用しているプラグイン同士が影響していれば、しらみつぶしに検証するしかないという状態になっており、ブラックボックスの塊という状態です。

【個人的な結論】Netlify × Gatsby.jsの使いどころ

 これまでの内容から、Netlify × Gatsby.jsは凝ったサイトや企業LP/HPで使用する際は、Netlifyのサービスに頼りすぎないほうが良いでしょう。
 凝ったサイトをつくると導入するプラグインが多くなって事故を起こす可能性が高まり、
製品お申込みフォームなど、フォームが重要な役割を担う場合、Formが正常に動かず業務に支障が出る場合もあるからです。
また不具合が起きてもエラーログは閲覧できないため、急いで改善しなければならない、という状況では不利です。

では、Netlify × Gatsby.jsはどんなときに使用するのが好ましいか?といえば

  • Reactに慣れたい
  • 個人のポートフォリオや自己紹介サイトをつりたい
  • シンプルなシングルページが作りたい
  • 納期や作業時間に余裕がある!

という状況だと思います。

おわりに

あらためて、今回の開発を経て、Gatsbyで作成したサイトでNetlifyの機能をフル活用は難しいと感じました。
何としても解決したい、という熱意はもちろんありますが、時間は有限。
そのため、企業サイトなどスピード感が求められる開発では無理に追わず、別のサービスを検討する大切さがあるのだと学びました。(例:FormであればFormspreeGetformなど )

現在、自分のログ&ポートフォリオサイトをNetlify × Gatsby.jsで作成中ですが、Pagesにjsファイルを置くだけで、複数のページを作成でき、masterブランチへpushしただけでビルド、公開をしてくれるNetlifyは、やはり便利です。
だから、どうすれば少ない不具合で最大限活用できるのか、今後ゆっくり検証していきたいと思っています。

Netlify × Gatsby.jsで苦しんでいる方の少しでも助けに慣れれば幸いです。

さいごに

現在インターンをしている株式会社クアンドでは、
記事に取り上げたSynQ Remoteというサービスを2020年11月2日にリリースします!

参考サイト

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

React: Google Chrome for macOSでメモリリークが起こる

Google Crome for macOSで、メモリの消費が増え続けるという現象に遭遇しました。英語を含めても情報が少なかったので、備忘録としてまとめます。

Simple app causing a memory leak?

(2018/02/17)

リスナーが増え続けて、メモリが費やされるという問題が示されました。

それに対して、ReduxやCreate React Appの開発に携わったDan Abramov氏は、つぎのようにコメントしています(なお「React開発者ではありません」とは、ご自身の紹介文)。メモリ消費が増えているのはリスナーを膨大につくっているからだというのです。メモリリークではなく、開発ビルドでのみ起こり、プロダクション版では生じないと説明しています。また、リスナーもガベージコレクションにより片づけられるそうです。

Dan Abramov氏によるコメント(2018/02/19)

Fairly sure this isn't a memory leak but just excessive listener creation. It only happens in development builds, not in production. You can see these thread for more details: facebook/react#10576 and facebook/react#12141. We do create listeners but we expect GC to clean them up.

この問題はDan Abramov氏が同日にクローズしました。

Apparent Slow (25 Bytes per Render) Memory Leak with React / React-Dom?

(2017/08/31)

John Tucker氏により、メモリリークがわずかずつ増えるという問題が報告されました。簡単なテストサンプルで、現象が再現されています。けれど、2017年8月31日に同氏のコメントで撤回され、クローズされました。

テストの仕方がまずかったということです。ただし、ミスがあったというのではありません。ReactでなくChromeデベロッパーツールの問題だというのです。デベロッパーツールのメモリ消費が大きく、ツールを閉じるだけでタスクマネージャのメモリ使用量は減りました。再度ツールを開くと1時間につき1MBほどずつアプリケーションのメモリ使用量が増えたということです。

ガベージコレクションを実行できるようデベロッパーツールを開いていたものの、閉じればメモリ消費は適宜減り、ガベージコレクションが適切に働いたといいます。

John Tucker氏によるコメント(2017/08/31)

NEVERMIND.... It appears that my testing methodology was broken and it does NOT look like there is a problem with React (rather a problem with Chrome). I just so happened to be testing my actual application and noticed that my Chrome "Tab: Developer Tools" was insanely large (70+ MB). When I closed it, I noticed that Chrome's Task Manager stopped showing the steady growth in the application that I was troubleshooting. I was seeing growth like 1 MB per hour in the application itself when Developer's Tools was open.

I went back to the React test that I did here and this time left the Chrome Developer's Tools CLOSED and just watched the Task Manager. Originally, I left Developer's tools open so that I could trigger a GC.

Without Developer's tools open, I saw the automatic GC process get triggered every so often and the memory would regularly drop way down to 5MB.

React16 dev memory leak on render with event listeners

(2018/02/03)

スクロールやキーボード入力のイベントリスナーが加えられたとき、すばやくイベントを起こすとメモリリークにより反応が遅れると報告されました。ただし、ほかの人たちは適切に再現できなかったようです。それに対して、Dan Abramov氏からコメントが寄せられました。

Reactが開発用に起こしているフェイクイベントは、レンダリングのためのもので、ユーザーイベントには関わらないといいます(該当コードはコメントのリンク参照)。

Dan Abramov氏によるコメント(2018/02/03)

I don't believe it has to do with generating events fast. It has to do with renders.

Because fake events are created every time we call into user code in DEV here.

Reactは開発モードで、レンダリングされるコンポーネントに、それぞれリスナーを割り当てます。フェイクイベントは、コンポーネントコードから例外スローを切り分けるためのものだということです。プロダクション版では、この処理は外されます。

Dan Abramov氏によるコメント(2018/03/01)

As I mentioned before, it is expected that React will allocate a listener on every component render in development mode.

We use fake events to "isolate" component code so that if it throws, the browser displays an "Uncaught error" message even if some code above in the call stack accidentally catches it. Similarly this is what makes "break on all exceptions" work even though React is wrapping your components in a try/catch.

This overhead doesn't exist in production versions.

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