20210321のJavaScriptに関する記事は29件です。

appendChildメソッド実行時にかかる負荷を減らすために

DocumentFragmentオブジェクト

appendChildはJSから文書ツリーに要素を追加できる非常に便利なメソッドです。しかし、appendChildは場合によってオーバーヘッド(負担)の高い処理にもなってしまいます。
そこで使われるのがDocumentFragmentオブジェクトです。
DocumentFragmentとは、文字通り「文書の断片」であり、「組み立てたノードを一時的にストップするたみに使う入れ物」です。
例を見てみましょう。



See the Pen
abBexpr
by Ryuji Watanabe (@ryuji0526)
on CodePen.




上記は、配列の値ををli要素に入れ込んで、ul要素配下に追加する簡単なコードです。
このコードはもちろん正しく動作しますが、パフォーマンス的にはあまり望ましくありません。なぜなら、li要素がul要素に追加するたびにコンテンツが描画されるからです。上記の例では、この5回コンテンツが描画されていることになります。最描画は比較的オーバーヘッドの高い処理になります。
今回のように少ない場合だと負荷を感じることはないと思いますが、配列の要素がどんどん増えると、負担になってしまいます。
このような状況では、DocumentFragmentオブジェクト上でコンテンツを組み立ててからまとめてul要素に追加すべきです。

以下のように修正します。

See the Pen abBexpr by Ryuji Watanabe (@ryuji0526) on CodePen.



このようにDocumentFragmentオブジェクトに貯めることで、文書ツリーの更新は一回だけなので、最描画にかかるオーバーヘッドを最小限に抑えることができます。

おわりに

大規模開発では、このように処理時の負担を意識しなければならないと思います。(私も初心者なので、なかなか意識してコードを組めません、、、)
以下に負担を抑えることができるか、考えてコードを組めるように心がけましょう!(私もがんばります!)

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

初めてのLINEbot作成【Node.js + heroku】

はじめに

Qiita初投稿になります
Node.jsによるLINEbot製作を行ってみたく、自学のために行った流れをまとめていきます
Node.js、npmのインストール、Herokuのアカウント登録などは省きます
成果物としては、「ありがとう」と送信すると、「どういたしまして」、それ以外は「こんにちは」と返ってくるLINEbotを制作します

動作環境

・MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)
・mac OS Catalina バージョン 10.15.7
・node v15.3.0
・npm 7.0.14
・git 2.23.0

目次

  1. LINE側の準備
  2. Node.js側の準備
  3. コードの作成
  4. 応答テスト
  5. 本番環境(heroku)にデプロイ
  6. 参考文献

LINE側の準備

この見出しでは、LINE側で行うことをまとめていきます。

LINE Developerアカウントの作成

まずはこちらのURLからLINE Developerのアカウントを作成します
普段使用されているLINEアカウントがあればすぐに作成できます
https://developers.line.biz/ja/

プロバイダーの作成

次にプロバイダーの作成を行います。トップページにある以下の作成ボタンを押します。
スクリーンショット 2021-03-19 17.28.36.png
ここでは新しく作るプロバイダーをtestとしておきます。
スクリーンショット 2021-03-19 17.29.43.png

チャネルの作成

新しくプロバイダを作成すると以下の画面に移行します。ここでは「Messaging API」を選択します。
スクリーンショット 2021-03-19 17.30.02.png
その後、チャネルの種類を「Messaging API」に指定し、プロバイダーを先ほど作成した「test」に指定します。その後
・チャネルアイコン ※一度変更すると、1日経過しなければ変更することができません
・チャネル名 ※一度変更すると、7日経過しなければ変更することができません
・チャネル説明
・大業種
・小業種
・メールアドレス
・プライバシーポリシーURL(任意)
・サービス利用規約URL(任意)
を入力し、利用規約にチェックを入れ、作成ボタンを押します
スクリーンショット 2021-03-19 17.32.20.png

チャネルシークレットとチャネルアクセストークンの取得

次に外部からLINE APIにアクセスするためのチャネルシークレットとチャネルアクセストークン(長期)を取得します。
こちらの情報は外部に漏れないように気を付けましょう。それぞれの場所は
・チャネルシークレット
 チャネル基本設定の下部にあります
・チャネルアクセストークン(長期)
 Messaging API設定の下部にあり、発行ボタンを押すと表示されます
上記二つが取得できたら、とりあえずはメモ帳等に記録しておきましょう。

その他設定

Messaging API設定タブの応答メッセージ、編集、にて応答設定の中の詳細設定の、応答メッセージを以下の画像のようにオフ、Webhookをオンにしましょう。あいさつメッセージはオフにしていますが、オンでも構いません。
※Webhook URLに関しては後ほど入力します
スクリーンショット 2021-03-19 17.59.27.png
こちらで一旦LINE側の準備は終わりになります。

Node.js側の準備

npm init

任意のフォルダ(ここではlinebot_test)を作成し、そのフォルダ内で、npmのイニシャライズを実行します

コンソール
mkdir linebot_test
cd linebot_test
npm init

npm init実行後、いろいろ聞かれますが、全て空のままEnterで構いません

モジュールのインストール

LINEbotを動かすために必要なnpmモジュールをそれぞれインストールします

コンソール
npm install express @line/bot-sdk dotenv --save

ローカルでテストするためのトンネリングツールをインストール

ngrokというトンネリングツールを使用することで、簡単にテストが行えます。

コンソール
npm install -g ngrok

Node側の準備はこのぐらいです、次からコードを書いていきます。

コードの作成

先ほど作成したフォルダ内で以下を実行し、index.jsを作成します

コンソール
touch index.js

その後、以下のコードをコピペします

index.js
// モジュールのインポート
const server = require('express')();
const line = require('@line/bot-sdk'); // Messaging APIのSDKをインポート
require('dotenv').config();
// -----------------------------------------------------------------------------
// パラメータ設定
const lineConfig = {
  channelAccessToken: process.env.LINE_ACCESS_TOKEN, // 環境変数からアクセストークンをセットしています
  channelSecret: process.env.LINE_CHANNEL_SECRET, // 環境変数からChannel Secretをセットしています
};
// -----------------------------------------------------------------------------
// Webサーバー設定
server.listen(process.env.PORT || 3000);
// -----------------------------------------------------------------------------
// ルーター設定
const bot = new line.Client(lineConfig);        // APIコールのためのクライアントインスタンスを作成
server.post('/webhook', line.middleware(lineConfig), async (req, res) => {
  res.sendStatus(200);                          // 先行してLINE側にステータスコード200でレスポンスする。
  const [lineEvent] = req.body.events;          // events配列から配列の0番目の要素だけを変数に代入
  const inputMessage = lineEvent.message.text;  //入力された文字を代入
  if (!lineEvent) {
    return;
  }
  if (lineEvent.type !== 'message' || lineEvent.message.type !== 'text') {
    return;
  }

  if (inputMessage === 'ありがとう') {           //「ありがとう」と入力された場合
    bot.replyMessage(lineEvent.replyToken, {
      type: 'text',
      text: 'どういたしまして',
    });
  } else {                                    //それ以外の文字が入力された場合
    bot.replyMessage(lineEvent.replyToken, {
      type: 'text',
      text: 'こんにちは',
    });
  }

});

そして環境変数のため、.envファイルを作成します。

コンソール
touch .env

.envファイル内は、LINE側の準備で取得したチャネルシークレットとチャネルアクセストークンを用意し、以下のコードに貼り付けてください

.env
LINE_ACCESS_TOKEN='メモしたチャネルアクセストークン'
LINE_CHANNEL_SECRET='メモしたチャネルシークレット'

フォルダ構成は以下のようになります
スクリーンショット 2021-03-19 18.48.35.png

応答テスト

いよいよテストになります。

LINEbotの友達登録

LINE DevelopeのMessaging API設定のボット情報にある「ボットのベーシックID」をコピーして、自分のLINEの友達検索、にてIDで検索すると作成したbotが出てくるので、そちらを友達にします

スクリーンショット 2021-03-19 23.28.47.png

このボット情報の下にQRコードが表示されているので、そちらから友達になっても構いません

ngrokの起動

コンソールを二つ立ち上げ、片方では

コンソール
ngrok http 3000

とすると、以下のようになるので

スクリーンショット 2021-03-19 18.58.29.png

https://~~~~.ngrok.io の部分をコピーし

LINE DevelopeのMessaging API設定のWebhook設定、webhook URLに貼り付け、後ろに「/webhook」を記入します

スクリーンショット 2021-03-19 19.02.24.png

index.jsの起動

もう一方のコンソールにて、

コンソール
node index.js

としてサーバーを起動します

応答テスト

いよいよテストです。
botに対して「ありがとう」と入力しましょう

すると、「どういたしまして」と返ってきます

それ以外の言葉を入力すると、「こんにちは」と返ってくるはずです。
スクリーンショット 2021-03-19 23.35.54.png

本番環境(heroku)にデプロイ

git init

herokuにデプロイする際はgitを使用するので、まずgit initを行います

コンソール
git init

git ignore

その後、チャネルアクセストークンなどが外部に漏れてしまわないように.gitignoreファイルを作成し、
以下のように記述しておきましょう

.gitignore
npm-debug.log
node_modules
.env

こうしておくことで、pushしても.envファイルが外部に漏れることはなくなります
また、node_modules npm-debug.logに関してもプッシュする必要がないのでignoreしておきましょう

heroku create

その後、herokuにログインし、heroku createを実施して新たにサーバーを作成しましょう
その後、herokuに環境変数を読ませるため、heroku config:setにて環境変数を定義しましょう

コンソール
heroku login
heroku create
heroku config:set LINE_ACCESS_TOKEN='メモしたチャネルアクセストークン'
heroku config:set LINE_CHANNEL_SECRET='メモしたチャネルシークレット'

Procfileの作成

次に、herokuがまず何を実行すればいいかを記したファイル「Procfile」を作成します

コンソール
touch Procfile

Procfile内は以下のように記します

Procfile
web: node index.js

デプロイ

いよいよデプロイです、以下のようにgit addを行い、git commitを行って、herokuにプッシュしましょう

コンソール
git add -A
git commit -m "コミット名"
git push heroku master

デプロイ後、LINE Developersのwebhook URLを変更

コンソールからherokuにデプロイを行うと、以下のようなログが流れます
スクリーンショット 2021-03-20 0.13.40.png
https://~~~~~.herokuapp.com/ の部分をコピーして、webhook URLに張り付けます

スクリーンショット 2021-03-20 0.16.48.png

この後、再度LINEbotにメッセージを送ってみましょう、localでテストしたときと同じ返答が帰ってくれば成功です

参考文献

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

【React】Redux Thunk での非同期処理

始めに

Redux の store に対して非同期処理で action を dispatch するときには工夫が必要になります。redux-thunk を用いて実現する方法を今回まとめてみました。
誤り等ありましたらコメントでご指摘いただけますと助かります :bow:

redux-thunk とは

Redux のミドルウェアのひとつで、これを用いることで純粋なオブジェクトだけでなく関数も action として dispatch することができるようになります。
非同期ロジックを組み込んだ関数を store が解釈できるようになり、適切なタイミングで dispatch を行ってくれます。
コンポーネント側からしたら ActionCreator の戻り値を dispatch しておけば後は store がうまくやってくれるので、コンポーネントのロジックの見通しが良くなります。

簡単な実例

export const App = () => {
  const tasks = useSelector(state => state.tasks);
  const dispatch = useDispatch();

  useEffect(() => {
    // APIからfetchしたデータをもとにstoreにdispatch
    dispatch(fetchTasks());
  }, [])

  return (
    <ul>
      {tasks.length && tasks.map(task => (
        <li key={task.id}>{task.name}</li>
      ))}
    </ul>
  );
}

例えば、上のようなコンポーネント。
Redux とのつなぎ込みは Hooks を使っており、また useEffect から dispatch を呼んでおります。

ActionCreator を下のように実装したとします。

export const fetchTasks = () => {
  fetch(api_path).then(response => {
    return response.json();
  }).then(tasks => {
    return { type: 'set_tasks', tasks: tasks };
  })
}

一見、action としてオブジェクトを return しているようにも見えますが、fetch API が同期的に返すのは Promise オブジェクトのため、dispatch が期待する結果にはなっていません。
下のエラーが発生します。

 Actions must be plain objects. Use custom middleware for async actions.

意図通りにやるには、promise が resolve されタスクキューに入ったコールバック処理が実行されるまで待ち dispatch を行う必要があります。

例えば、下のように非同期処理の結果を dispatch する関数を設けて、コンポーネントでこの関数を呼ぶやり方。

const fetchTasks = () => {
  fetch(api_path).then(response => {
    return response.json();
  }).then(tasks => {
    dispatch({ type: 'set_tasks', tasks: tasks })
  })
}

これでとりあえず動きはしますが、非同期処理と dispatch が混在していてコンポーネントの処理の見通しが悪くなってしまいます。また、dispatch 先の store に依存しているので mock でのテストなどがしずらそうです。

そこで、redux-thunk で Redux を拡張させ、非同期処理を含んだ関数をそのまま dispatch できるようにすることで処理をシンプルにしようとなります。

まずは、sotre 作成時にインポートした thunk を適用させます。

const store = createStore(reducer, applyMiddleware(thunk));

そしてActionCreator で、「dispatch を引数にとり、内部で(非同期処理後に) action をdispatch する関数」を返すようにします。

export const fetchTasks = () =>
  dispatch => {
    fetch(api_path).then(response => {
      return response.json();
    }).then(tasks => {
      dispatch({ type: 'set_tasks', tasks: tasks })
    })
  }

こうすることでロジックを Redux に寄せることができますね。
あとは冒頭記載通りにコンポーネントから dispatch(fetchTask()) をしてこの関数ごと dispatch してやれば、store 側でうまく対応をしてくれます。

拡張された store での処理

store では、まずミドルウェアが渡ってきた action がオブジェクトか関数かの判定を行います。オブジェクトであった場合は通常通り reducer に渡します。関数であった場合は dispatch を引数に関数を実行し (非同期処理などを経て)action が dispatch されるまで待ちます。そして、ここで disptach された apction がオブジェクトであったら今度は reducer に渡す、という流れです。
公式が貼っていたリンクの記事のアニメーションが分かりやすいです)
ちなみに、関数の第二引数として getState もとることができ、store の既存の state に関数内からアクセスして処理を分けることも可能です。

この流れを掴んだ上でソースを覗いてみると、この根幹の部分は意外とシンプルな実装であることが分かりますね。

scr/index.js
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

参考

https://github.com/reduxjs/redux-thunk
https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60
https://www.tohuandkonsome.site/entry/2019/02/05/231503
https://kde.hateblo.jp/entry/2019/02/14/220155

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

AWS for JavaScript で S3 の getObject で NoSuchKey を期待してたのに AccessDenied が返ってくる

問題

以下のようなコードコードを実行し

    let prev = null;
    try {
      prev = await S3.getObject({
        Bucket: 'my-bucket-hogehoge',
        Key: 'path/to/object',
      }).promise();
    } catch(e) {
      // オブジェクトが存在しない場合(NoSuchKey)は無視,それ以外は throw し直す
      if (e.code !== 'NoSuchKey') {
        throw e;
      }
    }

存在しないオブジェクトへのアクセスは NoSuchKey が返ってくるものと期待していて、s3rver を利用したローカル環境での動作確認はそれで問題なかった。

しかし、デプロイして実際にどうさせると AccessDenied が返ってくる。

原因

なんで?
と1時間くらい悩んだが SDK のドキュメントに書いてあった

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property

Permissions

You need the s3:GetObject permission for this operation. For more information, see Specifying Permissions in a Policy. If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.

  • If you have the s3:ListBucket permission on the bucket, Amazon S3 will return an HTTP status code 404 ("no such key") error.
  • If you don’t have the s3:ListBucket permission, Amazon S3 will return an HTTP status code 403 ("access denied") error.

s3:ListBucket Action が許可されていないと access denied を返すと

対応

以下のようにポリシードキュメントで権限を付与したら解決。
Resource の指定方法が s3:getObject と異なるので注意。

  (一部抜粋)
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::my-bucket-hogehoge/*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::my-bucket-hogehoge",
            "Effect": "Allow"
        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASを使ってGoogleカレンダーにカレンダーを追加する

はじめに

コロナということもあって外出する機会も減り、仕事もリモートワークで
体を動かさなくなってきてしまった今日このごろですね。

以前はYouTubeでトレーニング動画をみて家でやってたのですが、半年で続かなくなっちゃいましたね。
そんな中、友人に誘われたので一緒にジムに入ることにしました。

どうせ入るなら元を取りたいし、どれくらい行ってるのかも気になるので
「いつ行ったかをメモろう!」ってなってたんですけど
毎回 "Y年m月d日 H:i:s 〜" って記録するのが面倒なので自動化したいな〜と思い、作るに至りました。

何を使って自動化するか

Googleカレンダー

結局のところ

  • 日付
  • 時間
  • 場所

をメモれるものってカレンダー系のアプリしかないな〜って思ったんですよね
普段から予定管理に使っているのもあって

$$\style{font-size:200%;font-weight:bold;align: center; font-family: "Helvetica Neue",Helvetica,"ヒラギノ角ゴ ProN W3","Hiragino Kaku Gothic ProN","メイリオ",Meiryo,sans-serif}{\text{Googleカレンダーでメモしよう!}}$$

となりました

Google App Script

Googleカレンダーを操作するならやっぱり同じGoogleが開発してるGASがいいのだろうとなって
$$\style{font-size:200%;font-weight:bold;align: center; font-family: "Helvetica Neue",Helvetica,"ヒラギノ角ゴ ProN W3","Hiragino Kaku Gothic ProN","メイリオ",Meiryo,sans-serif}{\text{GASを使おう!}}$$
となりました

どうやって自動化するか

GASのWebアプリ化

ボタン1つで打刻できれば良い = アクセスがきたらデータを追加
と置き換えれば話は早いです

仕組みを考える

カレンダーの追加って

  • タイトル
  • 開始時刻
  • 終了時刻

が必要で、
入室時に1回、退室時に1回と押すとしたら
退室時間がわかってから記録するようにしなければなりませんが、
「入室時の時間ってどこにメモっておけば...」が障害になりました

そこで調べてみたら、GASの機能に
Cache Servicehttps://developers.google.com/apps-script/reference/cache)
というものがありました
キャッシュを最長で6時間保持しておけるようなのでこれならいけそうです
※6時間以上トレーニングする人は他のやつ使ってください 笑

実装

アクセスできる用の関数を用意

アクセス時に呼び出される関数は名前が決まっていますので、今回はGET通信で呼び出されるdoGetを使用します

doGet.gs
//GET通信時に呼び出される
function doGet(){
}

クエリ文字列を取得

GASでクエリ文字列を取得する方法は以下の通りです
今回は入退室フラグとしてEoLFlg(Enter or Leave Flag)を使います

doGet.gs
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;
}

追加するカレンダーの取得

今回は「ジム」というカレンダーを作りました
カレンダーIDからそのカレンダーのオブジェクトを取得できるので、そのように記載します

doGet.gs
const calendarId = '[対象カレンダーのカレンダーID]←カレンダーから 設定>マイカレンダーの設定>カレンダーの統合から確認できます';
const calendar = CalendarApp.getCalendarById(calendarId);

カレンダーのクラスの使い方を詳しく知りたい方は以下の公式ドキュメントへどうぞ
https://developers.google.com/apps-script/reference/calendar/calendar-app

キャッシュを使う準備

キャッシュを使えるように関数を準備します
※他の方のコードの引用なので、最後に参考文献として載せてあります

makeCache.gs
// キャッシュの保存・取得をする関数 ※Qiitaからの引用(https://qiita.com/golyat/items/ba5d9ce38ec3308d3757)
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), 21600);
      return value;
    }
  };
}

実装(コード)

以上で完了です!
定数を変えればほぼコピペで使えるソースを下記に載せておきますね
細かく説明を載せておきますので、コピペして使う前に確認をしてください

addCalendar.gs
// 定数の宣言(カレンダーID、カレンダーオブジェクト、追加するカレンダーのタイトル、場所)
const calendarId = '{カレンダーID}';
const calendar = CalendarApp.getCalendarById(calendarId);
const title = '{タイトル}';
const location = '{場所}';
const option = {'location':location};

// キャッシュ周りの定数(保存時間、関数、キーとして使用するフラグの値)
const cacheTime = 21600;
const cache = makeCache();
const EoLFlgs = ['E', 'L'];

// GETでのアクセス時に対応
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;

  // EoLFlgが E 又は Lの時に起動
  if( EoLFlgs.indexOf(EoLFlg) != -1){
    var date = new Date();
    cache.put(EoLFlg, Utilities.formatDate( date, 'Asia/Tokyo', 'YYYY/MM/dd H:m:s'));

    switch(EoLFlg){
      // E:入室時 念の為退室時刻のキャッシュをクリア
      case "E":
      cache.put('L', null);
      break;

      // L:退室時 入室時刻 < 退室時刻 となっていればカレンダーに追加
      case 'L':
      var startTime = cache.get(EoLFlgs[0]);
      var endTime = cache.get(EoLFlgs[1]);
      cacheClear();
      if( startTime < endTime ){
        var result = calendar.createEvent(title, new Date(startTime), new Date(endTime), option);
      }else{
        return HtmlService.createTemplateFromFile('ERROR').evaluate();
      }
      break;
    }
    return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
  }
}

// キャッシュの保存・取得をする関数 ※Qiitaからの引用(https://qiita.com/golyat/items/ba5d9ce38ec3308d3757)
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), cacheTime);
      return value;
    }
  };
}

// キャッシュクリア用関数
function cacheClear(){
  // それぞれのキャッシュをクリア
  for(var i = 0; i < EoLFlgs.length; i++){
    cache.put(EoLFlgs[i], null);
  }
}

定数

addCalendar.gs
// 定数の宣言(カレンダーID、カレンダーオブジェクト、追加するカレンダーのタイトル、場所)
const calendarId = '{カレンダーID}';
const calendar = CalendarApp.getCalendarById(calendarId);
const title = '{タイトル}';
const location = '{場所}';
const option = {'location':location};
calendarId

追加するカレンダーのID
Googleカレンダーの設定画面に書かれているものをいれる(図のオレンジの箇所)
スクリーンショット 2021-03-21 21.14.17.png

title

カレンダーのタイトル
スクリーンショット 2021-03-21 21.22.05.png

location

カレンダーに設定される場所
スクリーンショット 2021-03-21 21.24.18.png
※optionで説明等も追加することができます

定数(キャッシュ等)

addCalendar.gs
// キャッシュ周りの定数(保存時間、関数、キーとして使用するフラグの値)
const cacheTime = 21600;
const cache = makeCache();
const EoLFlgs = ['E', 'L'];
cacheTime

キャッシュの保存時間 最大6時間(21600秒) ※makeCacheで使用

cache

makeCacheの定数化

EoLFlgs

クエリ文字列で使用するEoLFlgの配列
E = Enter(入室)
L = Leave(退室)
のフラグとして使用

doGet

addCalendar.gs
// GETでのアクセス時に対応
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;

  // EoLFlgが E 又は Lの時に起動
  if( EoLFlgs.indexOf(EoLFlg) != -1){
    var date = new Date();
    cache.put(EoLFlg, Utilities.formatDate( date, 'Asia/Tokyo', 'YYYY/MM/dd H:m:s'));

    switch(EoLFlg){
      // E:入室時 念の為退室時刻のキャッシュをクリア
      case "E":
      cache.put('L', null);
      break;

      // L:退室時 入室時刻 < 退室時刻 となっていればカレンダーに追加
      case 'L':
      var startTime = cache.get(EoLFlgs[0]);
      var endTime = cache.get(EoLFlgs[1]);
      cacheClear();
      if( startTime < endTime ){
        var result = calendar.createEvent(title, new Date(startTime), new Date(endTime), option);
      }else{
        return HtmlService.createTemplateFromFile('ERROR').evaluate();
      }
      break;
    }
    return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
  }
}
var EoLFlg = e.parameter.EoLFlg;

クエリ文字列から取得したEoLFlg

流れの説明
  1. EoLFlgの値が" E" もしくは "L"だった場合にのみ起動
  2. 起動時の時刻をキャッシュとして保存
  3. Eの場合(入室時):Lのキャッシュをクリア
  4. Lの場合(退室時):それぞれのキャッシュ情報を取得後、キャッシュクリア
  5. EのキャッシュがLのキャッシュより過去日になっていれば、カレンダーに追加
  6. EのキャッシュがLのキャッシュより過去日になっていなければ、エラー表示
  7. 正常に処理が終われば成功の表示

return HtmlService.createTemplateFromFile('ERROR').evaluate();
return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
はエラー時、成功時にHTMLを出力するための記述なので、
HTMLを3ファイル追加してください
* ERROR.html
* successE.html
* successL.html


キャッシュ操作の関数

addCalendar.gs
// キャッシュの保存・取得をする関数 ※Qiitaからの引用(https://qiita.com/golyat/items/ba5d9ce38ec3308d3757)
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), cacheTime);
      return value;
    }
  };
}

// キャッシュクリア用関数
function cacheClear(){
  // それぞれのキャッシュをクリア
  for(var i = 0; i < EoLFlgs.length; i++){
    cache.put(EoLFlgs[i], null);
  }
}
makeCache

キャッシュを操作する関数
cache.get(key)で引数のキーのキャッシュを取得できる
cache.put(key, value)で引数のキーのキャッシュを設定できる
※上記の定数で指定したchcheTimeはここで使う

cacheClear

キャッシュクリアする関数
EoLFlgsのループをし、nullを入れる

以上が処理の流れと実装内容になります

さいごに

今回はGoogle App Scriptを用いてGoogleカレンダーにカレンダーを追加する方法について書きました
勉強のメモとして書きましたが、GASを使ってなにかしたいと思っている人の参考になれば嬉しいです

最後までお読みいただきありがとうございました

参考文献

  • Cache Serviceを詳しく説明してくれている方の記事

  • タイムゾーンの変更方法について詳しく説明してくれている方の記事

備考

自分はiPhoneのショートカットで
「ジムに到着したら」「ジムを出発したら」ってオートメーションに
今回作ったWebアプリにアクセスするショートカットを実行できるようにして使ってます

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

falsyな値とtruthyな値

falsyな値
・false
・null
・0
・undefined
・0n
・Nan
・""

truthyな値
・それ以外

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

無限スクロール中毒防止のChrome拡張機能を作った話

結論 作ったもの

"No Infinite Scroll"
インストールをすると無限スクロールを検知するたびに、
「本当に初めやろうとしてたことやってるのか?考え直せ!」
という趣旨の叱咤激励で戒めてくれます。
無限スクロール界隈の大御所、Twitterとかを開くと警告が出まくり使う気がなくなるというメリットがあります。

エンジニア,ネットサーフィン多い問題

Googleや公式ドキュメントとStackoverflowを往復する日々。
個人的には、最近バックエンド系のDocker,Ansible,Linuxの勉強をしていると、エラーの対処のために一日中こうしたサイトを往復することが多いです。

なんでついでにTwitterとか見ちゃう?

上のような問題の中、関係ないTwitterとかニュースサイトとかついついチェックしたくなる。
なんで?と思ってよくよく考えたら、
この手のサイトは無限にコンテンツが出てくるから中毒性が高いのでは?
と思うようになった。

底無し沼 ”無限スクロール”

無限スクロールのあるサイトでは、中毒性が高く時間を溶かすわりに、価値の薄い情報や広告に晒される。

こうした情報の氾濫の中で、ネットサーフィンが仕事柄のエンジニアは、集中を保つことは年々難しくなっているのではないでしょうか?

世界では、こうした中毒性のある無限スクロールを禁止しようという一部の流れもあるようです。

ソーシャルメディア依存軽減テクノロジー法

巨大テック企業に対して特に批判的な米国のジョシュ・ホーリー上院議員(共和党)が、ソーシャルメディア企業による無限スクロールや映像の自動再生、達成バッジなどのデザイン機能の使用を制限する新法案を提出した。

教訓:世の中は変えられないが、自分の環境はある程度変えられる

「お願い!無限スクロールをやめてください」とシリコンバレーの偉い人にJapanese Dogeza Styleで(土下座で)嘆願しても、多分そうはしてくれません。ただ、自分で拡張機能を作ったりすれば、少なくとも自分の環境は改善できます。

激化するサービス間の競争の中で、人間の脳をハックする、中毒性の高いサービスが生まれるのは当然のことです。無意識に何もかも身を任せてしまうことのないように、皆さんも自分でお手製のツールを作成してみてはいかがでしょうか?

参考用のソースコード

Chrome Web Storeに提出した物と同じコードをgithubで公開しています。
https://github.com/kantasv/no-infinite-scroll/tree/main

ソースコードの一部紹介 content.js

寝たいので説明は割愛します。

content.js
const EXTENSION_NAME = 'No Infinite Scroll'
const BIG_NUMBER = 10000
let timeoutId;
let maxClientHeight = null

const heightIncreased = () => {
    // base case
    if (!maxClientHeight) {
        maxClientHeight = document.body.clientHeight
    } else {
        if (maxClientHeight < document.body.clientHeight) {
            console.log(`[${EXTENSION_NAME}] Possibly infinite scroll detected`)
            maxClientHeight = document.body.clientHeight
            return true
        }
        return false
    }
}

const getRandomMessage = () => {
    const questions = ['Hey, is this infinite scroll website truly important for you? ', 'Are you still doing what you initially meant to do?', 'Do you know that infinite scroll is highly addictive?']
    const actions = ['Breath deeply, and think about it for a while.', 'Inhale and exale for a moment. Be mindful to stay productive and happy.', 'You face tsunami of infomation nowadays, but your attention is finite.']
    var min = 0;
    var max = Math.max(questions.length) - 1

    const qIndex = Math.floor(Math.random() * (max + 1 - min)) + min;
    const aIndex = Math.floor(Math.random() * (max + 1 - min)) + min;

    return questions[qIndex] + ' ' + actions[aIndex]
}

const initSpeedBumpModal = () => {
    // modalWrapper
    const modalWrapper = document.createElement('div')
    modalWrapper.style.backgroundColor = '#738276'
    modalWrapper.style.zIndex = BIG_NUMBER
    modalWrapper.style.position = 'fixed'
    modalWrapper.style.top = '0px'
    modalWrapper.style.left = '0px'
    modalWrapper.style.height = '100%'
    modalWrapper.style.width = '100%'
    modalWrapper.style.display = 'none'

    // speedBumpMessageContainer
    const speedBumpMessageContainer = document.createElement('div')
    speedBumpMessageContainer.style.backgroundColor = '#738276'
    speedBumpMessageContainer.style.color = 'white'
    speedBumpMessageContainer.innerHTML = getRandomMessage()
    speedBumpMessageContainer.style.margin = '20px'
    speedBumpMessageContainer.style.height = '800px'
    speedBumpMessageContainer.style.width = '800px'
    speedBumpMessageContainer.style.margin = '0 auto'
    speedBumpMessageContainer.style.display = 'flex'
    speedBumpMessageContainer.style.alignItems = 'center'
    speedBumpMessageContainer.style.justifyContent = 'center'

    const gotitButton = document.createElement('div')
    gotitButton.innerText = 'Click here to dismiss.'
    gotitButton.style.color = 'white'
    gotitButton.style.margin = '10px'
    gotitButton.onclick = closeSpeedBumpModal
    gotitButton.style.position = 'fixed'
    gotitButton.style.right = '0px'
    gotitButton.style.bottom = '0px'

    speedBumpMessageContainer.appendChild(gotitButton)
    modalWrapper.appendChild(speedBumpMessageContainer)
    document.body.appendChild(modalWrapper)
    return modalWrapper
}
const showSpeedBumpModal = () => {
    modalWrapper.style.display = 'block'
}
const closeSpeedBumpModal = () => {
    modalWrapper.style.display = 'none'
}

const modalWrapper = initSpeedBumpModal()

window.addEventListener("scroll", function () {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(function () {
        if (heightIncreased()) {
            showSpeedBumpModal()
        }
    }, 500);
})

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

ペライチで追っかけボタンを作成する(要レギュラープラン)

やりたいこと

スクロールしてもずっと画面の下にくっついてくる例の緑っぽいボタン!

前提

ペライチのレギュラープランが必要です

やること

1.htmlブロック要素を追加

ブロックの挿入 > HTML埋め込み
※[link]は任意のURLに付け替え

<div id="float-div">
    <a href='[link]' target="_blank" >追っかけボタン</a>
</div>

2.JS/STYLEの追加

ページ情報埋め込み > headタグ内の埋め込み

<style>
    #float-div {
        width: 200px;
        height: 60px;
        position: fixed;
        right: 7vw;
        bottom: 3vh;
        background: #56C500;
        opacity: 0.8;
        border-radius: 5px;
    }

    #float-div a {
        display: block;
        position: absolute;
        margin: 0 auto;
        top: 25%;
        text-align: center;
        width: 100%;
        text-decoration: none;
        font-size: 18px;
        font-weight: bold;
        color: #fff;
    }
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 円弧の書き方

JavaScriptで円を描く方法を実際の行為に置き換えながら説明してみました。
ほぼこれの内容です。

画用紙の準備

canvas要素

canvas要素は、JavaScriptという名の鉛筆で、図形などを描くための画用紙のようなイメージです。widthとheightとそれぞれ幅と高さを指定します。
後でJavaScriptで要素を取得するためにidを指定します。

   <canvas id="canvas" width="150" height="200"></canvas>

getContext

canvas要素をidで取得し、getContextメソッドで書きたい図形が2次元だということを指定します。変数ctxに代入してそれに対して様々なメソッドで円を書いていきます。
※3次元もできるっぽいですが、まだ勉強してません。

   var canvas = document.getElementById("canvas");
   var ctx = canvas.getContext("2d");

鉛筆をもつ

beginPath

beginPathで鉛筆を持ちます。

   ctx.beginPath();

書きたい円を想像する

arcメソッド

書きたい円を想像してその設計図をarcメソッドに書いていきます。

   ctx.arc(75,75,20,0,(Math.PI/180)*90,true)

arc(x軸,y軸,半径,弧が開始する角度,弧が終了する角度,弧の向き)に従って、値を入れていきます。

x軸,y軸

作成したcanvas要素の左上を原点とし、描きたい円弧の中心をどこにするかを指定する。
width=150,height150のcanvas要素を作成した場あり、x=75,y=75とするとcanvas要素の中心に、円弧の中心を指定できます。

半径

作成したい円の半径を指定します。そのままですね。

弧が開始する角度,弧が終了する角度

円の中心を通り水平に線を引いたときの右側の円との接点を0°、円の中心を通り垂直に線を引いたときの下側の円との接点を90°として値を設定します。下記例です。(中心が雑ですみません。)

開始する角度に0、終了する角度に90を指定した場合
スクリーンショット 2021-03-21 20.29.53.png

開始する角度に90,終了する角度に270を指定した場合
スクリーンショット 2021-03-21 20.39.33.png

ここでいう角度は、多くの人が想像するであろう60°とか45°とかいう度数法ではなく、弧度法(ラジアン)で指定します。弧度法(ラジアン)のことを知らなくても、(Math.PI/180)*degreesdegreesに度数法の数字を指定すれば良いです。終了する角度に90を指定したい場合は(Math.PI/180)*90のように。
ちなみに、ラジアン知らなかったのですが下記サイトで理解できました。

弧の向き

時計回りか、反時計回りかです。真偽値で示します。falseが時計回り、trueが反時計周りで、指定しない場合はfalseとみなされ時計回りに弧が描かれます。

円弧を描く

strokeメソッドを使って円弧を書きます。ちなみにfillメソッドだと、指定した開始角度と終了角度を結んだ線と円弧で囲まれた部分が黒く塗りつぶされます。

   ctx.stroke()

完成型コード

   var canvas = document.getElementById("canvas");
      if (canvas.getContext) {
        var ctx = canvas.getContext("2d");
        ctx.beginPath();
        ctx.arc(250,250,100,(Math.PI/180)*90,(Math.PI/180)*270);
        ctx.stroke();        
      }

ブラウザによってはgetContextメソッドが使用できない場合があるらしく、if文いれています。

おわりに

需要あるかどうかわかりませんが、誰かの役に立てば幸いです。

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

シンプルで使いやすいJSONフォーマットツールを作ってみた

はじめに

こんにちは、システム開発チーム「presto」です。
開発業務(プログラミング中)に「こんなツール、あんなツールが身近なところにあったらいいなぁ」と思ったことはありませんか?

ここではそんなツールをVuetifyを使って作ってみたので紹介させてください。

JSONフォーマットツール

今回紹介させていただくのは、JSONフォーマットツールです。

画面は以下の通りです。

JSONフォーマットツール

使い方

使い方は以下の通りです。
1. 画面上部のテキストエリアにJSON文字列を入力する
2. (任意)フォーマットのインデント数を指定する
3. 結果確認

1. 画面上部のテキストエリアにJSON文字列を入力する

JSONフォーマットツール_手順1

例として、以下を入力します。

{"number":12345,"result":true}

JSONフォーマットツール_手順1_入力内容

2. (任意)フォーマットのインデント数を指定する

デフォルトのインデント数は「4」となっています。

JSONフォーマットツール_手順2_インデント数指定

3. 結果確認

以下のような値が表示されます。

{
    "number": 12345,
    "result": true
}

JSONフォーマットツール_手順3_フォーマット結果

インデント数を「0」と指定すると、整形後のJSON文字列がMinifyされます。

{"number":12345,"result":true}

JSONフォーマットツール_手順3_インデント数指定_Minify

JSONフォーマットできない文字列を入力した場合

JSONフォーマットできない文字列を入力した場合、SyntaxErrorが表示されます。

SyntaxError: Unexpected token d in JSON at position 6

JSONフォーマットツール_SyntaxError

まとめ

今回は、JSONフォーマットツールの紹介をさせていただきました。

今後もよろしくお願いします。
ありがとうございましたー!

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

【JavaScript】配列を分割して配列を生成する

はじめに

配列の要素を任意の個数で分割したいと思ったときに、既に記事にしていた方がいました。
ナレッジ共有に感謝しかありません。ありがとうございます。
本記事の最後に出典としてURLを貼っておきます。
この記事の目的として自分の知識の整理のための記事のため、もっと詳細な説明を知りたい場合は、出典元の記事を参照していただければと思います。

配列を任意の個数の配列に分割する関数

以下が配列を任意の個数の配列に分割する関数になります。
何をやっているかを説明していきます。

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const sliceByNumber = (array, number) => {
  // 元の配列(今回で言うと変数arrayを指します)を基に、分割して生成する配列の個数を取得する処理です。
  // 今回は元の配列の要素数が10個、分割して生成する配列は2つの要素を持つことを期待しています。
  // 上記のことから今回は、元の配列から5つの配列に分割されることになります。
  const length = Math.ceil(array.length / number);

  // new Arrayの引数に上記で取得した配列の個数を渡します。これで配列の中に5つの配列が生成されます。
  // 5つの配列分だけループ処理(mapメソッド)をします。map処理の中でsliceメソッドを使用して、元の配列から新しい配列を作成して返却します。
  // sliceメソッドの中では、要素数2つの配列を生成します。
  // fillメソッドはインデックスのキーを生成するために使用しています。もし使用しない場合はmapメソッドはindexがないため、mapメソッドが機能しません。
  return new Array(length)
    .fill()
    .map((_, i) => array.slice(i * number, (i + 1) * number));
};

console.log(sliceByNumber(array, 2));
// [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ], [ 9, 10 ] ]

終わりに

今回参考にさせて頂いた記事では、詳細な説明までありとても参考になりました。
改めてありがとうございます。
僕も他の人のナレッジ共有できるように詳細で分かりやすい記事を書いていきたいと思います。

参考サイト

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

【GAS】③Classroomのコース情報を変更

目次

  1. 【GAS】①Classroomのコース一覧を取得する
  2. 【GAS】②Classroomのコースを新規作成
  3. 【GAS】③Classroomのコース情報を変更(このページ)

コース情報の取得

ここではすでに作成済みコースのコース情報をGASで変更する方法を説明します。まずコース一覧の取得方法に従って変更したいコースのcourseIDを調べ、getメソッドで変更したいコースを取得します。getメソッドはコース情報をオブジェクト形式で返します。

/**
 * Retrieves course by id. 
 */
function getCourse() {
  const courseId = '123456';
  try {
    const course = Classroom.Courses.get(courseId);
    Logger.log('Course "%s" found. ', course.name);
  } catch (err) {
    Logger.log('Course with id "%s" not found', courseId);
  }
}

取得したコース(course)の名前を調べたい場合にはcourse.name、取得したコースの教師グループメールアドレスを調べたい場合には、course.teacherGroupEmailなどと「取得したコース.コース情報」で指定する。コース情報として指定できるプロパティはResource:coursesを参照のこと。

コース情報の変更

以下のコース情報は必要に応じて何時でも変更することが可能です。

  • name(コース名)
  • section(セクション)
  • descriptionHeading(説明のヘッダー、どこに表示されるのか不明)
  • description(説明)
  • room(部屋)
  • courseState(コース状態)

コース情報の変更にはCoursesクラスのupdateメソッド(courses.update)を使います。コース情報を変更するには、次の例のように変更したいコース情報を、オブジェクトのプロパティを変更する要領で修正し、それを反映させる方法を取ります。

/**
 * Updates the section and room of Google Classroom. 
 */
function courseUpdate() {
  const courseId = '123456';
  let course = Classroom.Courses.get(courseId);
  course.section = 'Period 3';
  course.room = '302';
  course = Classroom.Courses.update(course, courseId);
  Logger.log('Course "%s" updated.', course.name);
}

このように説明すると、コース情報の一つであるcourseIdも変更できそうに思えますが、courseIdの変更はできない仕様になっています。

同じくCoursesクラスのpatchメソッド(courses.patch)でもコース情報の変更はできるのですが、次の例をみれば分かるように引数の指定方法がやや煩雑です。なぜコース情報を変更する方法としてupdateメソッドとpatchメソッドが用意されているのか、よく分かりません。

/**
 * Updates the section and room of Google Classroom. 
 */
function coursePatch() {
  const course_id = '123456';
  let course = {
    'section': 'Period 3',
    'room': '302'
  };
  const mask = { 
    updateMask: 'section,room' 
  };
  course = Classroom.Courses.patch(body=course, id=course_id, updateMask=mask);
  Logger.log('Course "%s" updated.', course.name);
}

次回は、コースを削除する方法についてまとめます。

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

Reactの関数コンポーネントとHooks #2

Part1の続きです。

メモ化フックとは

同じ結果を返す処理に関しては初回のみ処理を実行しておき、2回目以降は前回の処理結果を呼び出すことで毎回同じ処理を実行する事を無くす。これはプログラミングではメモ化と呼ばれる高速化のテクニックのひとつです。
これをReact Hooks上で簡単に利用できるのがuseMemouseCallbackです。
パフォーマンス向上のためにuseMemo、useCallbackは使われてるという事です。(つまりこの2つを使わなくても動作自体はします。)

パフォーマンス向上のポイントは、以下2つです。
・不要な再描画や再計算を無くす
・再レンダリングを抑える

レンダリングに時間のかかるコンポーネントや頻繁に再レンダリングが発生するコンポーネントに有効と言えます。

useMemo: 関数の結果をメモ化

//基本形、第2引数に入れた依存配列の値のいずれかが変化した場合にのみ再計算が実行される
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

実際のサンプル countが更新された時だけ再計算が実行される。そうでない場合はメモ化(保存)された値を返します。

const [count, setCount] = useState(0)
const hoge = useMemo(() => count * 2, [count])


useCallback: 関数のメモ化

親コンポーネントからコールバック関数をpropsとして受け取った子コンポーネントは再描画されてしまいます。関数の内容が同じだとしても、コンポーネントが再描画される度に再生成されるので、等価ではないからです。

それを防ぐために、useCallbackを使います。useCallbackでメモ化したコールバック関数を Propsで渡せば子コンポーネントの不要な再レンダリングをスキップできます。

//基本形、第2引数に入れた依存配列の値のいずれかが変化した場合にのみ変化します
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

使い方の注意点 1
・useCallbackでメモ化したコールバック関数を、それを生成したコンポーネント自身で利用はできない(つまりPropsで子に渡す場合のみ有効)
・親コンポーネントからuseCallbackを渡しても、子コンポーネント自体がReact.memoでメモ化されている必要がある(つまり、useCallbackとReact.memoはセットで使う)

↑の注意点を守らないと意味のないuseCallbackになってしまいます。

以下がその注意点を守ったサンプルになります。useCallback, React.memoを使ってなくても動作に変わりはありませんが、入力の度にconsole.log('InputBox is rendered')が呼ばれます。この程度の実装なら大した影響はないですが、コンポーネントが増えて大規模になってきたりするとパフォーマンスに悪影響をもたらす原因になるので、積極的に取り入れていきたいと思います。

//親コンポーネント
const App = () => {
  const [ input, setInput ] = useState("")
  const onChange = useCallback(
    (e) => {
      setInput(e.target.value)
    },
   []
  );
  return <InputBox onChange={onChange} />
}

//子コンポーネント
const InputBox = React.memo(props => {
  const { onChange } = props
  console.log('InputBox is rendered')
  return <input type="text" onChange={onChange}/>
})

使い方の注意点 2
useMemouseCallbackもuseEffectと同じく第2引数に何も入れなかった場合、新しい値がレンダーごとに毎回計算されてしまい、これらを使う意味がなくなるので、第2引数に依存する値(監視する値)もしくは空配列は必須です。


useContext
コンポーネントツリー上、親子関係にない(=ツリー上離れたところにいる)コンポーネント間で同じ値を共有する事ができる。
(propsでバケツリレーしないでも値の受け渡しができるReduxの様な使い方が可能です。)

//Provider(親)
export const Context = createContext({});
const hoge = { name: 'taro yamada' };

return (
  <Context.Provider value={hoge}>
    <ChildComponent />
  </Context.Provider>
)

//Consumer(子)
import { Context } from "...";

const value = useContext(Context);
//valueの中に{ name: 'taro yamada' }が入る

Consumer(子)側でデータを受け取るだけなら上記の様に簡単にできます。

単純にデータを渡すだけでなく、親からはコールバックを渡して、子の方からデータを送ってもらう事も可能です。

//Provider(親) addToData関数で子から値を受け取りStateに保存する
return (
  <Context.Provider sendData={hoge => addToData(hoge)}>
    <ChildComponent />
  </Context.Provider>
)

//Consumer(子) sendDataを使って親にデータを送る
import React , { useContext } from 'react';
import { Context } from "...";

const sendData = useContext(Context);
sendData(hogeData);

このuseContextを拡張すると、Redux無しでStoreを持つ事ができるのですが、それは長くなるので別の記事で紹介します。


useRef
ざっくりいうとDOMにアクセスするするためのHooksです。

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

普通に単一のDOMを参照したい場合はこの様に簡単です。useRefでref オブジェクトを生成し、それをDOMに渡せば参照する事ができます。

しかし、配列データから複数個のDOMを生成してる場合はちょっと工夫が必要です。

こちらの記事にそのやり方が書いてあるので参照してください。


まとめ

関数コンポーネントのメリットは、constructor, renderなどが不要で記述量を減らせて、慣れれば圧倒的に楽に書けるのでReactプロジェクトでこっちが主流になる理由が良くわかりました。ただまだ不完全な部分もあるので、適時追記修正していきます。

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

Gatsbyで作成したページを転送する

Gatsby で作成したページをリダイレクトしたい場合、ホスティング先で設定が必要であったが、 Gatsby のプラグインを設定することで対応することも可能です。

プラグインの設定方法などをまとめました。

プラグインの追加

(2021/3/21時点)今回追加する gatsby-redirect-from は内部で gatsby-plugin-meta-redirect に依存しているため2つパッケージの追加が必要となります。

Yarn を利用している場合

npm i gatsby-redirect-from gatsby-plugin-meta-redirect

Npm を利用している場合

npm i gatsby-redirect-from gatsby-plugin-meta-redirect

プラグインの設定

先程追加した、プラグインの設定を以下ファイルに追記します。

gatsby-config.js
plugins: [
  'gatsby-redirect-from',
  'gatsby-plugin-meta-redirect',
]

MarkdownRemark ではなく Mdx を利用している場合

query option に allMdx を渡して下さい

gatsby-config.js
plugins: [
  {
    resolve: 'gatsby-redirect-from',
    options: {
      query: 'allMdx'
    }
  },
  'gatsby-plugin-meta-redirect',
]

Markdown ファイルに転送設定を追求

今回は、 old-sample-markdown.md と言うファイルを old-sample-markdown.md に変更した場合の例を記載します。

Markdown の先頭に redirect_from の記述を追加します。

sample-markdown.md
---
title: Sample Markdown
redirect_from:
  - /old-sample-markdown/
---

設定を追加し、再度 build など実施すると旧URLにアクセスした際に、新URLにリダイレクトされるようになりました。

その他補足

もし、 Netlify を利用しており、このプラグインを利用ぜず転送設定を行いたい場合は以下ドキュメントを参照してください。

今回記載する内容は、同一サイト内のパス変更だけの対応です。異なるドメインへのリダイレクト等は各ホスティングサービスが提供するリダイレクト設定を使用して下さい。

使用した plugin や ドキュメント

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

Node.jsでgRPCを動かそう - xhack勉強会

※こちらの記事は3/21に開催するxhack勉強会「Node.jsでgRPCを動かそう」の資料となります

本日のお品書き

始めに

ぜひ#xhack勉強会 で感想をツイートしてください!
また、可能な方はカメラオンにして欲しいです!顔が見えない中ひとりで話すのは寂しい、、

自己紹介

とむといいます。
新卒未経験からエンジニアになってまもなく2年。
今年の2月に転職をし、大阪から東京に引っ越してきました。
転職前はJavaを使って主にサーバ側の開発をしていましたが、現在はスマホアプリ開発をメインに行っている株式会社マンハッタンコードで働いています。
Twitterはこちらをクリック←ぜひフォローしてください!

以前からX-HACKさんの勉強会にはものすごくお世話になっていたのですが、今回初めて勉強会を主催させていただきます。
お手柔らかにお願いします、、

想定参加者

  • gRPCって聞いてピンと来ない人
  • JavaScriptが読める人
  • サーバ、クライアント、API、HTTPといった用語をある程度理解している人
  • Macを持っている人
  • ターミナルで基本的なコマンドが扱える人(npm・nodeコマンドがインストールされている人)

今日のゴール

注意:今回の勉強会では書き方の細かいルールなどは説明しません!

Node.jsでgRPCを動かそう

gRPCって何?

  • Googleが公開したRPCフレームワークの一つ

そもそもRPCって???

RPCはRemote Procedure Callの略。
たけがみまさきさん著のスターティングgRPCでは

Remote Procedure Callを訳すと遠隔手続き呼び出しという意味になります。すなわち、あるサービスからべつのサービスのアプリケーションの処理(サブルーチン/クラス/関数など)を呼び出す技術ということです。

と説明されています。
遠くにある関数とかを呼び出せちゃう便利なやつがRPCで、gRPCはその中の1つということをふんわりと頭に入れておいてもらえたらOKです!

gRPCのここがすごい

  • HTTP/2によって高速な通信ができる
  • ProtocolBuffers
    • データをシリアライズ化する仕組み
    • 独自の書き方(IDL=インターフェース定義言語)で呼び出しの定義ができる!!→異なる言語を使っているサービスの処理も呼び出せる!!! image.png

ここからは実際に手を動かしていきましょう!

ハンズオン

基本的な流れはこちら。
1. 必要なツールやライブラリをインストールする
2. Protocol Buffersを使って定義ファイルを書く
3. (定義ファイルをコンパイルしてコードを生成する)
4. 定義に沿ってサーバとクライアントを実装する

公式チュートリアルを参考に、よりシンプルなものを作ってみましょう

作るもの
指定した人に挨拶をするアプリみたいなやつ

  • クライアント側:誰に挨拶するかを決める
  • サーバ側:挨拶するフレーズを生成

1. 必要なツールやライブラリをインストールする

npm init

npm install grpc @grpc/proto-loader
or
npm install @grpc/grpc-js @grpc/proto-loader

grpcパッケージじゃなく下のgrpc-jsの方がいいかも?
参考

2. Protocol Buffersを使って定義ファイルを書く

helloworld.proto
// Protocol Buffersのバージョンを指定
syntax = "proto3";

// パッケージを指定
package helloworld;

// サービスを定義
service Greeter {
  // 引数の型:HelloRequestメッセージ
  // 戻り値の型:HelloReplyメッセージ
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// メッセージの型を定義
message HelloRequest {
  // 各フィールドに識別子としてタグを付ける
  // 値をフィールドに代入しているわけではないことに注意
  string name = 1;
}

message HelloReply {
  string message = 1;
}

3. (定義ファイルをコンパイルしてコードを生成する)

node.jsはコンパイルをせず2.で作成した定義ファイルを読み込めばヨシ!!

4. 定義に沿ってサーバとクライアントを実装する

サーバ側

クライアントからアクセスがあったら挨拶文を生成する(sayHelloする)サーバを動かしたい

server.js
const protoLoader = require('@grpc/proto-loader');
// インストールしたパッケージに合わせる
// const grpc = require('grpc');
const grpc = require('@grpc/grpc-js');
const PROTO_PATH = __dirname + '/helloworld.proto'

// 定義ファイル(.protoファイル)の読み込み
const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * sayHelloメソッド
 */
function sayHello(call, callback) {
  callback(null, {message: 'Hello ' + call.request.name});
}

/**
 * mainメソッド
 */
function main() {
  // サーバのインスタンスを生成
  const server = new grpc.Server();
  // サーバがGreeterサービスのリクエストを受け取るようにする
  server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
  // クライアントのリクエストをリッスンする(外部からのアクセスに備えて待機する)ためのアドレスとポートを指定
  server.bindAsync('127.0.0.1:50051', grpc.ServerCredentials.createInsecure(), () => {
    // サーバを起動する
    server.start();
  });
}

main();

サーバを起動!

node server.js

クライアント側
クライアントがやりたいのは

  • 誰に挨拶をするか決める
  • サーバ側(Remote)のsayHello(Procedure)を呼び出し(Call)
client.js
const PROTO_PATH = __dirname + '/helloworld.proto';

// インストールしたパッケージに合わせる
// const grpc = require('grpc');
const grpc = require('@grpc/grpc-js');
// 定義ファイル(.protoファイル)の読み込み
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * mainメソッド
 */
function main() {
  // サーバのアドレスとポートを指定してGreeterサービスのスタブコンストラクタを呼び出す
  const client = new hello_proto.Greeter('127.0.0.1:50051',
                                       grpc.credentials.createInsecure());
  // スタブでsayHelloメソッドの呼び出し
  // リクエスト(HelloRequestメッセージ型のオブジェクト)とコールバック関数をサーバ側に渡す
  client.sayHello({name: 'World'}, function(err, response) {
    console.log('Greeting:', response.message);
  });
}

main();

クライアントを起動!

node server.js

Greeting:Hello Worldと出力されれば成功です!!

お疲れ様でした!!

最後に

初めての勉強会開催だったので皆さんの感想が気になります!!
#xhack勉強会 でツイートしてくれたら嬉しいです!

近日開催されるxhack勉強会

株式会社マンハッタンコード

現在私が所属している株式会社マンハッタンコードでは、スマホアプリ開発を主軸にさまざまな挑戦をしています。
毎週土曜日にはイベントを開催しているので、気軽に遊びに来てください!


最後に、今回の勉強会を開く後押しをしてくださり、当日も参加してサポートしてくださったX-HACKの松田さんとよももさん、そして日曜の夜にわざわざ時間を作って参加してくださった皆さん、ありがとうございました!

参考

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

Node.jsでgRPCを動かそう

※こちらの記事は3/21に開催するxhack勉強会「Node.jsでgRPCを動かそう」の資料となります

本日のお品書き

始めに

ぜひ#xhack勉強会 で感想をツイートしてください!
また、可能な方はカメラオンにして欲しいです!顔が見えない中ひとりで話すのは寂しい、、

自己紹介

とむといいます。
新卒未経験からエンジニアになってまもなく2年。
今年の2月に転職をし、大阪から東京に引っ越してきました。
転職前はJavaを使って主にサーバ側の開発をしていましたが、現在はスマホアプリ開発をメインに行っている株式会社マンハッタンコードで働いています。
Twitterはこちらをクリック←ぜひフォローしてください!

以前からX-HACKさんの勉強会にはものすごくお世話になっていたのですが、今回初めて勉強会を主催させていただきます。
お手柔らかにお願いします、、

想定参加者

  • gRPCって聞いてピンと来ない人
  • JavaScriptが読める人
  • サーバ、クライアント、API、HTTPといった用語をある程度理解している人
  • Macを持っている人
  • ターミナルで基本的なコマンドが扱える人(npm・nodeコマンドがインストールされている人)

今日のゴール

注意:今回の勉強会では書き方の細かいルールなどは説明しません!

Node.jsでgRPCを動かそう

gRPCって何?

  • Googleが公開したRPCフレームワークの一つ

そもそもRPCって???

RPCはRemote Procedure Callの略。
たけがみまさきさん著のスターティングgRPCでは

Remote Procedure Callを訳すと遠隔手続き呼び出しという意味になります。すなわち、あるサービスからべつのサービスのアプリケーションの処理(サブルーチン/クラス/関数など)を呼び出す技術ということです。

と説明されています。
遠くにある関数とかを呼び出せちゃう便利なやつがRPCで、gRPCはその中の1つということをふんわりと頭に入れておいてもらえたらOKです!

gRPCのここがすごい

  • HTTP/2によって高速な通信ができる
  • ProtocolBuffers
    • データをシリアライズ化する仕組み
    • 独自の書き方(IDL=インターフェース定義言語)で呼び出しの定義ができる!!→異なる言語を使っているサービスの処理も呼び出せる!!! image.png

ここからは実際に手を動かしていきましょう!

ハンズオン

基本的な流れはこちら。
1. 必要なツールやライブラリをインストールする
2. Protocol Buffersを使って定義ファイルを書く
3. (定義ファイルをコンパイルしてコードを生成する)
4. 定義に沿ってサーバとクライアントを実装する

公式チュートリアルを参考に、よりシンプルなものを作ってみましょう

作るもの
指定した人に挨拶をするアプリみたいなやつ

  • クライアント側:誰に挨拶するかを決める
  • サーバ側:挨拶するフレーズを生成

1. 必要なツールやライブラリをインストールする

npm init

npm install grpc @grpc/proto-loader
or
npm install @grpc/grpc-js @grpc/proto-loader

grpcパッケージじゃなく下のgrpc-jsの方がいいかも?
参考

2. Protocol Buffersを使って定義ファイルを書く

helloworld.proto
// Protocol Buffersのバージョンを指定
syntax = "proto3";

// パッケージを指定
package helloworld;

// サービスを定義
service Greeter {
  // 引数の型:HelloRequestメッセージ
  // 戻り値の型:HelloReplyメッセージ
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// メッセージの型を定義
message HelloRequest {
  // 各フィールドに識別子としてタグを付ける
  // 値をフィールドに代入しているわけではないことに注意
  string name = 1;
}

message HelloReply {
  string message = 1;
}

3. (定義ファイルをコンパイルしてコードを生成する)

node.jsはコンパイルをせず2.で作成した定義ファイルを読み込めばヨシ!!

4. 定義に沿ってサーバとクライアントを実装する

サーバ側

クライアントからアクセスがあったら挨拶文を生成する(sayHelloする)サーバを動かしたい

server.js
const protoLoader = require('@grpc/proto-loader');
// インストールしたパッケージに合わせる
// const grpc = require('grpc');
const grpc = require('@grpc/grpc-js');
const PROTO_PATH = __dirname + '/helloworld.proto'

// 定義ファイル(.protoファイル)の読み込み
const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * sayHelloメソッド
 */
function sayHello(call, callback) {
  callback(null, {message: 'Hello ' + call.request.name});
}

/**
 * mainメソッド
 */
function main() {
  // サーバのインスタンスを生成
  const server = new grpc.Server();
  // サーバがGreeterサービスのリクエストを受け取るようにする
  server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
  // クライアントのリクエストをリッスンする(外部からのアクセスに備えて待機する)ためのアドレスとポートを指定
  server.bindAsync('127.0.0.1:50051', grpc.ServerCredentials.createInsecure(), () => {
    // サーバを起動する
    server.start();
  });
}

main();

サーバを起動!

node server.js

クライアント側
クライアントがやりたいのは

  • 誰に挨拶をするか決める
  • サーバ側(Remote)のsayHello(Procedure)を呼び出し(Call)
client.js
const PROTO_PATH = __dirname + '/helloworld.proto';

// インストールしたパッケージに合わせる
// const grpc = require('grpc');
const grpc = require('@grpc/grpc-js');
// 定義ファイル(.protoファイル)の読み込み
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * mainメソッド
 */
function main() {
  // サーバのアドレスとポートを指定してGreeterサービスのスタブコンストラクタを呼び出す
  const client = new hello_proto.Greeter('127.0.0.1:50051',
                                       grpc.credentials.createInsecure());
  // スタブでsayHelloメソッドの呼び出し
  // リクエスト(HelloRequestメッセージ型のオブジェクト)とコールバック関数をサーバ側に渡す
  client.sayHello({name: 'World'}, function(err, response) {
    console.log('Greeting:', response.message);
  });
}

main();

クライアントを起動!

node server.js

Greeting:Hello Worldと出力されれば成功です!!

お疲れ様でした!!

最後に

初めての勉強会開催だったので皆さんの感想が気になります!!
#xhack勉強会 でツイートしてくれたら嬉しいです!

近日開催されるxhack勉強会

株式会社マンハッタンコード

現在私が所属している株式会社マンハッタンコードでは、スマホアプリ開発を主軸にさまざまな挑戦をしています。
毎週土曜日にはイベントを開催しているので、気軽に遊びに来てください!


最後に、今回の勉強会を開く後押しをしてくださり、当日も参加してサポートしてくださったX-HACKの松田さんとよももさん、そして日曜の夜にわざわざ時間を作って参加してくださった皆さん、ありがとうございました!

参考

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

fetch API で POST したらエラー

Nest.jsで作ったAPIをReactからfetchしようとした時にエラーが出たので、その解決方法をメモします。

エラーの原因の結論は、データがうまく渡せていなかったからです。空で渡っていました。

エラー内容

実行したのは大体こんな感じです

App.tsx
async function addTodo() {
        const body = JSON.stringify({todo:todo,limit:limit})
        await fetch('http://localhost:3000/item',{
            method: 'POST',
            mode: 'cors',
            body: body,
        })
        .then(() =>{
            // 省略
        })
    }

POSTでfetchしたらエラーが出ました。

POST http://localhost:3000/item 400 (Bad Request)

原因

エラー内容をしっかりと見ていってわかったこと。

  • データが渡せていない(サーバー側で確認したら、空の配列になっていた)
  • サーバー側で「not null」のバリデーションを付けているため、空のデータに対してエラー

解決方法

ヘッダーにContent-Typeを指定して、Jsonであることを伝える事で解決

App.tsx
async function addTodo() {
        const body = JSON.stringify({todo:todo,limit:limit})
        await fetch('http://localhost:3000/item',{
            method: 'POST',
            mode: 'cors',
            body: body,
             // =====↓追加====
            headers:{'Content-Type': 'application/json'}
             // =====↑追加====
        })
        .then(() =>{
            // 省略
        })
    }

これで、データをサーバー側で受け取ることができました。
無事、エラーがなくなりました。

参考文献

Fetch の使用

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

【Vue.js】コンポーネント

コンポーネントとは

・画面を構成する要素を個々のUIパーツに分割したもの。

・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。

・メリット →複数の画面で再生できる。コードの見通しがよくなる。

コンポーネントの作成手順

ここでは一つのjavascriptファイルで記述していく。

・componentを登録する

componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
 ※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
 ※コンポーネントの登録は、インスタンスの生成よりも前に記述する。

js
Vue.component('user-list', {
    //componentメソッドでcomponentを登録する。
 data(){
    //dataでは常に関数の戻り値でオブジェクトを設定する。
  return {
   users: [
    {id: 1, name: '1ちゃん'},
    {id: 2, name: '2ちゃん'},
    {id: 3, name: '3ちゃん'},
    {id: 4, name: '4ちゃん'},
    {id: 5, name: '5ちゃん'}
   ]
  }
 },
 template:`
  <ul>
   <li v-for="user in users" :key="user.id">
    {{ user.name }}
   </li>
  </ul>
 `
})

const vm = new Vue({
  el: '#app',
})
html
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
 <user-list></user-list>
        //登録したuser-listコンポーネントをタグのようにして呼び出すことができる。
</div>

コンポーネントの入れ子構造

Vueインスタンスをルートとして、その下にコンポーネントがつながっている。
コンポーネント内のtemplateで他のコンポーネントを呼び出すこともできる。
 ※コンポーネントのtemplate直下には一つの要素しか書けないことに注意。

以下では、use-listコンポーネントのtemplateで<list-title></list-title>を呼び出してみる。

js
Vue.component('list-title', {
  template:`
    <h2>ユーザーリスト</h2>
  `
})

Vue.component('user-list', {
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ]
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id">
         {{ user.name }}
       </li>
     </ul>
   </div>
 `
})

const vm = new Vue({
  el: '#app',
})

出力結果

ユーザーリスト

  • 1ちゃん
  • 2ちゃん
  • 3ちゃん
  • 4ちゃん
  • 5ちゃん

ローカル登録とグローバル登録

・グローバル登録
→Vueのtemplate上のどこからでも呼び出して使うことができるが、使用していない場合もコンポーネントの読み込みが発生してしまう。

・ローカル登録
→コンポーネント名:コンポーネントのオブジェクト の形式で登録する。Vueインスタンスのcomponentsオプション配下のみで使用する。

ここまでグローバル登録してきたcomponentを、ローカル登録に書き換えてみる。

js
const ListTitle = {
        //オブジェクトの形式に変える
  template:`
    <h2>ユーザーリスト</h2>
  `
}

Vue.component('user-list', {
  components: {
    'list-title': ListTitle
     //user-listコンポーネントのcomponents内でローカル登録する
  },

ローカル登録の場合は登録したVueインスタンスのcomponentsオプション配下のみで使用できるので、以下のようにマウント先で呼び出しても機能しない。

html
<div id="app">
  <list-title></list-title>
  <user-list></user-list>
</div>

親コンポーネントから子コンポーネントへデータを渡す

コンポーネント内に定義したdataは、そのままでは他のコンポーネントから参照したり書き換えることができない。
そこでpropsを使えば、親から子への単一方向のデータの受け渡しが可能になる。

例として、「親コンポーネント:UserList」から「子コンポーネント:UserDetail」へデータを受け渡してみる。

・子コンポーネントを作成

js
const UserDetail = {
  props: {
    user: {
      type: Object
    }
        //Object型のuserというプロパティを親コンポーネントから受け取る
     //親コンポーネントでUserDetailコンポーネントを呼び出す際、
        //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している
  },
  template:`
    <div>
      <h2>選択中のユーザー</h2>
      {{ user.name }}
      //propsもdataと同様にアクセスできる
    </div>
  `
}

・親コンポーネントを編集

js
const UserList = {
  components: {
    'list-title': ListTitle,
    'user-detail': UserDetail
          //上で作成したUserDetailをローカル登録する
  },
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ],
      selected_user: {}
      //クリックイベントで選択中のユーザーを受け取るdataを定義し、
      //デフォルトでは空のオブジェクトを入れておく
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id" @click='selected_user = user'>
         //selected_userにクリックされたユーザーを格納する
         {{ user.name }}
       </li>
     </ul>
     <user-detail :user='selected_user'></user-detail>
        //クリックされたユーザーをv-bindでuser-detailへ受け渡す
   </div>
 `
}

このように、親コンポーネントのv-bindで渡したデータが、子コンポーネントのpropsに渡る。

propsの注意点

・v-bindの名前とpropsの名前は一致する必要がある。

・命名の規則
   v-bind:ケバブケースで記述(user-name)
   props:キャメルケースで記述(userName)

・propsで受け渡されたオブジェクト自体は直接書き換えることができないが、オブジェクト内のプロパティを書き換えることはできる。
   例:propsにuserというオブジェクとが定義されている場合。
    →user.name = '名前' のようにuserのプロパティへの代入はエラーにならないが、
     user = {} のようにuser自体への代入はエラーになる。

子コンポーネントから親コンポーネントへデータを渡す

例として、子コンポーネントでユーザー名を編集・登録し、親コンポーネントでそのユーザー名を表示するフォームを作ってみる。

・親となるVueインスタンス

js
const vm = new Vue({
  el: '#app',
  components: {
    'user-detail': UserDetail
  }
})

・HTML上で、Vueインスタンスにローカル登録されたUserDetailコンポーネントを表示する。

html
<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <user-detail></user-detail>
</div>

・親コンポーネント:UserDetail
user_nameというdataを持ち、templateで呼び出している。
またローカル登録したuser-formコンポーネントをtemplateで呼び出し、user_nameをv-bindで渡している。

js
const UserDetail = {
  components: {
    'user-form' : UserForm
            //このあと定義する子コンポーネントUserFormをローカル登録する
  },
  data(){
    return {
      user_name: 'サトウ ハナコ'
    }
 },
 template:`
   <div>
     <div>
       <span>ユーザー名: {{ user_name }} </span>
     </div>
     <div>
       <user-form :user-name='user_name' @update:user-name='user_name = $event'></user-form>
            //user-formコンポーネントには、v-bindでuser_nameを渡す
            //このあと定義するupdateメソッド
     </div>
   </div>
 `
}

・子コンポーネント:UserForm

js
const UserForm = {
  template:`
    <div>
      <div>ユーザー名変更フォーム</div>
      <input v-model='user_name' />
      <button @click='update'>名前変更</button>
    </div>
  `,
      //user_nameを編集するinputタグと、確定するボタンを設置
  props:{
    userName: { type: String, required: true }
  },
  data(){
    return {
      user_name: this.userName
    }
      //user_nameを編集するため、propsをdataに設定する
  },
  methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
      //templateで設置したボタンがクリックされると、updateメソッドが呼び出される
      //$emitメソッドで親にデータを渡す
  }
}

$emitメソッド:親コンポーネントへデータを渡す際に使う。
・第一引数…v-onのイベント名(ここでは親コンポーネントで設定した@update:user-nameのイベントハンドラが実行される)
・第二引数…親コンポーネントに渡す値

ここで親コンポーネント「UserDatail」を見てみる。

<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>

この$eventには、$emitの第二引数にしていしたthis.user_nameが格納される。

これで親コンポーネントによりユーザー名が表示され、子コンポーネントによりユーザー名変更フォームが表示される。
フォームにテキストを入力してボタンをクリックすると、親コンポーネントによるユーザー名の表示も変更されるようになる。

sync修飾子を使ってデータを渡す

ここまでで書いたコードを、sync修飾子を使って書き換えてみる。

sync修飾子は親コンポーネントのv-bind属性に指定する。

・親コンポーネント「UserDatail」のv-bindが使われている部分を編集する。
sync修飾子をつけることで、@以下を記述する必要がなくなる。

変更前
<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>

変更後
<user-form :user-name.sync='user_name'>
</user-form>

・子コンポーネント「UserForm」の$emitの呼び出し部分を編集する。
$emitの第一引数を'メソッド名: propsの名前'とする。

変更前
methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
}

変更後
methods: {
    update(){
      this.$emit('update:userName', this.user_name)
    }
}

これでコードを実行すると、同じように動作する。

スロットの使い方

slotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。

以下の例で、使い方を見てみる。

・js
header・main・footerのそれぞれにslotタグが使われており、layoutコンポーネントを使用する側から、この3つのslotにコンテンツを挿入することができる。

js
const Layout = {
  template:`
    <div class ="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `
}

const vm = new Vue({
  el: '#app',
  components: {
    'layout': Layout
  }
})

・html
layoutタグでlayoutコンポーネントを呼び出し、slotに挿入するコンテンツを記述する。

html
<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <layout>
    <template slot='header'>
       //name="header"と指定されているslotタグの位置に挿入される
      Header
    </template>
    Main
    コンテンツ
       //slot属性を指定せずに記述した場合、
        layoutコンポーネントの名前なしslotの位置に挿入される
    <span slot='footer'>
       //spanのような通常のhtml要素にslot属性を指定した場合は、
        span要素自体がslotの位置に挿入される
      Footer
    </span>
  </layout>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】コンポーネント ~作成からデータの受け渡しまで~

コンポーネントとは

・画面を構成する要素を個々のUIパーツに分割したもの。

・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。

・メリット →複数の画面で再生できる。コードの見通しがよくなる。

コンポーネントの作成手順

ここでは一つのjavascriptファイルで記述していく。

・componentを登録する

componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
 ※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
 ※コンポーネントの登録は、インスタンスの生成よりも前に記述する。

js
Vue.component('user-list', {
    //componentメソッドでcomponentを登録する。
 data(){
    //dataでは常に関数の戻り値でオブジェクトを設定する。
  return {
   users: [
    {id: 1, name: '1ちゃん'},
    {id: 2, name: '2ちゃん'},
    {id: 3, name: '3ちゃん'},
    {id: 4, name: '4ちゃん'},
    {id: 5, name: '5ちゃん'}
   ]
  }
 },
 template:`
  <ul>
   <li v-for="user in users" :key="user.id">
    {{ user.name }}
   </li>
  </ul>
 `
})

const vm = new Vue({
  el: '#app',
})
html
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
 <user-list></user-list>
        //登録したuser-listコンポーネントをタグのようにして呼び出すことができる。
</div>

コンポーネントの入れ子構造

Vueインスタンスをルートとして、その下にコンポーネントがつながっている。
コンポーネント内のtemplateで他のコンポーネントを呼び出すこともできる。
 ※コンポーネントのtemplate直下には一つの要素しか書けないことに注意。

以下では、use-listコンポーネントのtemplateで<list-title></list-title>を呼び出してみる。

js
Vue.component('list-title', {
  template:`
    <h2>ユーザーリスト</h2>
  `
})

Vue.component('user-list', {
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ]
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id">
         {{ user.name }}
       </li>
     </ul>
   </div>
 `
})

const vm = new Vue({
  el: '#app',
})

出力結果

ユーザーリスト

  • 1ちゃん
  • 2ちゃん
  • 3ちゃん
  • 4ちゃん
  • 5ちゃん

ローカル登録とグローバル登録

・グローバル登録
→Vueのtemplate上のどこからでも呼び出して使うことができるが、使用していない場合もコンポーネントの読み込みが発生してしまう。

・ローカル登録
→コンポーネント名:コンポーネントのオブジェクト の形式で登録する。Vueインスタンスのcomponentsオプション配下のみで使用する。

ここまでグローバル登録してきたcomponentを、ローカル登録に書き換えてみる。

js
const ListTitle = {
        //オブジェクトの形式に変える
  template:`
    <h2>ユーザーリスト</h2>
  `
}

Vue.component('user-list', {
  components: {
    'list-title': ListTitle
     //user-listコンポーネントのcomponents内でローカル登録する
  },

ローカル登録の場合は登録したVueインスタンスのcomponentsオプション配下のみで使用できるので、以下のようにマウント先で呼び出しても機能しない。

html
<div id="app">
  <list-title></list-title>
  <user-list></user-list>
</div>

親コンポーネントから子コンポーネントへデータを渡す

コンポーネント内に定義したdataは、そのままでは他のコンポーネントから参照したり書き換えることができない。
そこでpropsを使えば、親から子への単一方向のデータの受け渡しが可能になる。

例として、「親コンポーネント:UserList」から「子コンポーネント:UserDetail」へデータを受け渡してみる。

・子コンポーネントを作成

js
const UserDetail = {
  props: {
    user: {
      type: Object
    }
        //Object型のuserというプロパティを親コンポーネントから受け取る
     //親コンポーネントでUserDetailコンポーネントを呼び出す際、
        //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している
  },
  template:`
    <div>
      <h2>選択中のユーザー</h2>
      {{ user.name }}
      //propsもdataと同様にアクセスできる
    </div>
  `
}

・親コンポーネントを編集

js
const UserList = {
  components: {
    'list-title': ListTitle,
    'user-detail': UserDetail
          //上で作成したUserDetailをローカル登録する
  },
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ],
      selected_user: {}
      //クリックイベントで選択中のユーザーを受け取るdataを定義し、
      //デフォルトでは空のオブジェクトを入れておく
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id" @click='selected_user = user'>
         //selected_userにクリックされたユーザーを格納する
         {{ user.name }}
       </li>
     </ul>
     <user-detail :user='selected_user'></user-detail>
        //クリックされたユーザーをv-bindでuser-detailへ受け渡す
   </div>
 `
}

このように、親コンポーネントのv-bindで渡したデータが、子コンポーネントのpropsに渡る。

propsの注意点

・v-bindの名前とpropsの名前は一致する必要がある。

・命名の規則
   v-bind:ケバブケースで記述(user-name)
   props:キャメルケースで記述(userName)

・propsで受け渡されたオブジェクト自体は直接書き換えることができないが、オブジェクト内のプロパティを書き換えることはできる。
   例:propsにuserというオブジェクとが定義されている場合。
    →user.name = '名前' のようにuserのプロパティへの代入はエラーにならないが、
     user = {} のようにuser自体への代入はエラーになる。

子コンポーネントから親コンポーネントへデータを渡す

例として、子コンポーネントでユーザー名を編集・登録し、親コンポーネントでそのユーザー名を表示するフォームを作ってみる。

・親となるVueインスタンス

js
const vm = new Vue({
  el: '#app',
  components: {
    'user-detail': UserDetail
  }
})

・HTML上で、Vueインスタンスにローカル登録されたUserDetailコンポーネントを表示する。

html
<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <user-detail></user-detail>
</div>

・親コンポーネント:UserDetail
user_nameというdataを持ち、templateで呼び出している。
またローカル登録したuser-formコンポーネントをtemplateで呼び出し、user_nameをv-bindで渡している。

js
const UserDetail = {
  components: {
    'user-form' : UserForm
            //このあと定義する子コンポーネントUserFormをローカル登録する
  },
  data(){
    return {
      user_name: 'サトウ ハナコ'
    }
 },
 template:`
   <div>
     <div>
       <span>ユーザー名: {{ user_name }} </span>
     </div>
     <div>
       <user-form :user-name='user_name' @update:user-name='user_name = $event'></user-form>
            //user-formコンポーネントには、v-bindでuser_nameを渡す
            //このあと定義するupdateメソッド
     </div>
   </div>
 `
}

・子コンポーネント:UserForm

js
const UserForm = {
  template:`
    <div>
      <div>ユーザー名変更フォーム</div>
      <input v-model='user_name' />
      <button @click='update'>名前変更</button>
    </div>
  `,
      //user_nameを編集するinputタグと、確定するボタンを設置
  props:{
    userName: { type: String, required: true }
  },
  data(){
    return {
      user_name: this.userName
    }
      //user_nameを編集するため、propsをdataに設定する
  },
  methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
      //templateで設置したボタンがクリックされると、updateメソッドが呼び出される
      //$emitメソッドで親にデータを渡す
  }
}

$emitメソッド:親コンポーネントへデータを渡す際に使う。
・第一引数…v-onのイベント名(ここでは親コンポーネントで設定した@update:user-nameのイベントハンドラが実行される)
・第二引数…親コンポーネントに渡す値

ここで親コンポーネント「UserDatail」を見てみる。

<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>

この$eventには、$emitの第二引数にしていしたthis.user_nameが格納される。

これで親コンポーネントによりユーザー名が表示され、子コンポーネントによりユーザー名変更フォームが表示される。
フォームにテキストを入力してボタンをクリックすると、親コンポーネントによるユーザー名の表示も変更されるようになる。

sync修飾子を使ってデータを渡す

ここまでで書いたコードを、sync修飾子を使って書き換えてみる。

sync修飾子は親コンポーネントのv-bind属性に指定する。

・親コンポーネント「UserDatail」のv-bindが使われている部分を編集する。
sync修飾子をつけることで、@以下を記述する必要がなくなる。

変更前
<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>

変更後
<user-form :user-name.sync='user_name'>
</user-form>

・子コンポーネント「UserForm」の$emitの呼び出し部分を編集する。
$emitの第一引数を'メソッド名: propsの名前'とする。

変更前
methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
}

変更後
methods: {
    update(){
      this.$emit('update:userName', this.user_name)
    }
}

これでコードを実行すると、同じように動作する。

スロットの使い方

slotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。

以下の例で、使い方を見てみる。

・js
header・main・footerのそれぞれにslotタグが使われており、layoutコンポーネントを使用する側から、この3つのslotにコンテンツを挿入することができる。

js
const Layout = {
  template:`
    <div class ="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `
}

const vm = new Vue({
  el: '#app',
  components: {
    'layout': Layout
  }
})

・html
layoutタグでlayoutコンポーネントを呼び出し、slotに挿入するコンテンツを記述する。

html
<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <layout>
    <template slot='header'>
       //name="header"と指定されているslotタグの位置に挿入される
      Header
    </template>
    Main
    コンテンツ
       //slot属性を指定せずに記述した場合、
        layoutコンポーネントの名前なしslotの位置に挿入される
    <span slot='footer'>
       //spanのような通常のhtml要素にslot属性を指定した場合は、
        span要素自体がslotの位置に挿入される
      Footer
    </span>
  </layout>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

改めてクロージャ(Clojure)とは何か【JavaScript】

JSの基本をやり直すためにJavaScriptにとってのクロージャについて解説。

よくある解説として、
「クロージャとは関数閉包のことです。(終わり)」
のようなWiki情報だけで理解できる人は前提知識がいろいろありそうな人でないと無理な気がします。
そこで自分なりにピンとくるように噛み砕いて見ます(間違えていたり語弊があったら指摘をいただきたいです)

もう少し優しくクロージャって何?

JavaScriptにおけるクロージャとは2つの要素が組み合わさったものとして考えます。

①関数内にスコープされたプロパティ(キーと値)のまとまったデータをもつこと
②関数の中に関数があって返り値にもなっていること

①+②がクロージャです。スコープとはご存知だと思いますがここでは宣言された関数の範囲のことを指します。
そして①をよりJavaScript以外でも抽象化させた専門用語として"環境"と呼ぶそうです(レキシカル環境とも呼ぶ)。

さて話は戻して、ここでクロージャ(関数clojure)のソースコードを載せました。

sample.js
const clojure = () => {
  let member = 0;

  const shareHouse = () => {
    return (member += 1);
  };

  return shareHouse;
};

上のソースコードでいうところの
「①関数内にスコープされたプロパティ(キーと値)のまとまったデータをもつこと」は、
宣言した変数memberのことです。(プロパティに置き換えると { member: 0 } )

「②関数の中に関数があって返り値にもなっていること」は、
関数shareHouseにあたります。

クロージャの使い方

先のコードだけ見てもどのように使うのかがわかりません。以下の手順で実行すると見えてきます。

①返り値が return shareHouse となっているのでそのまま 関数clojure をそのまま実行して 返り値(関数shareHouse) を取り出す。
②shareHouseをそのまま2回実行する

するとどうでしょうか。
shareHouseから見て外側に宣言されたmemberの値が更新されています。

sample.js
const clojure = () => {
  let member = 0;

  const shareHouse = () => {
    return (member += 1);
  };

  return shareHouse;
};

const invite = clojure();
console.log(invite()); // 返り値 1
console.log(invite()); // 返り値 2

当然ながら 変数member は 関数clojure の中でスコープされているのでよりグローバルな変数から代入されても値は保持されています。

sample.js
//省略

const invite = clojure();
console.log(invite()); // 返り値 1
console.log(invite()); // 返り値 2

let member = 10;
console.log(invite()); // 返り値 3

クロージャの使い道

「これの何が嬉しいの?」とここで疑問に思います。

①関数内にスコープされたプロパティ(キーと値)のまとまったデータをもつこと
②関数の中に関数があって返り値にもなっていること
と先に述べましたが、実際のコードにおいて②の関数とは計算だったり、APIを引っ張ってきたり、繰り返し処理をいれたりと機能が実装されています。

つまり、関数の処理結果を関数の範囲内に閉じ込めておきながら値を引っ張っていきたいケースで使うことができます。

省略したクロージャの書き方

しかし同じような手法はクラスにすれば同じことができます。(もしくはオブジェクト指向型の書き方で)
加えて、まだ直感的に理解しやすいのはクラスです。

しかしクロージャであればクラスよりもスッキリして書くことができます。

sample.js
const clojure = (member = 0) => () => member += 1;

const invite = clojure();
console.log(invite()); // 返り値 1
console.log(invite()); // 返り値 2

clojureの引数に member = 0 をおくことで変数の宣言ができます。
そして 関数clojure の返り値である () => member += 1; を 関数clojure外 から実行すれば先の例と同じように値を保持したまま値を変更していくことができました。

もしくは、

sample.js
const clojure = (member = 0) => (invited = 1) => member += invited;
const invite = clojure();
console.log(invite());
console.log(invite());

関数clojure の返り値にも引数を設定した形である (invited = 1) => member += 1; のような形で値を保持したまま値を変更していくこともできます。

ただし、返り値は一つしか取れないため複数の機能をもったものを実装したいのであればクラスの方が良いでしょう。

なんだかパッとしないなという人は下のコードを見れば理解しやすいはずです。

sample.js
const clojure = (member = 0) => {

  // const shareHouse = (invited = 1) => {
  //   return (member += invited);
  // };

  return (invited = 1) => {
    return (member += invited);
  };
};


// const clojure = (member = 0) => (invited = 1) => member += invited;
const invite = clojure();
console.log(invite());
console.log(invite());

改めてクロージャとは

ここまでくれば堅苦しい言い方、「クロージャとは関数閉包のことです。(終わり)」と言われても少しは言わんとすることがわかってきたのではないでしょうか。

更にここで、MDN Web Docsのクロージャの定義を引用します。
「クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。」

つまり、データ(プロパティのまとまり)の値の変化を関数の中の関数がコントロールしているといったところでしょうか。
ちなみにレキシカル環境とは何か、についてですが、レキシカル環境とは、静的スコープにおける関数の環境のことで、静的スコープとはJavaScriptのスコープの決まり方です。対照的に動的スコープがあります。
この点を掘り下げた内容に関してはJavaScript の原理:クロージャの真実が詳しいです。

なぜJavaScriptの関数を使ったクロージャは値を保持できるのか

sample.js
const clojure = () => {
  let member = 0;

  const shareHouse = () => {
    return (member += 1);
  };

  return shareHouse;
};

ここでの 変数member は 関数shareHouse の引数でもなければローカル変数でもありません(つまり自由変数)。しかし 関数shareHouse の中で値を参照できています。
これはJavaScriptのメモリ管理の仕組み上、クロージャにおける自由変数はメモリリークされずに残っているためです。

Reactの関数コンポーネントにおいてもクロージャの仕組みが用いられることがあります。

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

自律カスタム要素の使い方

自律カスタム要素の使い方

自律カスタム要素とは、<app-custom-element>のようなこのままページにおける自作HTML要素のことです。組み込み要素から継承するものと違って、前者は継承元の動きを受け継いだうえで、ページには<p is="app-custom-element">のようにis属性の指定が必要であるに対して、自律カスタム要素は完全に0から作り上げたものです。中身の構築はshadow domをつけるのが一般的な手法です。

// 基礎の組み込みhtml要素からではなく、大本となるHTMLElementから継承する
class PopUpInfo extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // 自律カスタム要素にshadow domをつけるのが一般的な手法
    const shadow = this.attachShadow({ mode: 'open' });

    const info = document.createElement('span');
    info.setAttribute('class', 'info');

    // 自身に渡してきた属性をアクセスすることが可能
    const text = this.getAttribute('data-text');
    info.textContent = text;

    // Create some CSS to apply to the shadow dom
    const style = document.createElement('style');

    style.textContent = `
      .info {
        font-size: 0.8rem;
      }
    `;

    // Attach the created elements to the shadow dom
    shadow.appendChild(style);
    shadow.appendChild(info);
  }
}

// カスタム要素を登録する
customElements.define('pop-up-info', PopUpInfo);

ページでの使い方は以下のようです。

<pop-up-info data-text="Your card"></pop-up-info>

dev toolsで見たイメージは大体こんな感じです:
image.png

参考記事

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

【React】Parsing error: Cannot find module '@babel/helper-validator-identifier'が出た時の対処法

背景

npx create-react-appでプロジェクト立ち上げたらParsing error: Cannot find module '@babel/helper-validator-identifier'と出たので対処。

状況

create-react-appで立ち上げたプロジェクトのimport文でエラーが起きる。
そこでpackage.jsonを確認すると以下のようになっていました。

package.json
{
  "name": "react-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
}

babel関係が一切インストールされていませんでした。

対処法

なので、以下のコマンドをターミナル上で実行。

npm install --save-dev @babel/core @babel/preset-env

するとエラーは消え、普通にアプリが立ち上がりました。

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

関数どれ使う?JavaScript

JavaScriptには関数の定義方法がいくつかありますね。

関数宣言

kansuSengen.js
function echo() {
  console.log("hello");
}

echo(); // helloと出力される

一番ベターなやつです。これが書ければもう何も困ることはないかなと言う感じです。

関数式

kansuSiki.js
const echo = function () {
  console.log("hello");
};

echo(); // helloと出力される

関数宣言と同じ挙動をしますが、ちょっと長い記述になっています。

アロー関数

kansuSiki.js
const echo = () => {
  alert("hello");
};

echo(); // helloと出力される

簡単に言うと関数式の略版ですね!
今風でとてもかっこいいです。積極的にこれを使いましょう。
違いもありますが、当記事では(関数式 = アロー関数)としておきます。

関数宣言と関数式はどっちを使うのが良いのか?

僕的には、どれでも良いと思います。プロジェクトのコーディング規約に従えば良い。
関数定義法の細かい仕様を考えだすとめんどくさそうなので、少しだけ説明します。

関数宣言には巻き上げがある

kansuSengen.js
echo(); // helloと出力される

function echo() { // 呼び出しより後の記述だが実行できる。
  console.log("hello");
}

このコードは正常に実行されます。呼び出しより後に宣言されているのに。
これが巻き上げというものです。
プログラムは基本上から下に実行されますが、宣言は実行時に全て上に移動します。
※変数宣言も同様です。(変数の場合、宣言のみで初期化はされない)

関数式は巻き上げがない

kansuSiki.js
echo(); // 呼び出しより後に定義されている為エラーとなり、出力出来ない。

const echo = function () {
  console.log("hello");
};

これは実行出来ません。なぜなら巻き上げられるのは宣言のみだからです。

const echo

のみが巻き上げられ、echo( )時点では処理の代入がされていないのです。
じゃあ記述場所を問わない関数宣言の方が良さそう!と思いますが、大体はこんな処理が一般的な気がします。

kansuSiki.js
window.addEventListener('load', function () {
  echo(); // helloと出力される。
});

const echo = function () { 
  console.log("hello");
};

これは画面表示されたタイミングでecho( )を呼んでいる単純なプログラムです。
そもそもスコープが違うので実行出来ます。やっぱり関数宣言、関数式(アロー関数)どちらでも良さそうですね。スコープについても少しだけ触れます。

グローバルスコープとローカルスコープ

変数の参照範囲はどのプログラム言語でもあります。

グローバルスコープ

  • トップレベル(関数の外)に記述されている変数や関数はグローバルスコープとなる。
  • ここに宣言されている変数はプログラム中のどこからでも参照可能。(ただし、同じグローバルスコープ内では記述順によっては先ほどの関数式の例のようにエラーとなる)
  • varで宣言した場合、グローバル変数となる。

補足

グローバル変数とはプログラム中のどこからでも参照する事が出来る変数の事です。
例えば、HTML要素を取得するときにdocument.get〜みたいに書くと思います。
このdocumentもグローバル変数の1つなのです。ちなみに、letやconstで定義した場合グローバルスコープにはなりますが、グローバル変数にはなりません。

ローカルスコープ

ローカルスコープには関数スコープとブロックスコープというものがあります。

関数スコープ

  • 関数内に宣言された変数は、その関数内でのみ参照可能。

ブロックスコープ

  • ブロック内に宣言された変数は、そのブロック内のみ参照可能
    ※ブロックスコープが適用されるのはlet、constで宣言されたもの

結局どれ使うか

やっぱり自分の書きやすい方法で良いと思います。
注意点があるとすれば、関数宣言は上書きされます。

kansuSengen.js
window.addEventListener('load', function () {
  echo(); // おはようと出力される。
});

function echo() {
  console.log("hello");
}

function echo() { // こっちが勝つ!!
  console.log("おはよう");
}

javaみたいにエラーなんて出してくれません。
当たり前ですが、関数式をvarで定義した場合も上書きされます。

kansuSiki.js
window.addEventListener('load', function () {
  echo(); // おはようと出力される。
});

var echo = function () {
  console.log("hello");
};

var echo = function () { // こっちが勝つ!!
  console.log("おはよう");
};

varは再代入、再定義が可能なので関数式使うのであればconstが無難かと思います。
つまり同じ関数名をつけなければどちらでも良いですね。
結論、好きな方、プロジェクトに添った方を使用しましょう。

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

Preloadでブラウザにリソースを前もってFetch させる

01 HTMLタグ

<link rel="preload" href="script.js" as="script">

02 Preload(上記) + リソースを通常読み込み処理

// script
let pl = document.createElement('script');
pl.src"xxx.js";
document.body.appendChild(pl);

// StyleSheet
let plStyle = document.createElement('link');
plStyle.href="xxx.css";
plStyle.rel="stylesheet";
document.head.appendChild(plStyle);

03 CSSファイルが先読みできたらすぐ実行

<link rel="preload" href="style.css" as="style" onload="this.onload=null;this.rel="stylesheet">

04 スクリプト 先読み

// ブラウザはxxx.js リソースを先読み込み、実行しない
ar preloadLink = document.createElement("link");
preloadLink.href = "xxx.js";
preloadLink.rel = "preload";
preloadLink.as = "script";
document.head.appendChild(preloadLink);

// ブラウザで xxx.js リソースを実行
var preloadedScript = document.createElement("script");
preloadedScript.src = "xxx.js";
document.body.appendChild(preloadedScript);

05 as属性の値

// as属性の値

audio: オーディオファイル, as typically used in <audio>  

document: HTML ドキュメント  <frame> or <iframe> 埋め込み  

embed: <embed> エレメント に埋め込みリソース  

fetch: fetchによるアクセスリソース もしくは XHR リクエスト(ArrayBuffer or JSONファイル)  

font: Fontファイル

image: Imageファイル

object: <object> エレメントに埋め込みリソース  

script: JavaScriptファイル

style: CSS stylesheet

track: WebVTTファイル

worker: JavaScript web worker もしくは shared worker

video: Videoファイル <video>タグ

参照元:MDN rel="preload" によるコンテンツの先読み
https://developer.mozilla.org/ja/docs/Web/HTML/Preloading_content

参照元:rel=”preload” によってリソースを先読みさせる
https://laboradian.com/rel-preload/

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

JavaScript データ型

JavaScriptのデータ型

ここで紹介するのは以下の六つのプリミティブ型

  • undefined
  • null
  • Boolean
  • Number
  • String
  • Symbol

Undefined

Undefined型には「undefined」というリテラル値のみ。
var又はletで変数を宣言して、初期化されていない状態は「undefined」になります。

let messge;
console.info(message);//undefined
console.info(name);//例外

宣言されていない変数に対して行える操作はtypeofのみ

let name;
console.info(typeof name);//undefined
console.info(typeof age);//undefined

ここはちょっと要注意、
宣言されて初期化していない変数と宣言されていない変数にtypeof操作すると両方とも[undefined]になります。

Null

Null型もUndefined型と同じ、「null」というリテラル値のみ。
「null」というのはオブジェクトを指していないことを表します

let letNull = null;
console.info(typeof letNull);//object、見ての通り「null」はオブジェクトです

※「undefined」も「null」から派生しているが、違う物なので。以下のプログラムを見れば分かると思う。

console.info(undefined == null);//true
console.info(undefined === null);//false

Boolean

Boolean型以外のデータ型をBoolean型に変換する際のルール

String型:空文字以外は「true」
Number型:0以外は「true」(マイナスとインフィニティも含まれる)
Object型:null以外は「true」
Undefined型:undefinedは「false」、N/Aは「true」

Number

整数の変数は基本的に十進数のリテラルで定義するのですが、十進数以外のリテラルでも定義可能
八進数の場合は必ず0から始まる、その後八進数の数字を続く

let num1 = 55;//整数
let num2 = 070;//八進数の「70」と認識され十進数の56をセットされる
let num3 = 079;//9が八進数ではないので、0を無視して十進数の79をセット
let num4 = 08;//上記と同様、0を無視して十進数の8をセット

だが厳格モードではプレフィックスの「0」がsyntaxエラーになるため「0o(八進数)」、「0x(十六進数)」で定義しないといけない。

'use strict';
let num1 = 55;//整数
console.log(num1);
let num2 = 0o70;//八進数、十進数のの56でセット
console.log(num2);
let num3 = 0o79;//9が八進数ではないので、エラー
console.log(num3);
let num4 = 0o8;//上記と同様、エラー
console.log(num4);
let num5 = 0xA;//十六進数、十進数の10でセット
console.log(num5);

浮動小数点数を定義するときはこうなります

let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1;  //有効ですがお勧めしない

ECMAScriptでは常に浮動小数点数を整数に変換しようとするので、下記の定義では整数として処理される

let floatNum4 = 1.;  //小数点以降はないので、整数の1として処理
let floatNum5 = 10.0; //小数点以降は0なので、整数の10として処理

浮動小数点数は算術計算の際下記のようなの精度上の問題があります、これは全部ではないので要注意

console.log(0.1+0.1+0.1);  //0.30000000000000004
console.log(1+0.1+0.1);    //1.2000000000000002
console.log(1+0.05+0.05+0.05);  //1.1500000000000001

これはECMAScriptだけの問題ではなく、IEEE 754標準を利用する他言語にも同じ問題が存在する

Numberによる数値変換

console.log(Number(true));              // 1
console.log(Number(false));             // 0
console.log(Number(5));                 // 5
console.log(Number(null));              // 0
console.log(Number(undefined));         // NaN
console.log(Number("11"));              // 11
console.log(Number("-11"));             // -11
console.log(Number("11.1"));            // 11.1
console.log(Number("011"));             // 11
console.log(Number("0xA"));             // 10
console.log(Number(""));                // 0
console.log(Number("11a"));             // NaN

let objToNum1 = new Object();
objToNum1.valueOf = () => "10";
objToNum1.toString = () => "20";
console.log(Number(objToNum1));         // 10

let objToNum2 = new Object();
objToNum2.valueOf = () => "10a";
objToNum2.toString = () => "20";
console.log(Number(objToNum2));         // NaN

let objToNum3 = new Object();

objToNum3.toString = () => "20";
console.log(Number(objToNum3));          // 20

let objToNum4 = new Object();
console.log(Number(objToNum4));         // NaN

Numberでオブジェクトを変換する際はまずvalueOfの値で変換する、valueOfがない場合はtoStringの値で変換する、両方とも存在しない場合はNanになります。

parseIntによる数値変換

console.log(parseInt("   1234blue")); // 1234 
console.log(parseInt("")); // NaN 
console.log(parseInt(" 0xA")); // 10
console.log(parseInt(22.5)); // 22 
console.log(parseInt("70")); // 70
console.log(parseInt("0xf")); // 15
console.log(parseInt("f",16)); // 15
console.log(parseInt("fl",16)); // 15、「l」が有効な16進数ではないため無視

parseIntでは先頭のスペースが無視され、最初の数字又は「+ -」から解析していく、文字列の最後又は数字以外の文字まで終了。後続の数字以外の文字は皆無視される。

parseFloatによる数値変換

console.log(parseFloat(" 1234blue")); // 1234
console.log(parseFloat(" 0xA")); // 0 
console.log(parseFloat(" 22.5")); // 22.5 
console.log(parseFloat(" 22.34.5")); // 22.34 
console.log(parseFloat(" 0908.5")); // 908.5 
console.log(parseFloat(" 3.125e7")); // 31250000

parseFloatでは先頭の「0、スペース」が無視される、二番目の小数点から以降は無視される。十進数のみ処理するのでそれ以外は「0」

String

let num = 10; 
console.log(num.toString()); // "10" 
console.log(num.toString(2)); // "1010" 
console.log(num.toString(8)); // "12" 
console.log(num.toString(10)); // "10" 
console.log(num.toString(16)); // "a"

数値のtoString関数を使って10進数以外の文字列を得る事が可能。

console.log(String(null)); // "null" 、toStringを利用できないのでこうする必要がある
console.log(String(undefined)); // "undefined"  、toStringを利用できないのでこうする必要がある

Symbol

唯一の標識を定義するためのデータ型、例えばObjectに絶対に重複する事のないプロパティを定義したい時はSymbolを使うとよい。
Symbolは絶対に重複することのないプロパティ名だと考える方が分かりやすいかと。

Symbolの定義は

let s1 = Symbol();
let s2 = Symbol("s2");  //パラメータはコメントみたいな物だから、特に気にしなくてもいい
let gs1 = Symbol.for("global symbol"); //グローバルSymbolレジストリリスト内で利用可能なシンボルを生成します、既に存在したらそれを返す

let symbolObj = {
 [s1]:'普通のシンボル',
 [s2]:'パラメータ付きのシンボル'
};
symbolObj[gs1]='グローバルシンボル';
console.log(symbolObj);//{Symbol(): "普通のシンボル", Symbol(s2): "パラメータ付きのシンボル", Symbol(global symbol): "グローバルシンボル"}

シンボルはコンストラクタとして使用することはできない

let b = new Boolean();
let n = new Number();
let s = new String();
let sym = new Symbol();//type errorになります
let mySymbol = Symbol();//このようにシンボルを定義する

組込シンボルに関してはここから参照

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

【RPGツクールMV/RPGツクールMZ】マップのメモ欄を使った判定

RPGツクールMVではmeta."文字列"で判定可能。
私がプラグイン改造で実装した例を掲載します。

GALV_BasicEventShadows.js
https://galvs-scripts.com/2016/04/07/mv-basic-event-shadows/

例.マップのメモ欄にを含む場合にプレイヤーの影を生成しない
$dataMap.meta.文字列で判定

  Spriteset_Map.prototype.doActorShadows = function () {
    if ($gameSystem._playerShadow) {
      // add
      var fols = $gamePlayer.followers()._data;
      for (var i = 0; i < fols.length; i++) {
        fols[i]._shadow = true;
        this.createBShadow("f" + i + 1, fols[i]);
      }
      $gamePlayer._shadow = true;
      //マップメモ欄に<noShadow>が含まれている場合プレイヤーの影を消す(returnで処理を中断し生成しない)
      if ($dataMap.meta.noShadow) {
        return;
      }
      this.createBShadow("f0", $gamePlayer);
    } else {
      // Remove
      var fols = $gamePlayer.followers()._data;
      for (var i = 0; i < fols.length; i++) {
        fols[i]._shadow = false;
        this.destroyBShadow("f" + i + 1, fols[i]);
      }
      $gamePlayer._shadow = false;
      this.destroyBShadow("f0", $gamePlayer);
    }
  };

BattleVoice.js
https://plugin.fungamemake.com/archives/2606

例.戦闘中かつメモ欄にが含まれるスキルを使用した場合、ボイスSEを演奏しない。
BattleManager._action.item().meta文字列で判定

  var playActorVoice = function (actor, type) {
    if (!canPlayActorVoice()) {
      return;
    }
    //改変箇所。戦闘中かつメモ欄に<invalidVoice>が含まれるスキルを使用した場合、演奏しない。
    if (BattleManager._action.item().meta.invalidVoice) {
      return;
    }

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

シャドーDOMの使い方

シャドーDOMの使い方

shadow domとはカプセル化したdom treeを画面上のエレメントに動的にアタッチする手法のことです。このサブdom treeには外部cssの影響を受けず、独自で指定されたcssのみが適用されます。どういう場面で使うべきというと、外部のcss(例えばプロジェクト全体に影響を及ぼすstyle.css)の影響を受けたくない場合、html要素をshadow domにすれば実現可能です。

・基本的な使い方
let shadow = elementRef.attachShadow({mode: 'open'});
shadow domをつける要素に対してattachShadowメソッドを呼び出して、この要素にshadow rootをつけた同時に、それの参照をもらい、この要素自身もshadow hostとなります。
オプションmodeをopenにすると、elementRef.shadowRootで外部のJavascriptからshadow rootへのアクセスを可能になります。falseに設定すると、.shadowRoot呼び出してもnullしか返ってこなくなります。
attachShadow呼び出し前: element
attachShadow呼び出し後: element(shadow host) → shadow root

shadow.appendChild(document.createElement('b'));
shadow.appendChild(document.createElement('style'));
shadow dom treeはすでに作成済みです、中に要素をどんどん追加しましょう。追加されたstyley要素はshadow dom tree内にあるものにしか適用されないです。
appendChild呼び出し前:element(shadow host) → shadow root
appendChild呼び出し後:element(shadow host) → shadow root → b

作成されたdom要素をdev toolsでスクショをとってみた:
image.png

参考記事:
https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_shadow_DOM

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

このエラーの解決方法 Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>

Reactを書いていたら以下のようなWarningエラーが
Consoleに出たので備忘録として残しておく

今回出現したエラー文

Consoleに、以下のようなエラー文が出力された

Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>

エラー文をDeepLで翻訳すると・・・

<div>は<p>の子孫として表示できません

ん?どういうこと?

エラーの原因

先ほどのエラー文は
「HTMLの入れ子のルール」に関するエラー

今回は、pタグの中にdivタグを
入れ子にしているからWarningエラーが起きています

pタグの中にdivタグを
入れ子にすることはできないからです

解決方法

Reactの場合は関数コンポーネントの
returnでJSXを返している箇所のコードを見て

以下のような
HTMLの構成になっていないか確認する

const Sample = () => {
  return (
   <p>
     <div>pタグにdivは入れられない</div>
   </p>
 )
}

もしこのような構成になっているコードを確認したら
以下のようにHTMLの構成を変更してあげればOKです!

const Sample = () => {
  return (
   <div>
     <p>divタグにpタグは入れられる</p>
   </div>
 )
}

これで解決できるはずです!

ところで、なぜpタグにdivタグは入れられないのか?

この質問に対する回答としては

HTML5から「ブロック要素」と「インライン要素」
という概念が廃止されて

入れ子のルールが「コンテンツモデル」
という概念で決まるようになったからです

コンテンツモデルとは「その要素にはどのカテゴリーのコンテンツを入れていいか」を決めているルールのことで、タグの入れ子のルールは全てこの「コンテンツモデル」で決まっています

だからpタグの中にdivタグを入れると「あなたの書いたコードはコンテンツモデル的におかしいよ。なので警告出しとくから直してね」というWarningエラーが出たんですね。Reactは優しい!

この話題に関する詳しい説明は
この記事が非常にわかりやすいです。

暇な時に軽く目を通しておくと
HTMLをより深く理解できるのでおすすめです!

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

map()メソッドについて【備忘録】

こんにちは!
今回も、開発で、map()メソッドを使う機会があったので、備忘録としてまとめて行きます!

map()メソッドって...?

map()

map() メソッドは、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。
出典)MDN Array.prototype.map()


配列の各要素をコールバック関数で処理して、関数の戻り値から新しい配列を作って返す。
出典)柳井政和著 『JavaScript[完全]入門』(2021)p.227


コールバック関数とは、関数の引数として渡される関数のことです。関数の引数には、文字列や数値などのように、「値」を取りますが、関数を引数に取ることも可能なのです。

今回のmap()メソッドは、引数として渡された関数で行われた処理の結果から、新しい配列を作るメソッドなのです!

map()メソッドの使用例

map()メソッドの使用例は、以下です。

const array = [2, 4, 6, 8];

const arr = array.map(ary => ary*2);

console.log(arr);
/* 実行結果
[4, 8, 12, 16]
*/

上記のコードを元に、ちょこっと解説してみます!

最初に配列を用意します。上の例では、配列arrayを用意し、4つの要素を格納しています。

ここで、例えば、配列の要素を全て2倍にしたいと思ったとします。map()メソッドの登場です。
map()の引数として、関数が渡されていますね?(コールバック関数)
その関数の中で、ary*2と言う処理が行われています。
この処理は、最初に作った配列arrayの要素全てに対して行われます。

このコールバック関数での処理の結果を戻り値として、これらを使って、新しい配列が作られます。

まとめ

map()メソッドとは、引数に渡した関数で元の配列の要素それぞれに対して処理を行い、その処理結果から新たな配列を作り出すメソッドでした!

お読み頂きありがとうございました!
もし、間違いや補足などございましたら、コメント頂けますと助かります!

参考資料

MDN Array.prototype.map()

柳井政和著 『JavaScript[完全]入門』(2021)

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