20210120のJavaScriptに関する記事は25件です。

AppSync & GraphQL 入門

AppSync

AppSync とは?

GraphQL というAPI仕様を用いて「柔軟なAPI」を提供するAWSのマネジメントサービス

ちなみに、従来の REST API 形式だと AWSは API Gateway を提供している

GraphQL とは?

Facebookが開発しているWeb APIのための規格

「クエリ言語」 と 「スキーマ言語」 からなる

REST API は、1URLに対し1つのAPIや情報を提供できるのに対し、

GraphQL は欲しいデータを以下のようなクエリとして発行すると、欲しいデータを欲しいObject形式で得ることができます

// リクエスト
query GetCurrentUser {
  currentUser {
    id
    name
  }
}

// レスポンス
{
  id: 'hoge',
  name: 'yamada'
}

AppSyncの仕組み

AppSyncImage-1024x542

AppSyncは直接DynamoDBの値を取得・更新・削除することができます

従来のAPI Gatewayだと、AWS Lambda が間に必要でしたが、

AppSyncは Lambda レス でDynamoDBへのアクセスが可能です

代わりに、AppSync内のリゾルバーという領域にロジックを記述します

クエリ

実行されるGraphQlのこと

スキーマ

どの型の値をどこで使うかを定義する設計書

リゾルバー

関数のこと。ロジックを記述する。

リゾルバーは、リクエストマッピングテンプレートレスポンスマッピングテンプレート で構成さる

リクエストマッピングテンプレート は、「変換」と「実行」のロジックが含まれている

リソース

データベースのこと。AppSync では AWS DynamoDB に自動的に接続される

AppSyncの料金

使用した分だけ課金されます

クエリとデータ変更操作

4.00USD ≒ 423.87 円 / クエリおよびデータ変更操作 100 万回あたり

リアルタイム更新

データが更新された際に、リアルタイムに更新する機能

2.00USD ≒ 211.94 円 / リアルタイムアップデート 100 万回

最初の12ヶ月の無料利用枠の対象でもあるようなので、登録後12ヶ月は一定回数は無料で使用できます

料金の詳細はこちら

試してみる

実際に、AppSyncを用いてイベントを取得・登録する処理を実装してみます、とても簡単です

AppSync API を作成

AWS ログインして、AppSyncページへ移動し、「APIを作成」

サンプルプロジェクトから「イベントアプリ」を選んで「開始」

API 名は [yourname] App としてください

左メニューから「クエリ」ページに移動すると、GraphQL Explorer が表示されます

ここで、GraphQLを試すことが可能です

image

▶︎ボタンから、実行したいクエリを選択してみると、右側に結果が表示されます

image

デフォルトで2つのクエリが用意されています

mutation CreateEvent {
  createEvent(
    name: "My First Event"
    when: "Today"
    where: "My House"
    description: "Very first event"
  ) {
    id
    name
  }
}

query ListEvents {
  listEvents {
    items {
      id
      name
    }
  }
}

1つめの mutation CreateEvent は、新たな Event のデータを作成するための mutation です

2つめの query ListEvents は、DBに登録されている Event のデータを取得するための query です

CreateEvent を何度か実行すると ListEvents の結果が変わることがわかります

GraphQLには3種類のクエリがある

| 名前 | 説明 |
| -- | -- |
| query | データ取得 (read) |
| mutation | データ作成/更新/削除 (create / update / delete) |
| subscription | リアルタイムイベントを受け取れる。内部的にはwebsocketが使われている |

先ほどのサンプルでは query と mutation を使用しています

実際に Javascript で GraphQL を使ってみる

Axios を使って試してみます

こちらにサンプルコードを用意しました

URL と API KEY を AppSyncコンソールの設定ページから見つけて、セットしてみてください

上手くいけば、GraphQLのresultが、consoleに表示されます

  const data = await axios.post(
    API_URL,
    {
      query: `
      // ここにqueryをかく    
      `
    },
    {
      headers: {
        // header に APIキーを渡す。 appSync設定画面から取得
        "x-api-key": ""
      }
    }
  ); 

request body にクエリを記述、request headerx-api-key として API KEY を持たせることで認証されます、とても簡単ですね

認証方法

appSyncでは、4つの認証方法が用意されています

| 名前 | 概要 | ユースケース |
| -- | -- | -- |
| API_KEY | 今回使ったもの。最大 365 日間有効に設定可能で、該当日からさらに最大 365 日、既存の有効期限を延長可 | パブリック API の公開が安全であるユースケース、または開発目的での使用が推奨 |
| AWS_IAM | IAMポリシーを紐づけて使用 | IAMロールごとに、特定の機能のみに制限したい場合 |
| OPENID_CONNECT | OpenID Connect (OIDC) トークンを適用 | OpenID Connectを使いたい場合(未調査) |
| AMAZON_COGNITO_USER_POOLS | Amazon Cognito ユーザープールによって提供される OIDC トークンが使用されます | Amazon Cognito ユーザープールによって提供されるOIDCトークンを使いたい場合(未調査) |

基本的には、上2つ API_KEYAWS_IAM を使うパターンが多いでしょう

認証についての詳細(公式ページ)

データベースの中身を見てみる

左メニューから、 データソース を選択すると、DynamoDB へのリンクがあります

AppSync が自動生成してくれたテーブルが、ここに表示されています

image

APIを変更・追加してみる

createEvent に who という項目を追加してみる

AWSコンソールの左メニューから スキーマ を選択すると、定義されている Schema が表示されます

この中から、 Mutation の下にある createEvent を見つけ、引数に who を追加してみます

スクリーンショット 2020-10-28 19 28 00

右上から スキーマを保存 します

クエリ ページから、who に適当な値を追加して、実行してみます

image

これで、dynamoDBに who が追加されたか確認してみましょう

左メニューから データソース -> AppSyncEventTable のリソースを開きます

あれ、項目 who が追加されていると思いましたが、追加されていません?

image

理由は簡単です、 リゾルバー も変更する必要があります?

リゾルバー とは、このページの冒頭で表示した図にあるように、ロジックを記述する領域です

リゾルバー の変更は、 スキーマ ページの右カラムから可能です

createEvent を見つけましょう⤵︎

image

リクエストマッピングテンプレート を以下のようにして、 who を追記します

{
    "version": "2017-02-28",
    "operation": "PutItem",
    "key": {
      "id": { "S": "$util.autoId()"}
    },
    "attributeValues": {
      "name": { "S": "$context.arguments.name" },
      "where": { "S": "$context.arguments.where" },
      "when": { "S": "$context.arguments.when" },
      "who": { "S": "$context.arguments.who" },
      "description": { "S": "$context.arguments.description" }
    }
}

リゾルバーを保存して、実行すると、who 項目が追加されていることが確認できました?

image

今回は内部の挙動を理解するために、

ブラウザからAWSコンソールを通じてスキーマやリゾルバーの変更を行いましたが、

実際には AWS CloudformationAmplify Framework などを用いると良いそうです

まとめ

メリット

  • GraphQL は REST API に比べて欲しいデータを欲しい形式で得ることが可能

  • GraphQL により、画面や機能ごとに、個別にAPIを定義するコストが削減される

  • AppSync を使えばリソース(DynamoDB)との連携を楽に行うことができる

  • 既存の REST API を AppSync でラップして、GraphQL を導入することも可能らしい(未調査)

デメリット

  • GraphQL, Appsync の学習コストがかかる

  • フロントエンドの都合の良いように、値を返す必要があるため、リゾルバーのロジックが複雑になる

  • 効率的にデータを処理できないので、パフォーマンスが低下し、N+1問題が発生する

※ N+1問題・・・ループ処理の中で都度SQLを発行してしまい、大量のSQLが発行されてパフォーマンスが低下してしまう問題のこと

実際にAppSyncで実装してみたページ

dynamoDB に入っているニュースのデータを AppSync を使って表示しています

https://umamichi.com/news/

これから調べる

AWS_IAM を使った AppSync 認証方法。 Cognito を使うらしいです

参考

https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/welcome.html

https://xp-cloud.jp/blog/2020/06/01/7159/

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

モトローラ形式(S-Record)(.motファイル)のチェックサムを付与するツールつくった【自分用】

モトローラ形式の文字列をぶちこむと、末尾のチェックサムをいい感じにしてくれるツールをつくってみた

See the Pen MotCheckSumCalculator by kob58im (@kob58im) on CodePen.

きゃぷちゃ

image.png

サンプルmotデータ(参考サイト1より)

S00F000068656C6C6F202020202000003C
S11F00007C0802A6900100049421FFF07C6C1B787C8C23783C6000003863000026
S11F001C4BFFFFE5398000007D83637880010014382100107C0803A64E800020E9
S111003848656C6C6F20776F726C642E0A0042
S5030003F9
S9030000FC

参考サイト

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

#UIFlow の BLE UART を使った #M5Stack_Core2 ( #M5Stack )からブラウザへのデータ送信とグラフ化

これまで、UIFlow の BLE UART を試して以下の記事を書いてきました。

この記事は、上記で試してきた流れと、以下の記事で使ったグラフ化とを組み合わせた内容になります。

●【JavaScript 2020】 MQTT で受信したデータを Smoothie Charts(smoothie.js)以外でリアルタイムにグラフ化: Chart.js とプラグインを利用 - Qiita
 https://qiita.com/youtoy/items/252f255c9d794bf3d964

概要

まず、今回試した内容が動作している様子をご覧ください。
M5Stack Core2 のタッチスクリーン上に触れると、その x座標か y座標の値を取得し(どちらを取得するかは、Bボタン押下で変更できる仕組みあり)、それが BLE経由でブラウザに送られます。ブラウザ側では、受け取った値を使ってリアルタイムにグラフを描いています。

プログラム

M5Stack Core2 で動作している UIFlow のプログラムと、ブラウザ側で動いている HTML+JavaScript のソースをそれぞれ記載します。

UIFlow

UIFlow側は、以下のような仕組みにしてます。

UIFlowのプログラム.jpeg

  • Aボタン押下 ⇒ 300ミリ秒間隔で動くタイマーを開始
  • Bボタン押下 ⇒ 取得する値(タッチスクリーン上で触れられた位置の座標)を x/y のどちらにするか切り替え
  • Cボタン押下 ⇒ タイマーを止める
  • タイマーの処理 ⇒ タッチスクリーン上で触れられた位置の座標 x/y を BLE で送信しつつ、画面上にも表示

タイマーの処理の最後のブロック、「テキストに変換する」を入れないとうまく動かなかったので入れたものです。
その1つ上のブロック、画面のラベルでの表示のほうはそれが不要だったのですが、テキストのプログラムに切り替えてそれぞれを見てみると、画面表示のほうはテキストに変換する処理が裏で入ってました。

HTML+JavaScript

ブラウザ側の処理(BLE関連の処理やグラフ描画の処理)は以下のように実装しています。
利用する際には、こちらを HTMLファイルにしてブラウザで開き、最初に画面上の「接続」ボタンを押してペアリングから始めてください。その後は、M5Stack Core2側を操作していく流れです。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>UIFlow  BLE</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment-with-locales.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.8.0/dist/chartjs-plugin-streaming.min.js"></script>
  </head>

  <body>
    <h1>UIFlow  BLE  グラフ描画</h1>
    <button onclick="onStartButtonClick()">接続</button>
    <br />
    <canvas id="myChart"></canvas>

    <script>
      const UUID_1 = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
      const UUID_2 = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; // Write、今回は使わない
      const UUID_3 = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // Notify

      let bluetoothDevice;
      let characteristic;

      async function onStartButtonClick() {
        try {
          console.log("Requesting Bluetooth Device...");
          const device = await navigator.bluetooth.requestDevice({
            filters: [{ namePrefix: "m5ble" }],
            optionalServices: [UUID_1],
          });
          console.log("Connecting to GATT Server...");
          const server = await device.gatt.connect();
          console.log("Getting Service...");
          const service = await server.getPrimaryService(UUID_1);
          console.log("Getting Characteristic...");
          characteristic = await service.getCharacteristic(UUID_3);
          await characteristic.startNotifications();
          console.log("> Notifications started");
          characteristic.addEventListener(
            "characteristicvaluechanged",
            handleNotifications
          );
        } catch (error) {
          console.log("Argh! " + error);
        }
      }

      const ctx = document.getElementById("myChart").getContext("2d");

      let chart = new Chart(ctx, {
        type: "line",
        data: {
          datasets: [
            {
              data: [],
            },
            {
              data: [],
            },
          ],
        },
        options: {
          scales: {
            xAxes: [
              {
                type: "realtime",
                realtime: {
                  delay: 200,
                },
              },
            ],
          },
        },
      });

      async function handleNotifications(event) {
        if (characteristic) {
          try {
            const value = event.target.value;
            const inputValue = new TextDecoder().decode(value);
            console.log(inputValue);

            chart.data.datasets[0].data.push({
              x: Date.now(),
              y: inputValue,
            });
            chart.update({
              preservation: true,
            });
          } catch (error) {
            console.log("Argh! " + error);
          }
        }
      }
    </script>
  </body>
</html>

グラフ描画まわりの処理の説明は、記事の上のほうでも掲載した以下の記事のほうをご覧ください。

●【JavaScript 2020】 MQTT で受信したデータを Smoothie Charts(smoothie.js)以外でリアルタイムにグラフ化: Chart.js とプラグインを利用 - Qiita
 https://qiita.com/youtoy/items/252f255c9d794bf3d964

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

画像のプレビュー機能

はじめに

画像投稿時に、画像ファイルの名前だけが表示されるだけで、きちんと出来ている分かりづらいと感じたので、プレビュー機能を実装しました。

プレビュー機能実装前

1.準備

1. プレビュー機能を実装させるためのjsファイルを作成する。app/javascript/packsにpreview.jsを作成
app / javascript / packs / preview.js

2.preview.jsを読み込めるようapplication.jsを編集する。

app/javascript/packs/application.js
require("@rails/ujs").start()
require("@rails/activestorage").start()
require("channels")
require('./preview') # 追記する

3.viewファイルに画像が表示される場所を指定する。

views/ideas/new.html.erb
<div class="img-upload">
  <div class="left-img-upload">
    <div class="weight-bold-text">
      関連画像(関連する画像があれば添付してください)
    </div>
    <div class="click-upload">
      <p>クリックしてファイルをアップロード</p>
      <%= f.file_field :image, id:"idea-image" %>
    </div>
  </div>
  <div class="right-img-upload">
    <div id="image"></div>  <!--追記する-->
  </div>
</div>

2.プレビュー機能実装

1 で作成したpreview.jsにプレビュー機能のコードを記述する。

app/javascript/packs/preview.js
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image');

    const createImageHTML = (blob) => {
       // 画像を表示するためのdiv要素を生成
      const imageElement = document.createElement('div');
      // 表示する画像を生成
      const blobImage = document.createElement('img');
        blobImage.className="preview"; //←createElementで生成したimgにクラス名を付けている
      blobImage.setAttribute('src', blob);
      // 生成したHTMLの要素をブラウザに表示させる
      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    };
    document.getElementById('idea-image').addEventListener('change', function(e){
      // 画像が表示されている場合のみ、すでに存在している画像を削除する
      const imageContent = document.querySelector('img');
      if (imageContent){
        imageContent.remove();
      }
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);
      createImageHTML(blob);
    });
  });
}



最後にCSSで画像のサイズを指定する。

.preview {
  height: 250px;
  width: 250px;
  object-fit: contain;
}

実装完了

画像が表示されるようになったおかげで、自分が選択した画像が分かりやすくなりました。

最後に

javascriptを用いた実装はあまり行っていなかったため、いい復習となりました。
また、createElementで生成した要素にクラス名をつける方法など学ぶことができ、勉強になりました。

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

C3jsでリアルタイムグラフ

結果

See the Pen C3js TimeSeries Realtime Graph by Nagitch (@nagitch) on CodePen.

解説

過去1分間の時間軸を表示する

軸の設定をtype: timeseries(時系列), max/minに現在秒, 一分前の秒にしておくと自動的に過去1分間の時間軸を表示してくれます。
一秒刻みでデータを登録して無理やりプロットする、みたいなトリックは使わなくて大丈夫です。
また 2021-01-20 19:47:35 のような表示にする&1分前の計算のためにdayjsを使っていますが、同じ形になれば何を使っても問題ありません。

const timeNow = () => dayjs().format('YYYY-MM-DD HH:mm:ss');
const timeTail = () => dayjs().subtract(1, 'm').format('YYYY-MM-DD HH:mm:ss');

const chartAxis = {
  x: {
    type: 'timeseries',
    min: timeTail(),
    max: timeNow(),

また等間隔に満遍なく軸表示するために fit: true, 表示を倒して見やすくするため rotate:-50 を指定します。

    tick: {
      fit: true,
      rotate: -50,
      format: '%Y-%m-%d %H:%M:%S',
    }

時間軸(表示範囲)の更新

時間軸の更新には axis.min(), axis.max() 関数を使います。直接値を書き換えるだけだと反映されません。 load() を叩いても更新されません。

setInterval(() => {
  chart.axis.min({x: timeTail()});
  chart.axis.max({x: timeNow()});
  ...
}, 1000)

サンプルでは毎秒新しいデータをプロットしていますが、歯抜けになっても大丈夫です。
好きなタイミングでどの秒にデータを入れても正しくプロットされます。

  chartData.columns[0].push(timeNow());
  chartData.columns[1].push(Math.random());
  chart.load({columns: chartData.columns});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

what is the best java website

토토 정보와 토토사이트, 안전놀이터, 먹튀 검증을 제공합니다. 토사랑 사이트 추천은 믿을 수 있습니다. 여러분이 안전히 사설 토토 이용을 할 수 있도록 하며, 양질의 메이저놀이터 분석으로 최고의 토토 커뮤니티 사이트를 지향합니다. 스포츠토토 정보공유 방문을 환영합니다.https://www.tolove24.com

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

[JavaScript]書き方と基礎文法に関して(変数、四則演算、条件分岐、繰り返し処理)

今回は、JavaScriptの基礎文法について書いていきます!!
JavaScriptの文法はRubyと似ていて少し頭の中がごちゃごちゃします(笑)

なので、記憶が新しい内に整理していきます。

Javascriptの書き方(2通り)

scriptタグを使います。

その後からは、下記の2種類の方法で出来ます。

①htmlファイル内に書いていく。

①.html
<!DOCTYPE html>
<html lang="ja">
  <head>  <meta charset="UTF-8">
  </head>
  <body>
    <script>..........</script> //scriptタグ内に書いていく。
  </body>
</html>

②〇〇.jsファイルに書いていく。 
 *.js = javascript拡張子ファイルのことです。イメージとしては、cssファイルと同様です。

②.html
<!DOCTYPE html>
<html lang="ja">
  <head>  <meta charset="UTF-8">
  </head>
  <body>
    <script src="app.js"></script> //src=を用いて、jsファイルと繋げる。
  </body>
</html>

②の方が、ファイルないがごちゃごちゃせず、別ファイルにてJavaScriptのみ見れるので、
②で書いていくことが望ましいです。
(むしろ①で書くことなんてあるのかな、、、、。)

なので、これからの基礎文法は、.jsファイル内に書いていく事を想定して説明していきます。

変数

今回は、 hello を変数とした場合で書きます。

Rubyでいうと、下記の書き方でした。

Ruby.rb
   hello = "Hello world"

JavaScriptに関しては、少し異なります。

JavaScript.js
   var hello = "Hello World";      // var+変数 = 値(文字もしくは数字)

相違点を下記の表でまとめました。

相違点 説明
var variable 英語で変数という意味。その略。
; 文の末には、;が必須。忘がち。

四則演算

これは、Rubyとほぼ同じですね。

JavaScript.js
   alert(2+4);   // 結果は、6
   alert(10-2);  // 結果は、8
   alert(3*4);   // 結果は、12
   alert(10/5);  // 結果は、2

ちなみに、 alert は、ブラウザの上にアラートを表示させるものです。
おそらくパソコンを使っている皆さんなら一度は見たことがあ流と思います。多分、、、、。
次の条件分岐の項目で、アラートの画像が出てきます。(結果の部分)

条件分岐

if構文になります。
こちらもRubyと似ていますが、少しだけ違ったりしてややこしいです。
例を下記に記します。

JavaScript.js
   var Tom = 20;
   var John = 18;

   if( Tom > John){                 // ()の中には条件を記載
    alert('Tomは、Johnより年上です。');  // どう処理するかを記載
   } else if( Tom == John) {        //  ()の中には条件を記載 
    alert('Tomは、Johnと同い年です。');  // どう処理するかを記載  
   } else {
    alert('Tomは、Johnより年下です。');
   }

結果
スクリーンショット 2021-01-20 17.59.48.png
とhtmlに表示される。

繰り返し処理

こちらは、 Whilefor を使います。

・While構文

JavaScript.js
   var max= 100;
   var num= 1;
   var count= 0;

   while(num < max){
     num = num * 2;
     count = count + 1;
   }
   alert('2を掛け続けて'+ max + 'を超えるのに必要だった回数は' + count + '回です');

結果
スクリーンショット 2021-01-20 18.40.38.png

・For構文

JavaScript.js
   var i;  
   var num = 0;

   for(i = 1; i < 101; i++){       //(初期値; 条件式; 増減値)で記載する。
     num = num + i;                //繰り返す度に+1される。
   }
   alert('1から100まで足し算した結果は' + num + 'です。');   
                                  //条件式が当てはまらなくなったらalertが適応される。

結果
スクリーンショット 2021-01-20 18.26.13.png

他にもありますが、一旦はこちらにて終わります。
以上、たにーでした。

よかったら、LGTMをよろしくお願いします!
守られるとモチベーションになります:joy:

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

[個人開発]使い捨てチャットアプリ作ってみた

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
windows10
heroku
postgresql
ActionCable

gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem 'bcrypt'
gem 'activeadmin'
gem 'devise'
gem 'rack-attack'

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めるんです。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

使い捨てのチャットを作成できるサービス「Scorch Chat」をリリースした!

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
heroku
postgresql
ActionCable
slim

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めます。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

Railsはオワコンか?

ほかの記事で話題になっていたので取り上げます。

僕はrailsはオワコンではないと思います。
結構昔から言われ続けてますが、まだオワコンにはなってないでしょう?
それが答えな気がします。

railsを圧倒するようなフレームワークが出ない限りオワコンにはならない気がします。

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

手軽にチャットを作成できるサービスをリリースした!

初めに

今回はweb上で動くチャットアプリ 「スコーチチャット」を作りました。
アカウント登録不要
ダウンロード不要
で使い捨てで使われることを想定してます。

chat-top.png
パスワードも設定可能です

URL
https://scorch-chat.herokuapp.com/

開発環境

rails6
ruby2.7
heroku
postgresql
ActionCable
slim

いつもと同じです。

開発期間

5日くらいです。
webアプリは当たる確率が6%程で質より量を打ったほうがいいといわれたのでなるべく早く作りました。

チャット画面

chat-smart.png

[自分流]新しい技術の勉強法

結論から言うと、元あるコードを改造することです。

初めて使う技術のコードを一気に書くのは難しいです。
なので作りたいアプリになるべく似たコードを
githubなどで落としてきてそれで開発を進めます。
するとインプットとアウトプットを実践でできるので
いい勉強法になると思ってます。

今回はじめてActionCableを使ったのですが最初は全く分かりませんでしたがもう
何となくわかるようになりました

Railsはオワコンか?

ほかの記事で話題になっていたので取り上げます。

僕はrailsはオワコンではないと思います。
結構昔から言われ続けてますが、まだオワコンにはなってないでしょう?
それが答えな気がします。

railsを圧倒するようなフレームワークが出ない限りオワコンにはならない気がします。

終わりに

ここまで読んでくれてありがとうございました。

スコーチチャットをぜひ使ってみてください!
URL
https://scorch-chat.herokuapp.com/

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

【Vue.js】コンポーネントを動的に切り替える時とその注意点【動的コンポーネント】

前提

コンポーネントを複数用意した際の切り替え方。
その際注意すること。

  • Houseコンポーネント
  • Contentコンポーネント
  • 親コンポーネント : App.vue

今回は二つ子コンポーネントを用意。
ボタンを押すことで二つのコンポーネントを切り替えることができる。

Houseコンポーネント

House.vue
<template>
  <div>
    <p>House</p>
  </div>
</template>

Contentコンポーネント

Content.vue
<template>
  <div>
    <p>Content</p>
    <input type="text">
  </div>
</template>

<script>
export default {
  destroyed() {
    console.log('destoryed');
  }
}
</script>

コンポーネント

App.vue
<template>
  <div>
    <button @click="currentComponent = 'Content'">Content</button>
    <button @click="currentComponent = 'House'">House</button>
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'House',
    };
  },
}
<script>

コンポーネントの切り替え

keep-alive

  • keep-aliveタグ
  • componentタグ
  • v-bind:is(:is)

keep-aliveのなかにcomponentネスト
componentの中でv-bind:isを使ってプロパティを指定すると完成。

App.vueではcurrentComponentプロパティにコンポーネントを値として挿入しているが、
直接指定も可能。

App.vue
//直接指定

<component :is="House"></component>
<component :is="Content"></component>

ライフサイクルフック

ライフサイクル

  • activated
  • deactivated

keep-aliveを使用することによって新たなライフサイクルが二つ追加される。

  • created
  • destroyed

この二つをイメージするとわかりやすい。


注意点

異なる点として、毎回インスタンス化されないため、実際にcreatedやdestroyedされているわけではない。

そのため、inputなどに入力したデータは切り替え後も消えずに残る

参照

udemy講座 : https://www.udemy.com/course/vue-js-complete-guide/learn/lecture/15371400#content
講師 : よしぴー(Yoshipi)さん

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

v-calender でCSSが効かない件

環境

 エディタ:VS-CODE
Vue:@vue/cli 4.5.9 vue": "2.6.11",
"v-calendar": "2.1.6",
"bootstrap": "4.5.3",

初めての記事なので、わかりにくい場合は申し訳ありません。

カレンダーをVueで利用しようと思い、v-calenderを選択しました。
サンプルとして、GitHubの
https://github.com/nathanreyes/v-calendar/blob/master/docs/.vuepress/components/homepage/custom-calendar.vue
からコードをコピペしてVS-CODEに張り付けたのですが、CSSが適用されず困っていました。
調べたところissueにも回答が見つけられず、自力で調べてみました。
参考issue : https://github.com/nathanreyes/v-calendar/issues/727
まだ、Gitのサンプルと違う?というところはありますが、備忘録的に記載しておきます。

1.npm or yarn経由でTailwindをインストールする

https://tailwindcss-ja.entap.app/docs/installation

   # Using npm
   npm install tailwindcss
   または、
   # Using Yarn
  yarn add tailwindcss 

2.main.js にてCSSをインポートする。

  CSSの該当ファイルが見つからず苦労しました・・・

main.js

import "tailwindcss/dist/tailwind.css";
//VCalender の設定
import VCalendar from 'v-calendar';
Vue.use(VCalendar);

image.png

より良い方法があればご指導ください。

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

【Next.js】CSR,SSG,SSR,ISRがあやふやな人へざっくり解説する

前書き

仕事でNext.jsを書いているのですがSSG SSR ISRらへんの知識があやふやだったので噛み砕いて解説してみました。間違っているところなどあれば、ご指摘していただけるとありがたいです?‍♂️

以下、本題です。

それぞれの基本的な解説

CSR(クライアントサイドレンダリング)

クライアントサイド レンダリング(CSR)は JavaScriptを使用し、直接ブラウザでページをレンダリングすることを意味します。すべてのロジック、データフェッチ、テンプレーティングやルーティングは、サーバーではなくクライアント上で扱われます。

引用元

つまりサーバーではなく、(JavaScriptによって)ブラウザ側でレンダリングする方法です。しかしCSR(クライアントサイドレンダリング)は大きいアプリケーションの場合、クライアントで処理するJavascriptの量も増えますよね。これに伴い、ユーザーにページを表示させるのが遅くなってしまいます。(ユーザーのデバイススペックに依存してしまう)

上記の問題を解決するために、SSR(サーバーサイドレンダリング)が出てきます。

SSR(サーバーサイドレンダリング)

CSR(クライアントサイドレンダリング)のようにロジックやデータフェッチをブラウザで行うのではなく、サーバー側で処理(データフェッチ等を)し、HTMLを構築してクライアント側に返す方式です。この方法ではCSR(クライアントサイドレンダリング)のようにクライアント(ユーザーデバイス)のスペックに依存せず、ハイスペックなサーバーでHTMLを構築することができます。

とはいえリクエストごとにサーバーで処理し、HTMLが構築されるため、ユーザーを待たせる時間が長くなってしまいます。そこで、SSGが出てきます。

SSG(静的サイトジェネレーション)

SSRの問題(CSRもですが)として、ユーザーのリクエストを受けてからHTMLを構築するので、時間がかかってしまう、という課題がありました。これを解決してくれるのがSSG(静的サイトジェネレーション)です。

SSG(静的サイトジェネレーション)はビルド時にHTMLを構築しておきます。この時、外部APIからのデータフェッチも行います。そしてユーザーからリクエストされた時に事前に構築してあるHTMLを表示します。また、アプリケーションサーバーからHTMLを返すのではなく、CDNにキャッシュしておくことでユーザーに対して高速にページを表示することができます。こうすることにより、SSRよりもユーザーに対して高速にページを表示させることができます。

しかし、またもや問題が出てきます。しかも今回は二つ。

一つ目は、ビルド時に大量のデータを取ってくることは現実的ではありません。例えば、アマゾンのように巨大なECサイトの場合、ビルド時に全てのデータを取ってきて、HTMLを構築することは難しいでしょう。

この問題はフォールバックが解決してくれます。

二つ目は、リソースの更新が頻繁な場合は、どうなるでしょうか?例えば、ツイッターみたいに複数人がコンテンツを更新するような場合(更新が激しい場合)は、このままのSSGでは情報を表示させることができません。だってビルド時しかデータフェッチしていないのだから。

この問題を解決するためにISRが使われます。

それぞれ解説していきます。

フォールバック

export async function getStaticPaths() {
  return {
    paths: [
      // 省略
    ],
    //ここ!!
    fallback: true
  }
}

getStaticPaths()関数内のfallbackが確認できるかと思います。この値がfalseの場合、存在しないページにアクセスした時に404ページに飛ばされます。trueにした場合、データフェッチしていない状態のHTMLが返され、その後ブラウザ側でデータフェッチが行われて、HTMLが再構築されます。

同時にサーバー側でも同様にデータフェッチが行われ、HTMLの構築が行われます。次回以降のリクエストでは、サーバー側から完全なHTML(データも含まれた)が返されます。

とはいえ、非完全な状態のHTML(データが含まれていない)でクライアントに送られてしまうので、この部分が欠点ですね。この点も解決できます。

blocking

export async function getStaticPaths() {
  return {
    paths: [
      // 省略
    ],
    //ここ!!
    fallback: 'blocking'
  }
}

fallbackの値にblockingを入れることで、データが取得されていないページにアクセスした時、サーバーから不完全な状態でHTMLが送られるなんてことはなく、データフェッチが行われてからHTMLが構築され、クライアント側にHTMLが送られます。

ISR(Incremental Static Regeneration)

export async function getStaticProps(hoge) {
  return {
    props: { hoge },
    revalidate: 10, // ここを追加
  }
}

getStaticProps()関数のreturn内にrevalidateの値として任意の数字を入れてください。その秒数以降にリクエストがきた時に、サーバー側でデータフェッチを再度行い、HTMLを再構築します。ここでポイントになってくるのが、リクエストしたユーザーにはキャッシュしていたHTMLを返すということです。

一定期間ごとにサーバーサイドレンダリングを行うことで、高速なページ描画を実現しています。こうすることにより、表示されるデータの更新頻度が高くても新しいデータが表示されるようになりました。

とはいえ、ISRは常に最新のものがユーザーに届けられるわけではありません。(最初のリクエスト時にはキャッシュされたHTMLが返されるので。)使い分けとしては下記のようなイメージで良いと思います。

  • リクエスト時に最新の情報でなくても良い:ISR
  • リクエスト時に最新の情報が出てほしい:SSRSWR

以上です?‍♂️

参考記事

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

switch / if を式として

はじめに

無職 やめ太郎(本名)氏の記事
4歳娘「パパ、constしか使わないで?」
のコメント欄にて、switchif を式として構成する話が盛り上がっているようだった。
こういう話好き。

ただ、記事の主旨はタイトル通り「変数を const のみにするには」と言うのが主眼なようなので、コメントではなく記事にしてみることにした。

汎用的な条件分岐 (conditional branch) は Maybe と Either で構成できるよ、というお話。
若干関数型チックな雰囲気を感じるかもしれないけれど、モナモナ言い出さないので安心してほしい。

Maybe (ある / なし)

まずは Maybe について。
値が「ある」場合と「ない」場合を表現する構造。
他言語だと Optional と呼ばれることもしばしば。
ここでは単純に、要素数が 0 または 1 の配列、と言うことにしよう。

type Nothing = [];
type Just<A> = [A];
type Maybe<A> = Just<A> | Nothing;

Maybe を扱う関数は色々考えられるけれど、ここではよく使う maybe 関数を導入しよう。

const maybe: <A, B, C>(
    m: Maybe<A>,
    f: (a: A) => B,
    v: C
) => B | C
    = (m, f, v) => '0' in m ? f(m[0]) : v;

Maybe m が値を持っていればそれに関数 f を適用し、そうでなければ代わりの値 v を返す。
使い方は以下のような感じ。

usage
const greet = (maybeName: Maybe<string>) =>
  'ようこそ' +
  maybe(maybeName,
    name => name + '',
    'ゲストさん'
  );

console.log(greet(['ユーザー'])); // ようこそユーザー様
console.log(greet([])); // ようこそゲストさん

これだけでも、単純な分岐は表現できる。

Either (どちらか一方)

Either は 2 種に場合分けをして、A の場合の値と B の場合の値、どちらか一方、というものを表現する構造。

type Left<A> = { left: A };
type Right<B> = { right: B };
type Either<A, B> = Left<A> | Right<B>;

Left / Right を構築する関数と、値を取り出して関数を適用する either を定義しよう。

const Left: <A>(left: A) => Left<A>
    = left => ({ left });

const Right: <B>(right: B) => Right<B>
    = right => ({ right });

const either: <A, B, X, Y>(
    e: Either<A, B>,
    f: (a: A) => X,
    g: (b: B) => Y
) => X | Y
    = (e, f, g) => 'left' in e ? f(e.left) : g(e.right);

Either e を受け取って、Left であれば f を、Right であれば g を適用する。

使い方は、例えば以下のような感じ。

usage
const users = [ { id: 0, name: 'A' }, { id: 1, name: 'B' } ];

const getUser = (idOrName: Either<number, string>) =>
  either(idOrName,
    id   => users.find(x => x.id === id),
    name => users.find(x => x.name === name) 
  );

console.log(getUser(Left(0))); // { id: 0, name: 'A' }
console.log(getUser(Right('B'))); // { id: 1, name: 'B' }

実際には、「計算結果」または「エラーメッセージ」など、例外処理的なものを扱うことが多い。

Conditional Branch (条件分岐)

さて、上記 MaybeEither を組み合わせれば、

  • 入力値を受け取って
  • 複数の処理に分岐し
  • 出力値を返す

といった汎用的な条件分岐を構成できる。

考え方としては、

  • 未分岐のものは 入力値 (X) を持った Left<X>
  • 分岐済みのものは 出力値 (Y) をもった Right<Y>

として扱う、というもの。

Either<X, Y> が、条件分岐処理中の状態に相当する。

まず、初期状態は Left (未分岐)。

const start: <X>(input: X) => Either<X, never>
    = x => Left(x);

次に、条件分岐は X を受け取って Maybe を返す関数として表現する。
分岐した場合には Just を、分岐しなかった場合には Nothing を返す。

状態(Either)と条件分岐(Maybeを返す関数)から、次の状態を作る。

const branch: <X, Y, V>(
    e: Either<X, Y>,
    f: (x: X) => Maybe<V>
) => Either<X, Y | V>
    = (e, f) => either(e,
        x => maybe(f(x), y => Right(y), Left(x)),
        y => Right(y)
    );

入力となる e そして関数 f の戻り値によって 3 パターンの結果を生む。

e f(x) return
Left(x) Just(y) Right(y)
Left(x) Nothing Left(x)
Right(y) N/A Right(y)

最後に、条件分岐処理の終端は、X を受け取って値を返す関数として表現する。

状態(Either)と終端(値を返す関数)から、最終出力を得る。

const otherwise: <X, Y, V>(
    e: Either<X, Y>,
    f: (x: X) => V
) => Y | V
    = (e, f) => either(e, x => f(x), y => y);

EitherRight (既に結果がある) ならばその値、
Left (まだ結果が無い) ならば関数を適用した結果が戻り値となる。

この三つの関数を使って例として FizzBuzz を書いてみると以下のような感じ。

const fizzbuzzTest1 = (n: number) => {
    const e0 = start(n);
    const e1 = branch(e0, n => n % 15 ? [] : ['FizzBuzz']);
    const e2 = branch(e1, n => n % 3 ? [] : ['Fizz']);
    const e3 = branch(e2, n => n % 5 ? [] : ['Buzz']);
    return otherwise(e3, n => n);
};

Either を状態としたステートマシンだと考えても良いかもしれない。

こんな感じで汎用的な条件分岐が構成できる。

......といっても、TypeScript/JavaScript だと中間変数が必要になって明らかに書きにくいので、メソッドチェーンで書けるようにしよう。

Match クラス

クラスとファクトリを定義する。
名前はとりあえず Match / match とした。
ついでに otherwise は長いので else にしてしまおう。

class Match<X, Y> {
    private constructor(private e: Either<X, Y>) { }

    static from(): Match<void, never>;
    static from<X>(x: X): Match<X, never>;
    static from<X>(x?: X) {
        return new Match(start(x));
    }

    branch<V>(f: (x: X) => Maybe<V>) {
        return new Match(branch(this.e, f));
    }

    else<V>(f: (x: X) => V) {
        return otherwise(this.e, f);
    }
}

const match = Match.from;

次のように書ける。

const fizzbuzzTest2 = (n: number) =>
    match(n)
        .branch(n => n % 15 ? [] : ['FizzBuzz'])
        .branch(n => n % 3 ? [] : ['Fizz'])
        .branch(n => n % 5 ? [] : ['Buzz'])
        .else(n => n);

おおよそ形にはなったけれど、三項演算子がまだ不格好なので、
barnch を「条件」と「結果の生成」に分離しよう。
メソッド名は if としてみる。

class Match<X, Y> {
    /* 略 */

    // 追加
    if<V>(p: (x: X) => unknown, f: (x: X) => V) {
        return this.branch(x => p(x) ? [f(x)] : []);
    }
}

これでより「らしく」なる。

const fizzbuzz = (n: number) =>
    match(n)
        .if(n => n % 15 === 0, _ => 'FizzBuzz')
        .if(n => n % 3 === 0, _ => 'Fizz')
        .if(n => n % 5 === 0, _ => 'Buzz')
        .else(n => n);

Switch のようなもの

条件式に、関数ではなく入力値と比較する値を渡すものを定義しよう。
名前は case にする。

class Match<X, Y> {
    /* 略 */

    // 追加
    case<V>(p: X, f: (x: X) => V) {
        return this.branch(this.e, x => x === p ? [f(x)] : []);
    }
}

これで switch に似た扱いができるようになる。

const switchLike = (n: number) =>
    match(n)
        .case(0, _ => 'Zero')
        .case(1, _ => 'One')
        .else(_ => 'More'); // default

If のようなもの

入力値を無視すれば普通の if - else if - else と同じような書き方もできる。

const ifLike = (value: number) =>
    match()
        .if(_ => value > 6, _ => 'High')
        .if(_ => value > 3, _ => 'Middle')
        .else(_ => 'Low');

もっとこんなユーティリティもあるよ!とか、カリー化しようぜ!とか、まあ、使う人によって色々ありそうだけれど、そこら辺はお好みでどうぞ。

長いおまけ (改良版)

さて、本筋としてはこれで終わりなのだけれど、上記実装はやや物足りない。

まず、素直に Maybe / Either を使って構成しているので、中間オブジェクトを色々と生成してしまっている。
コスト的に気に掛けるほどのものではないが、不要なオーバーヘッドは可能ならば省きたい所。

二点目、TypeScript 限定の話として、型ガードが効かない。

例えば以下のようなケース。

const isString = (x: unknown): x is string => typeof x === 'string';

const strOrNum = (x: string | number) =>
  match(x)
    .if(isString, s => 'String:' + s)
    .else(n => 'Number:' + n);

if 内の sstringelse 内の n は number
と型推論してほしいところだが、残念ながら string | number のままである。

列挙型の漏れチェックなんかもできないので、どげんかせんといかん。

というわけで、2 点考慮して、ついでにユーティリティなども追加して書き直したのが以下。

やっていることは、

  • Left / Right それぞれをクラス化, Maybe 周りはメソッド内で処理
  • 型ガードが効くようにメソッドオーバーロードで受け、入力値の型から処理済みの型を除外
  • 型だけ変わる部分で自分自身を返し、不要なオブジェクト生成を回避
  • 他ユーティリティの追加

といった具合。

match.ts
type BooleanLike = unknown;
type Throwable = unknown;

export interface Match<X, Y> {
    else<V>(f: (x: X) => V): Y | V;

    branch<V>(f: (x: X) => [] | [V]): Match<X, Y | V>;

    // same as .branch(x => p(x) ? [f(x)] : [])
    if<V, T = X>(p: (x: X | T) => x is T, f: (x: T) => V): Match<Exclude<X, T>, Y | V>;
    if<V>(p: (x: X) => BooleanLike, f: (x: X) => V): Match<X, Y | V>;

    // same as .branch(x => p(x) ? [v] : [])
    ifValue<V, T = X>(p: (x: X | T) => x is T, v: V): Match<Exclude<X, T>, Y | V>;
    ifValue<V>(p: (x: X) => BooleanLike, v: V): Match<X, Y | V>;

    // same as .branch(x => x === t ? [f(x)] : [])
    case<V, T extends X = X>(t: T, f: (x: T) => V): Match<Exclude<X, T>, Y | V>;
    case<V>(t: X, f: (x: X) => V): Match<X, Y | V>;

    // same as .branch(x => x === t ? [v] : [])
    caseValue<V, T extends X = X>(t: T, v: V): Match<Exclude<X, T>, Y | V>;
    caseValue<V>(t: X, v: V): Match<X, Y | V>;

    // same as .else(x => v)
    elseValue<V>(v: V): Y | V;

    // same as .else(x => { throw newError(x) })
    throw(newError?: (x: X) => Throwable): Y;

    // same as .else(x => { throw t })
    throwValue(t: Throwable): Y;

    // covering check
    unreachable(this: Match<never, Y>): Y;
}

export function match(): Match<void, never>;
export function match<X>(x: X): Match<X, never>;
export function match<X>(x?: X): Match<X | void, never> {
    return new MatchLeft(x);
}

const left: <X, Y, T>(m: MatchLeft<X, Y>) => Match<Exclude<X, T>, Y>
    // = m => new MatchLeft(m._ as Exclude<X, T>);
    // @ts-ignore ** reinterpret cast to reduce runtime cost **
    = m => m;

const right: <X, Y>(y: Y) => Match<X, Y>
    = y => new MatchRight(y);

class MatchLeft<X, Y> implements Match<X, Y> {
    readonly _: X;

    constructor(target: X) { this._ = target; }

    // terminates

    else<V>(f: (x: X) => V) {
        return f(this._);
    }

    elseValue<V>(v: V) {
        return v;
    }

    throw(f?: (x: X) => Throwable): Y {
        throw f ? f(this._) : new Error('match error: ' + this._);
    }

    throwValue(t: Throwable): Y {
        throw t;
    }

    unreachable(): never {
        throw new Error('match error: ' + this._);
    }

    // non-terminates

    branch<V>(f: (x: X) => [] | [V]): Match<X, Y | V> {
        const r = f(this._);
        return '0' in r ? new MatchRight(r[0]) : this;
    }

    if<V, T>(p: (x: X | T) => x is T, f: (x: T) => V): Match<Exclude<X, T>, Y | V> {
        return p(this._) ? right(f(this._)) : left(this);
    }

    ifValue<V, T>(p: (x: X | T) => x is T, v: V): Match<Exclude<X, T>, Y | V> {
        return p(this._) ? right(v) : left(this);
    }

    case<V, T extends X>(t: T, f: (x: T) => V): Match<Exclude<X, T>, Y | V> {
        return this._ === t ? right(f(t)) : left(this);
    }

    caseValue<V, T extends X>(t: T, v: V): Match<Exclude<X, T>, Y | V> {
        return this._ === t ? right(v) : left(this);
    }
}

class MatchRight<X, Y> implements Match<X, Y> {
    readonly _: Y;

    constructor(result: Y) { this._ = result; }

    // terminates
    else() { return this._; }
    elseValue() { return this._; }
    throw() { return this._; }
    throwValue() { return this._; }
    unreachable() { return this._; }

    // non-terminates
    branch() { return this; }
    if() { return this; }
    ifValue() { return this; }
    case() { return this; }
    caseValue() { return this; }
}

先ほどの例もちゃんと推論できるようになる(なんでもできるわけではないが)。

const isString = (x: unknown): x is string => typeof x === 'string';

// OK
const strOrNum = (x: string | number) =>
  match(x)
    // Match<string | number, never>
    .if(isString, s => 'String:' + s)
    // Match<number, string>
    .else(n => 'Number:' + n);

// NG これはタイプガードであると見抜いてくれない
const strOrNum2 = (x: string | number) =>
  match(x)
    .if(x => typeof x === 'string', s => 'String:' + s)
    .else(n => 'Number:' + n);

// 一応明示すれば OK (長いが)
const strOrNum3 = (x: string | number) =>
  match(x)
    .if((x: any): x is string => typeof x === 'string',
         s => 'String:' + s)
    .else(n => 'Number:' + n);

いろいろ組み合わせた場合の挙動。

const isNumber = (x: unknown): x is number => typeof x === 'number';
const isString = (x: unknown): x is string => typeof x === 'string';

const getTime = (dateLike: number | string | Date): number =>
    match(dateLike)
        // Match<number | string | Date, never>
        .case('now', _ => Date.now())
        // Match<number | string | Date, number>
        .if(isString, Date.parse)
        // Match<number | Date, number>
        .if(isNumber, x => x)
        // Match<Date, number>
        .else(date => date.getTime());

漏れチェックもできるように。
unreachable メソッドは入力型が never でない時、コンパイルエラーとなる。

enum MyEnum {
    Hoge,
    Foo,
    Bar
}

const test1 = (x: MyEnum): string =>
    match(x)
        // Match<MyEnum, never>
        .case(MyEnum.Hoge, _ => 'Hoge')
        // Match<MyEnum.Foo | MyEnum.Bar, string>
        .case(MyEnum.Foo, _ => 'Foo')
        // Match<MyEnum.Bar, string>
        .case(MyEnum.Bar, _ => 'Bar')
        // Match<never, string>
        .unreachable(); // ok

const test2 = (x: MyEnum): string =>
    match(x)
        // Match<MyEnum, never>
        .case(MyEnum.Hoge, _ => 'Hoge')
        // Match<MyEnum.Foo | MyEnum.Bar, string>
        .case(MyEnum.Foo, _ => 'Foo')
        // Match<MyEnum.Bar, string>
        .unreachable();
        // tsc error
        // 型 'Match<MyEnum.Bar, string>' の 'this' コンテキストを
        // 型 'Match<never, string>' のメソッドの 'this' に割り当てることはできません。
        // 型 'MyEnum' を型 'never' に割り当てることはできません。

まあ、このくらいできればある程度は使い物になろう。

おわりに

この手のもの、書いても結局パフォーマンス気にしてグルーコードくらいでしか使わない人なので、やっぱり言語サポートが欲しい。(ちゃぶ台返し)

それはそれとして、この手の話はやっぱり楽しい。

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

notistack で複数のポップアップを同時に表示する

はじめに

notistackMaterial UISnackbar コンポーネントをラップしたライブラリでうす。
Screen Shot 2021-01-20 at 13.39.18.png
Screen Shot 2021-01-20 at 13.38.40.png
Screen Shot 2021-01-20 at 13.38.55.png
添付した画像のように、いくつかのカラーパターンを出し分けることができます。
また、notistack の最大の特徴は、ポップアップを積み重ねて表示できることです。
Screen Shot 2021-01-20 at 13.43.20.png

スタイルをカスタマイズする

notistack@1.0.0 でいくつかの breaking change がありましたが、私の環境は notistack@0.9.17 を利用しています。

notistack からプロバイダーと Custom Hooks をインポートします。

import { SnackbarProvider, useSnackbar } from 'notistack';

... 

SnackbarProvider は下記のように使います。

<SnackbarProvider
  iconVariant={{
    success: <SuccessIcon />
    error: <ErrorIcon />
  }}
  anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
  ContentProps={{
    style: { minWidth: 0, height: 33, alignContent: 'center' }}
    className: useCustomStyle().variantDefault,
  }}
  classes={useStayle()}
>
  {children}
</SnackbarProvider>

スタイルは @material-ui/coremakeStyles を用います。

import { makeStyles } from '@material-ui/core';

...

const useCustomStyles = makeStyles((theme) => ({
  variantDefault: { backgroundColor: 'black' },
}));

const useStyles = makeStyles((theme) => ({
  variantSuccess: { backgroundColor: 'green' },
  variantError: { backgroundColor: 'red' },
  containerAnchorOriginTopCenter: {
    top: 12,
  },
  collapseWrapper: {
    marginTop: 4,
    marginBottom: 4,
  },
}));

今回の例では

  • variantSuccess, variantError で variant の値によってスタイルを変えています
  • containerAnchorOriginTopCenter で上からの余白を設定しています
  • collapseWrapper で、個々の Snackbar に余白を設定しています

注意すべき点として、classes には variantDefault が存在しません。
参考
そのため、className として設定してあげる必要があります。
このようにして、Snackbar を自分が使いやすいようにカスタマイズできました。

※ 1.0.0 からは ContentProps がなくなっているので設定方法が異なります。

ポップアップを表示する

Snackbar を表示させる際は、Custom Hooks を利用します。

const { enqueueSnackbar, closeSnackbar } = useSnackbar();

...

const key = enqueueSnackbar(message, options);
closeSnackbar(key);

enqueSnackbar で取得した key を渡すことで対象のポップアップを消すことができます。
表示の際には個別にオプションを渡すことも可能です。
これにより、個々のポップアップに違いを出すことができます。

おわりに

ユーザの目を引くポップアップを出すシーンは多いと思います。
例えばエラーですが、複数のエラーが出た場合にはすべてを一度に見せてあげたほうがユーザフレンドリーかもしれません。

Material UI の Snackbar をきれいに整列して表示することができる notisnack
もし良ければ使ってみてください。

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

さて、驚くほど簡単にreCapthca認証でBOT、スパム対策を行おう!!

ログインフォームや会員登録を行うフォームにて、色々対策したいなと思い調べていると、Googleが提供しているreCaptchaというものを使うことで対策が出来るらしい。

誰しもが1回は見たことのある、「私はロボットではありません」てきなやつ。

ただ、画像をクリックしたりあの分かりづらい文字列を入力したり色々と面倒。。。

そこで、GoogleはこれらをやらなくてもユーザーがBotやスパムではないと判断する最新「v3」を今提供している。

イメージとしてはこんな感じ

無題.png

ページの右下にこのようなマークが表示され常にユーザーが人間らしい動きをしているかを確認している。

流石Googleさん!

今回はこのreCaptchaの簡単な使い方を説明していこうと思います!!

調べてみてわかったことが、割とドキュメントが少なく初めての人にとって分かりやすい記事がまりなあったので、書いてみようと思いました。

初めての人に向けて、書いていくのであまり詳しい説明は致しません。(詳しいことは分からない)

なので、reCaptchaを搭載したいという方はぜひ見て下さい!

それでは、一緒に説明を見ていきましょう!!!

Google reCaptchaの登録

まずは、「Google reCaptcha」に登録します。

詳しいやり方は、他の方が説明しているのでそちらを参考にして頂くと良いかと。

ちなみに僕は下記の記事を参考にして登録を行いました。

Google reCAPTCHAのWEBサイト登録とAPIキーの取得方法

jsファイルの読み込み

次に、サイトキーをコピーして、headタグに以下のスクリプトファイルの読み込みを行ってください。

※注意 bodyタグではなくheadタグです!!!間違えないように!

<script src="https://www.google.com/recaptcha/api.js?render={SITE_KEY}" async defer></script>

{SITE_KEY}にコピーしたサイトキーをペーストしてください。

トークンの呼び出し

最後に、認証を行いたいページにてTokenの発行を行います。

トークンの有効時間は2分であるため、入力フォームで多くの入力事項を記載する際は、ページに訪れた時ではなく、例えば「次へ」のボタンを押したときなどにトークンを発行してください。

トークンを呼び出す関数は以下のようになります。

index.js
     window.grecaptcha.ready(() => {
        window.grecaptcha.execute(process.env.VUE_APP_SITE_KEY, { action: '/signup' }).then(async (token) => {
          console.log('token')
        })
      })

このようにして、Tokenを発行することができます!

ロゴの消去(番外編)

ページ右下の認証ロゴって結構邪魔ですよね。

なので、ロゴを消去したいページでは以下のようなCSSを設定してあげてください。

.grecaptcha-badge {
  display: none
}

また、ページによってロゴを消したり表示する場合はこちらの記事にVueRouterで動的にpathを取得(検知)する方法詳しく書いてあるので、ぜひ興味のある方はご覧ください。

今回はこの辺で終わりにしたいと思います。

reCaptchaはまだこれで終わりではないです。

ここで発行したトークンが本当に正しいのかの判断を行わなければいけません。

そして、例えばトークンが正しかったらログインを実行し、正しくなければエラーメッセージを表示するといった形になります。

ただ、これを一気にやってしまうと頭がパンクしてしまうので、まずはこのトークンがちゃんと発行できているかを確認してから次の段階に進んでください。

近々、Laravelでトークンの認証を行う記事を書くのでぜひそちらもご覧ください。

以上、「さて、驚くほど簡単にreCapthca認証でBOT、スパム対策を行おう!!」でした!

良かったら、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

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

AR.jsのLocation Basedで遠くにモデルを表示させようとして困った話。

1.ARで遠くのランドマークをアイコンで表示したい

ARで遠くのランドマークをスマホに表示させたいとの希望がありました。
アクティブなユーザー数は、月100以下を想定していたので、有料のライブラリは無理だなぁってことで、「AR.js」で作成することになりました。

AR.jsは、Marker Tracking、Image Tracking、Location Basedに対応している優れもので、htmlに数行のコードでよしなにARを実装してくれます。

Location Based ARは、GPSの位置情報に基づいて指定の位置にコンテンツを表示させる仕組みです。今回はAR.jsのLocation Based とA-Frameバージョンを使用することにしました。

希望としては、こんな感じです。
実際には、ピンの代わりにランドマークのアイコン等を表示させます。

bg_pattern2_pin.png

2.サンプルコードで検証

AR.jsのドキュメントに記載されているサンプルを少し変えて試します。
サンプルでは、文字を表示しますが、今回は赤いBOXを表示させてみます。
場所は北海道、札幌の時計台にしました。

コードは以下です。
非常に簡単ですね。
<a-scene>の中で<a-box>でBOXを表示させます。
今回は、コードの詳細解説は本筋でないのでいたしません?。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>GeoAR.js demo</title>
    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script>
    <script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script>
  </head>

  <body style="margin: 0; overflow: hidden;">
    <a-scene
      vr-mode-ui="enabled: false"
      embedded
      arjs="sourceType: webcam; debugUIEnabled: false;"
    >
      <a-box
        material="color: red"
        gps-entity-place="latitude: 43.062533; longitude: 141.353638;"
        scale="50 50 50"
      ></a-box>

      <a-camera gps-camera rotation-reader></a-camera>
    </a-scene>
  </body>
</html>

あれ?全然表示されないやん?

なぜかBOXは表示されません。
小さすぎるのかな?と思いscaleを大きくしても変わりません。
色々と試行錯誤するも改善されません。

自分のiPhoneが壊れてるのか?などど時間を費やし・・・・。
・・・・・・
本家のissuesにありました。

おおよそ1km以上遠方のオブジェクトは表示されない。

えぇ? マジですか?

洒落にならないんですけど。

3.AR.jsのLocation Basedは、1km以上離れたオブジェクトは表示されない!

困りました。
そしてドキュメントをよく読むと、こんな記述が。
引用元:AR.js Documentation
Viewing every distant object
If your location-based AR content is distant from the user (around 1km or more), it is recommended to use the new arjs-webcam-texture component (introduced in AR.js 3.2.0), which uses a THREE.js texture to stream the camera feed and allows distant content to be viewed. This component is automatically injected if the videoTexture parameter of the arjs system is set to true and the sourceType is webcam. For example (code snippet only):

おい、おい、おいおい、解決策があるじゃぁないか!
良かったぁ。

これを読むと
  <a-scene
      vr-mode-ui="enabled: false"
      embedded
      arjs="sourceType: webcam; videoTexture: true; debugUIEnabled: false;"
    >

videoTexture: true;を設定すれば大丈夫だそうです。

?設定して、同じコードで試します。

4.確かにオブジェクトは表示されたけど、iPhoneだと画面がフリーズ?。

オブジェクトは、確かに表示されましたけど、カメラが起動した時の景色でフリーズします。
ここでのフリーズとは画面のビューが、どちらにカメラを向けての最初の画面から変わらない状態を指します。

これはARコンテンツをビデオ画面にオーバーレイで表示させてるような感じかと。
ログを見るとiOS14だとビデオのオートプレイはユーザーの明示的な許可が必要なため、パーミッションではじかれて、画面が最初の景色で固まってしまうのでは?と推測。

もうこれじゃぁ、ARでも何でもないよね。

詰みましたよね。これ。

さてどうしよう。

5.邪道だけれど、全てのオブジェクトを1km以内に表示させればいいのでは。

この方向性でいくしかないと決意。
map_open2.png

次回は、指定地点の方角と距離を求めて、1km以内に再配置させようと思います。
③の自分の位置から、①の実際位置との方角を求めて、表示可能な位置②に再配置させる計画です。

ひょっとしたら他に良い解決策があるのかもしれないけど。
次回は、位置を再計算させて調整するプランを書いていきます。

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

[TypeScript]mapの書き方

// 以下2つの書き方は同じ結果を得られる

const list:Hogehoge[] = array.map(item => function(item));

const list:Hogehoge[] = array.map(function);

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

初めて使うReact入門

はじめに

こんにちわ。
この記事は、開発経験は手続き型を少々なプロダクトオーナーがりあクト! TypeScriptで始めるつらくないReact開発 第3版を読んで、四苦八苦しながらReactを使えるようになろうと苦戦しながら勉強したものを備忘録したものです。
1回では終わらないので何回か続くと思ので、お付き合いください。

因みに、この記事では端折っていますが、本の中では何故そのような技術を使っているのかという背景を踏まえて語られており、非常に理解しやすい内容です。ただ、動かすだけとは段違いです。
少しでもこの記事を見て面白そうだと思った方は、是非とも一度読んでみることをお勧めします。

普段は、TESTRUCTUREというテスト設計を支援するツールを作っていたりします。

実施環境

  • Windows 10 pro
  • core i7-8850H
  • Visual Studio Code
  • Ubuntu 20.04 LTS

環境準備

まずは、環境を準備します。

Node.jsのインストール

nodenvのインストール

本ではMacなのでこちらも合わせて参考にインストール。

まずは、anyenvをインストールします。

bash
git clone https://github.com/riywo/anyenv ~/.anyenv
echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
anyenv install --init
exec $SHELL -l

anyenvのインストールが完了したら、nodenvをインストールします。

bash
anyenv install nodenv
exec $SHELL -l

続いて、プラグインをインストールします。

bash
mkdir -p $(anyenv root)/plugins
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
mkdir -p "$(nodenv root)"/plugins
git clone https://github.com/nodenv/nodenv-default-packages.git "$(nodenv root)/plugins/nodenv-default-packages"
touch $(nodenv root)/default-packages
/.anyenv/envs/nodenv/plugins/nodenv-default-packages

ここにあるdefault-packagesのファイルの中身を以下にする。

yarn
typescript
ts-node
typesync

Node.jsのインストール

これでようやく準備が整ったので、Node.jsをインストールします。

bash
nodenv install -l
nodenv install 14.4.0
nodenv global 14.4.0

以上で、インストールが完了です。

Reactを動かしてみる

まず、お試しで、Hallo Worldしてみる。

bash
npx create-react-app hello-world --tempate typescript

インストールが完了したら、次のコマンドで動作を見る。

bash
cd hello-world
yarn start

http://localhost:3000 にブラウザでアクセスすると動いてるのが確認できます。
動いているのが見れると少し感動。
「Edit src/App.js and save to reload.」と書いてある通り、
App.jsを編集すると編集した内容を見ることができます。

Tutorial: Intro to Reactをやってみる

Hollo worldにある「Learn React」を押すとチュートリアルに案内されるので折角なのでやってみる。
Tutorial: Intro to React

最終的には、以下のファイルを書き換えて保存して、更新してみると、〇×ゲームが完成しています。

src/index.css
src/index.js

ここまでは、書いてある通りにやっていくので、途中は端折ります。

追加課題

そして、追加課題があるのでいくつかやってみようと思います。
追加課題を仕様変更だととらえると、各課題という名の仕様変更は、Reactのコンポーネントベースな考え方が、機能単位で分割されていて、それぞれが独立性が高く、ソースコードをいじりやすい気がしました。

各移動の場所を移動履歴リストに(col, row)形式で表示します。

どこのボタンを押したかを判別するために押した場所を記憶するstateを追加します。
また、移動履歴リストは、gameクラスのrenderに書かれているので、次ように書き換えます。
正方形は次のように配置されているので、

0 1 2
3 4 5
6 7 8

col:3で割った余りを記載します。
row:3で割った商を記載します。

index.js
class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
        }
      ],
      onClickHistory: [
        {
          onClickNum: 0,
        }
      ],
      stepNumber: 0,
      xIsNext: true
    };
  }

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    const onClickHistory = this.state.onClickHistory.slice(0, this.state.stepNumber + 1);
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares
        }
      ]),
      onClickHistory: onClickHistory.concat([
        {
          onClickNum: i
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }

  ...

  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
    const historyNum = this.state.onClickHistory;

    const moves = history.map((step, move) => {
      const col = historyNum[move].onClickNum % 3;
      const row = (historyNum[move].onClickNum - col) / 3;
      const desc = move ?
        'Go to move #' + move + ' (' + col + ',' + row + ')' :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

  ...
}

このように各ボタンに列と行が表示されていれば成功です。
reactadd1.png

移動リストで現在選択されている項目を太字にします。

太字のスタイルを追加します。

index.css
button.button1 {
  font-weight: bold;
}

gameクラスのrenderで返す値を、現在選択されたものと一致している場合は、太字のボタンを使用するように指定します。

index.js
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
    const historyNum = this.state.onClickHistory;

    const moves = history.map((step, move) => {
      const col = historyNum[move].onClickNum % 3;
      const row = (historyNum[move].onClickNum - col) / 3;
      const desc = move ?
        'Go to move #' + move + ' (' + col + ',' + row + ')' :
        'Go to game start';
      return move === this.state.stepNumber ?
        (
          <li key={move}>
            <button class="button1" onClick={() => this.jumpTo(move)}>{desc}</button>
          </li>
        ) : (
          <li key={move}>
            <button onClick={() => this.jumpTo(move)}>{desc}</button>
          </li>
        );
    });

太字になりました。
reactadd2.png

誰も勝てない場合は、引き分けの結果についてのメッセージを表示します。

勝利ステータスの処理のところで、勝者が決まっておらず、既に9回動いていた場合は、drawを表示してあげるようにします。

index.js
    let status;
    if (winner) {
      status = "Winner: " + winner;
    } else if (this.state.stepNumber === 9) {
      status = "draw";
    } else {
      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }

引き分けになりました。
reactadd3.png

まとめ

ここまでで、最低限のReactの環境構築と、ソースコードがかけるようになりました。
今後は、中身の要素技術や、応用的な使い方について触れたいと思います。

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

任意の文字列を出力するなるべく短いBrainf*ckの生成 [JavaScript]

作り終えて暫くするとコード見返してもわからなくなるので備忘録としてここに残すことにした
初投稿なのでかなり見にくいと思います
申し訳ないです…

先にこちら実物です https://mcbeeringi.github.io/amuse/bf.html

[21/01/20 14:31]追記
コードに不備があったので改良しました

動機

Twitterで突然リプにBrainf*ckがやって来たので
なんとかしてBrainf*ckでスマートに秒で返せるようにしたかった

設計

メモリは 255の次が0つまり8bit 書き込める数は無限 とする

アルゴリズム

当時(今も)自身が手書きする際のアルゴリズムをそのまま書き起こすことにした
(もちろん最善とは限らないです)
計算はcpuが頑張ってくれるのでいくらでも試行させられる素晴らしい

この手のコードを生成する↓

++[>++>---<<-]>+.--.

大体の流れ
 1.ASCIIで近い文字同士をグループにまとめる
 2.それぞれのグループで一番最初の文字になるべく近い数の最大公約数を求める
 3.最初のループ+端数調整→出力

変数は二つ
「近い文字同士」 ……8bitなのでその半分未満→0~127
「なるべく近い数の最大公約数」 ……探索でゴリ押す…?
この二つの数で長さが決定される

ポインタの移動によるコスト

ループの後調整をしながら出力をする段階でももちろんポインタは移動する
これに直接影響を与えるのがグループの並び順
これをなんらかの方法で最適化することで最終的な文字数の削減が見込める

インデックス並び替えとでも呼ぼうか
この処理を1と2の間に挟む

具体的には
隣接するグループインデックスの組み合わせを頻度順に書き出して
頻度が高いものから結晶が成長するようなイメージで一列に繋げて
繋げられなかったグループ番号を付け足して
既存のグループインデックスをそれで置き換える
という流れ

見るからに大変
ここで一番苦労した

なおこれでは多数派のインデックスが優先されてしまい
他の少数派の登場回数が多数派を上回っていても無視されるので
おそらく最善ではない

ひたすら書く

グループにまとめる

最初にBfに出力させたい文字列 s を用意する
このあとこの文字列を生で使う予定もないのでここでは配列で置き換える

// s='Hello World!'

s=s.split('').map(x=>x.charCodeAt(0)).filter(x=>x<256);// ASCII以外は無視
console.log('raw',s);

// >> "raw", [72,101,108,108,111,32,87,111,114,108,100,33]

 
これを適当な閾値 md(max distance) を設けて0-1=255があることに注意しながらグループ分けする
全体的に変数の名前の付け方がひどいです申し訳ない

// md=10;

var ind=[0],    // それぞれの文字がどのグループに属しているかのindex
    clm=[[s[0]]],   // グループを二次元の配列に入れる (column 直訳で列)
                    // 0グループの最初は一番最初の文字を入れておく
    tmp={}; // テンポラリ
s.slice(1).forEach((x,i)=>{i++; // 最初の文字を切ってforEachで回す
    tmp.mini=-1;    // 最終的に一番近いグループインデックスをここに書く (minimum index)
    tmp.mind=Number(md)+1;  // その距離 これ未満のときにこれとtmp.miniを更新
    clm.forEach((y,j)=>{    // 各グループに対して
        tmp.last=y[y.length-1]; // そのグループの最後の値
        tmp.dist=Math.abs(tmp.last-x);  // との距離
        tmp.dist=Math.min(tmp.dist,256-tmp.dist);   //逆回りも考慮
        if(tmp.dist<tmp.mind){  // 距離が今までより短かったら更新
            tmp.mind=tmp.dist;
            tmp.mini=j;
        }
    });
    if(tmp.mini==-1){   // 属せるグループがない場合は新しいグループを作成
        ind[i]=clm.length;
        clm[ind[i]]=[x];
    }else{          // 属せるグループがある場合はそこに入れる
        ind[i]=tmp.mini;
        clm[ind[i]].push(x);
    }
});
console.log('index',ind);
console.log('data',clm);

// >> "index", [0,1,1,1,1,2,3,1,1,1,1,2]
// >> "data", [[72],[101,108,108,111,111,114,108,100],[32,33],[87]]

インデックス並び替え

インデックス並び替えの処理を作る

と言ってもいきなりは辛いのでゴールの確認から
専用の関数 freq を作ってインデックスのみを投げて最適化してもらう

// 現状の確認
console.log('index',ind);
console.log('data',clm);

var freq_=freq(ind);    // 最適化したインデックス
ind=ind.map(x=>freq_.indexOf(x));   // 置換する
clm=freq_.map(x=>clm[x]);   // グループごと並び替える

//最適化後
console.log('index',ind);
console.log('data',clm);

//もう一度最適化をする意味がないことの検証
console.log(freq(ind)); // これは0,1,2…になるはず

肝心のfreq関数

もいきなりは辛いので形から
頻度順に並び替えた組を再び別の関数に投げる
ちなみに二桁以上のインデックスがある場合はうまく機能しないが
下手な閾値を設定しない限りそこまで大量のインデックスが来ることもないので

直しました

const freq=inp=>{
    var inpm=[...new Array(Math.max(...inp)+1).keys()]; // 0~インデックスの最大値の配列
    if(inpm.length<3){  //インデックスが十分に少ないときは無駄な操作になるのでそのまま返す
        console.log('freq skipped',inpm);
        return inpm;
    }

    // 全隣り合わせを列挙してゾロ目カットと並び順が逆なものは昇順に書き換え
    var arr=inp.map((x,i,c)=>{if(c[i+1])return[x,c[i+1]];})
            .filter(x=>x&&x[0]!=x[1]).map(x=>x[0]>x[1]?[x[1],x[0]]:x);

    // 頻度の計測 (海外のページ参考 元ページ見つかりませんでした…)
    var fq={};arr.forEach(x=>fq[x]=0);
    var s=arr.filter(x=>++fq[x]==1);
    s.sort((a,b)=>fq[b]-fq[a]);
    // s に頻度が高かった組み合わせから重複なく入っている

    // 頻度をもとに繋げる
    var tmp,out=[];
    while(true){
        tmp=joinPair(s);    // return [ 繋げた結果, 繋げられなかった組 ];
                    //(1組だけ渡したときはつなげられたものとして返す)
        s=tmp[1];   // 繋げられなかったものはもう一度試す
        out=out.concat(tmp[0]);
        if(tmp[1].length==0)break;
    }

    // 途中で欠けたインデックスを補完する  
    out=out.concat(inpm.filter(x=>out.indexOf(x)==-1));
    console.log('freq',out);
    return out;
}

渡された組を繋げる関数
joinPairを作る
何をもって成功とするかが難しい

const joinPair=x=>{
    var indm=Math.max(...(x.join('').split('')));   // 最大値
    console.log('joinPair',x,indm);

    // 一次元の座標x=indmに原点を作ってそこに最頻の組を置く
    var ind=new Array(indm+1).fill(null),   // どの位置にどのインデックスが入ったか
    s=new Array(indm).concat(x[0].split(''));

    // すでに配置した最頻は消去
    x.shift();
    ind[s[indm]]=indm;      // 最初の組の位置を記録
    ind[s[indm+1]]=indm+1;

    // 組同士を繋げる
    var tmp,flag=true;
    while(flag){
        flag=false;
        x=x.map((e,i)=>{
            if(ind[e[0]]&&!ind[e[1]]){
                if(!(s[ind[e[0]]+1]+1)){s[ind[e[0]]+1]=e[1];ind[e[1]]=ind[e[0]]+1;}//ok
                else if(!(s[ind[e[0]]-1]+1)){s[ind[e[0]]-1]=e[1];ind[e[1]]=ind[e[0]]-1;}//ok
            }//doom
            else if(!ind[e[0]]&&ind[e[1]]){
                if(!(s[ind[e[1]]+1]+1)){s[ind[e[1]]+1]=e[0];ind[e[0]]=ind[e[1]]+1;}//ok
                else if(!(s[ind[e[1]]-1]+1)){s[ind[e[1]]-1]=e[0];ind[e[0]]=ind[e[1]]-1;}//ok
            }//doom
            else if(!ind[e[0]]&&!ind[e[1]])return e;//pass
            //else if(ind[e[0]]&&ind[e[1]])//doom
            flag=true;console.log(e,s,ind);
        });
        // 繋げられなかった組も再配置できるか試す
        x=x.filter(y=>y);
        console.log(x);
    }
    // nullの除去 +1で0抜け回避
    return [s.filter(e=>e+1),x];
}

以上3つを繋げるとこんなかんじ
ここでの例はHello World!ではないです(短すぎる)
長いほど威力を発揮します

const freq=inp=>{
    const joinPair=x=>{
        var indm=x.reduce((a,y)=>Math.max(y[0],y[1],a||0));
        console.log('joinPair',x,indm);
        var ind=new Array(indm+1).fill(null),s=new Array(indm).concat(x[0]);
        x.shift();ind[s[indm]]=indm;ind[s[indm+1]]=indm+1;
        var flag=true;
        while(flag){
            flag=false;
            x=x.map((e,i)=>{
                if(ind[e[0]]&&!ind[e[1]]){
                    if(!(s[ind[e[0]]+1]+1)){s[ind[e[0]]+1]=e[1];ind[e[1]]=ind[e[0]]+1;}//ok
                    else if(!(s[ind[e[0]]-1]+1)){s[ind[e[0]]-1]=e[1];ind[e[1]]=ind[e[0]]-1;}//ok
                }//doom
                else if(!ind[e[0]]&&ind[e[1]]){
                    if(!(s[ind[e[1]]+1]+1)){s[ind[e[1]]+1]=e[0];ind[e[0]]=ind[e[1]]+1;}//ok
                    else if(!(s[ind[e[1]]-1]+1)){s[ind[e[1]]-1]=e[0];ind[e[0]]=ind[e[1]]-1;}//ok
                }//doom
                else if(!ind[e[0]]&&!ind[e[1]])return e;//pass
                //else if(ind[e[0]]&&ind[e[1]])//doom
                flag=true;console.log(e,s,ind);
            });
            x=x.filter(y=>y);console.log(x);
        }
        return [s.filter(e=>e+1),x];
    }
    var inpm=[...new Array(Math.max(...inp)+1).keys()];
    if(inpm.length<3){console.log('freq skipped',inpm);return inpm;}
    var arr=inp.map((x,i,c)=>{if(c[i+1])return[x,c[i+1]];})
        .filter(x=>x&&x[0]!=x[1]).map(x=>x[0]>x[1]?[x[1],x[0]]:x);
    //freq sort
    var fq={};arr.forEach(x=>fq[x]=0);
    var s=arr.filter(x=>++fq[x]==1);
    s.sort((a,b)=>fq[b]-fq[a]);
    //arr
    var tmp,out=[];
    while(true){
        tmp=joinPair(s);s=tmp[1];out=out.concat(tmp[0]);
        if(tmp[1].length==0)break;
    }
    out=out.concat(inpm.filter(x=>out.indexOf(x)==-1));console.log('freq',out);
    return out;
}

console.log('index',ind);console.log('data',clm);
var freq_=freq(ind);ind=ind.map(x=>freq_.indexOf(x));clm=freq_.map(x=>clm[x]);
console.log('index',ind);console.log('data',clm);console.log('test',freq(ind));


// >> "index", [0,1,1,1,1,1,0,2,1,1,0,3,3,0,1,0,0,4,3,2,0,1,0,3,5]
// >> "data", [[99,101,103,105,100,101,105,100],[111,110,115,111,108,108,111,110,110],[46,44],[40,39,39,41],[120],[59]]
// >> "joinPair", [[0,1],[0,3],[0,2],[1,2],[0,4],[3,4],[2,3],[3,5]], 5
// >> [0,3], [null,null,null,null,3,0,1], [5,6,null,4,null,null]
// >> [0,2], [null,null,null,null,3,0,1], [5,6,null,4,null,null]
// >> [1,2], [null,null,null,null,3,0,1,2], [5,6,7,4,null,null]
// >> [0,4], [null,null,null,null,3,0,1,2], [5,6,7,4,null,null]
// >> [3,4], [null,null,null,4,3,0,1,2], [5,6,7,4,3,null]
// >> [2,3], [null,null,null,4,3,0,1,2], [5,6,7,4,3,null]
// >> [3,5], [null,null,null,4,3,0,1,2], [5,6,7,4,3,null]
// >> []
// >> []
// >> "freq", [4,3,0,1,2,5]
// >> "index", [2,3,3,3,3,3,2,4,3,3,2,1,1,2,3,2,2,0,1,4,2,3,2,1,5]
// >> "data", [[120],[40,39,39,41],[99,101,103,105,100,101,105,100],[111,110,115,111,108,108,111,110,110],[46,44],[59]]
// >> "test", [0,1,2,3,4,5]

やっとインデックス並べ替えができました
慰労困憊
結晶が育つみたいで綺麗ですね()

最大公約数なのか…?

各グループの最初の数を集めて

tmp=clm.map(x=>x[0]);

これでうまい具合にループで作るのでした

ここで問題
Q.
 0~255までの数A,B,C,D…があって
 a*x+a'=A
 b*x+b'=B
 c*x+c'=C
 d*x+d'=D
 …
 のとき
 x+a+b+c+d+…+|a'|+|b'|+|c'|+|d'|+…が最小になるxを求めよ。

A.
 知るか!
 とりあえず4あたりから√256まで探索するぞ!

→探索します!!!

// jsのroundが特殊であることと今回必要なのは5捨6入なので用意する
const round=x=>-Math.sign(x)*Math.round(-Math.abs(x)),
slm=arr=>{  // (search loop minimum)
    var m=Number.POSITIVE_INFINITY,s,tmp;
    for(var i=4;i<=16;i++){
        tmp=arr.map(x=>Math.abs(x>127?256-x:x)) // 逆回り
               .map(x=>round(x/i)+x%i)
               .reduce((a,b)=>a+b)+i;   // 合計
        if(tmp<m){m=tmp;s=i;}   //更新
        console.log(i,tmp);
    }
    console.log('slm',s);
    return s;
};

// >> 4, 81
// >> 5, 69
// >> 6, 64
// >> 7, 60
// >> 8, 57
// >> 9, 55
// >> 10, 51
// >> 11, 66
// >> 12, 52
// >> 13, 68
// >> 14, 46
// >> 15, 72
// >> 16, 53
// >> "slm", 14

どうやらHello World!の場合はx=14が最適のようです
この結果を使ってグループの先頭に端数を除いた値を追加しておきます

md=slm(tmp);
tmp=tmp.map((x,i)=>{
    var r=round((x>127?x-256:x)/md);
    clm[i].unshift(r<0?256+r*md:r*md);
    return r;
});
console.log('boot',tmp);    // 端数
// >> "boot", [5,7,2,6]

ループ+端数→出力

ラストスパート
計算結果をBrainf*ckに落としていきます

s='+'.repeat(md)+'[';   // さっきのx=14
tmp.forEach(x=>s+='>'+(x>0?'+':'-').repeat(Math.abs(x)));   // x*__
s+='<'.repeat(tmp.length)+'-]>';
ind.forEach((x,i)=>{
    var p=x-(ind[i-1]||0),to=clm[x][1]-clm[x][0];clm[x].shift();
    // 目的のグループまでポインタを移動
    s+=(p>0?'>':'<').repeat(Math.abs(p));
    // 逆回り注意してメモリに書く
    p=Math.abs(to);s+=(to>0^p>128?'+':'-').repeat(p>128?256-p:p)+'.';
    console.log(i,x,s);console.log(clm);
});
console.log('result:',s.length,s);

// >> 0, 0, "++++++++++++++[>+++++>+++++++>++>++++++<<<<-]>++."
// >> [[72],[98,101,108,108,111,111,114,108,100],[28,32,33],[84,87]]
// >> 1, 1, "++++++++++++++[>+++++>+++++++>++>++++++<<<<-]>++.>+++."
// >> [[72],[101,108,108,111,111,114,108,100],[28,32,33],[84,87]]
// >> 中略
// >> 10, 1, "++++++++++++++[>+++++>+++++++>++>++++++<<<<-]>++.>+++.+++++++..+++.>++++.>+++.<<.+++.------.--------."
// >> [[72],[100],[32,33],[87]]
// >> 11, 2, "++++++++++++++[>+++++>+++++++>++>++++++<<<<-]>++.>+++.+++++++..+++.>++++.>+++.<<.+++.------.--------.>+."
// >> [[72],[100],[33],[87]]

// >> "result:", 104, "++++++++++++++[>+++++>+++++++>++>++++++<<<<-]>++.>+++.+++++++..+++.>++++.>+++.<<.+++.------.--------.>+."

完成です!
Hello World!が104字で出来ました

まとめ

私が探した限りHello World!の最小は105字までしか見つからなかったので
104字が出て来た瞬間はとても嬉しかったです

コードまみれ & とても長い記事になっちゃいました
記事書くの向いてないのがよくわかりました()
最後まで読んでくださりありがとうございます

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

マウスホバー時の構文覚書

JavaScriptを使った時のマウスホバー時のイベント構文について、自身への覚書きです。

 let textChange = document.getElementById('about');
  function mouse(word,word2){
  textChange.addEventListener('mouseover',()=>{ 
  textChange.innerHTML=word;
  });
  textChange.addEventListener('mouseleave',()=>{ 
    textChange.innerHTML=word2;
    });
  };
  mouse("こんにちは","さようなら");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js] v-ifの使い方

はじめに

vue.jsでv-ifを使ったので備忘録として記事を作成

v-ifとは

要素の表示・非表示を操作することができる

表示:true
非表示:false

実装方法

一部の要素を非表示にしたい場合はfalseを指定する

sample1.vue
<input v-if='false' />

非表示にしたい要素が複数ある場合はtemplateで囲む

sample2.vue
<template v-if='false'>
  <input />
  <input />
  <input />
</template>

参考

https://jp.vuejs.org/v2/guide/conditional.html

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

Jamstack とは何か( 2021 年 1 月時点)

Jamstack 公式サイト
https://jamstack.org/

Jamstack とは何か( 2021 年 1 月時点)

Jamstack is the new standard architecture for the web. Using Git workflows and modern build tools, pre-rendered content is served to a CDN and made dynamic through APIs and serverless functions. Technologies in the stack include JavaScript frameworks, Static Site Generators, Headless CMSs, and CDNs.

Jamstack は、ウェブの新しい標準アーキテクチャ。Git ワークフローと最新のビルドツールを使用して、あらかじめレンダリングされたコンテンツを CDN に提供し、API とサーバーレス機能を介して動的なものにする。スタックに含まれるテクノロジーには、JavaScript フレームワーク、スタティックサイトジェネレーター、ヘッドレス CMS 、CDN などがある。

The core principles of pre-rendering, and decoupling, enable sites and applications to be delivered with greater confidence and resilience than ever before.

プリレンダリングとデカップリングの基本原則により、サイトやアプリケーションをこれまで以上に信頼性とレジリエンス(変化に対処する能力)を持って配信することができる。

(参考)これまでの Jamstack / JAMstack

  • Matt Biilmann (Netlify 社)が 2016 年頃に提唱し始めたもの。
  • もともとは JAMstack 。 JavaScript 、APIs 、Markup の頭文字と言われていた。
  • 2019 年 12 月頃から 2020 年 4 月頃 にかけて JAMstack から Jamstack 表記へ変わった。
  • 定義/構成要素は JavaScript 、API 、Markup という要素を含むものから、Web サーバを介さないもの、プリレンダリングとデカップリングするもの、といった形で世の中の浸透具合にあわせて広義に変化している模様。

Jamstack とはつまり何か

  • Google の Rendering on the Web の以下図における CSR with Prerendering であると言える。 image.png
  • SSR するのではなく静的 HTML をプリレンダリングするもの。SPA がそうであるように、サーバサイドとクライアントサイドが分離(デカップリング)している構成であるもの。

Jamstack がなぜ理解しにくいのか

  • SSR (Server Side Rendering) 、CSR (Client Side Rendering) という概念を理解する必要があること
  • 前述の図にもあるように、SSR や CSR は更に細分化され、複数の種類があることを理解する必要があること
  • 静的レンダリングとプリレンダリングといったように似た用語で異なる概念があり混乱しやすいこと
  • SSR 、CSR 等を理解するのにアプリのビルド・デプロイのフローを理解していないと混乱しやすいこと
  • ISR (Incremental Static Regeneration) といった新しい概念も登場していること
  • 概念の実装例/具体例(例えば Next.js といったフレームワーク)が日々進化し機能拡張されていることから、一概に定義できず( SSG であり SSR である等)複雑化していること

参考記事

公開時期された時期に注意。
- JAMstackってなに?実践に学ぶ高速表示を実現するアーキテクチャの構成 (2019/12/10)
- Jamstackとは何か?まずは基本を理解しよう! - microCMS (2020/08/07)
- Next.jsにおけるSSG(静的サイト生成)とISRについて(自分の)限界まで丁寧に説明する (2020/11/11)
- Jamstackって何なの?何がいいの? (2020/02/08)

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

#UIFlow の BLE UART を使った #M5Stack_Core2 ( #M5Stack )とブラウザとの双方向無線通信

最新の UIFlow(M5Stackシリーズのビジュアルプログラミング環境)で BLE通信を実装するためのブロックが追加されたため、以前、M5Stack Core2 とブラウザとの間での BLE通信を試しました(ブラウザ側では Web Bluetooth API を利用)。

●【JavaScript 2020】 #UIFlow の BLE UART を使ったブラウザから #M5Stack_Core2 ( #M5Stack )への文字の送信 - Qiita
 https://qiita.com/youtoy/items/3da58570972803134f6c

この時は双方向の通信は試せておらず、ブラウザから M5Stack Core2 への文字列送信のみ試していました。

そこで今回、M5Stack Core2 から ブラウザへの文字列送信も試し、双方向通信を実装してみます。

注意

冒頭で紹介した記事の前に書いた、以下の記事の中で記載しているのですが、現状で UIFlow の BLE UART を利用できるのは、「M5Stack Fire」か「M5Stack Core2」のどちらかになりそうです。

●#UIFlow の BLE UART を使った文字のやりとりを #M5Stack_Core2 で試してみた( #M5Stack ) - Qiita
 https://qiita.com/youtoy/items/0aeac01927d60c33f421

双方向の通信を実装する

今回、実装した内容の掲載をメインにします。

UIFlow での設定・プログラムの作成

設定

画面上にラベルを 1つ配置し、日本語が扱えるようにフォントは「Unicode 24」を選択しています。

ラベルの設定.png

プログラム

ブロックを組み合わせて作ったプログラムは以下のとおりです。

UIFlowのプログラム.png

ブラウザ側のソースコード

ブラウザ側で、M5Stack Core2 からデータを受信して画面に表示したり、M5Stack Core2 にデータを送ったりするためのソースは以下のとおりです。これを HTMLファイルとしてローカルに保存します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>UIFlow  BLE</title>
  </head>

  <body>
    <h1>UIFlow  BLE</h1>
    <button onclick="onStartButtonClick()">接続</button>
    <br />
    <button onclick="sendMessage()">テキスト書き込み</button>
    <br />
    <textarea id="text"></textarea>

    <script>
      const UUID_1 = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
      const UUID_2 = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; // Write
      const UUID_3 = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // Notify

      let bluetoothDevice;
      let characteristic_A, characteristic_B;
      const textarea = document.getElementById("text");

      async function onStartButtonClick() {
        try {
          console.log("Requesting Bluetooth Device...");
          const device = await navigator.bluetooth.requestDevice({
            filters: [{ namePrefix: "m5ble" }],
            optionalServices: [UUID_1],
          });
          console.log("Connecting to GATT Server...");
          const server = await device.gatt.connect();
          console.log("Getting Service...");
          const service = await server.getPrimaryService(UUID_1);
          console.log("Getting Characteristic...");
          characteristic_A = await service.getCharacteristic(UUID_2);
          console.log("Getting Characteristic...");
          characteristic_B = await service.getCharacteristic(UUID_3);
          await characteristic_B.startNotifications();
          console.log("> Notifications started");
          characteristic_B.addEventListener(
            "characteristicvaluechanged",
            handleNotifications
          );
        } catch (error) {
          console.log("Argh! " + error);
        }
      }

      async function sendMessage() {
        if (!characteristic_A) {
          return;
        }
        const text = "aaa";
        const arrayBuffe = new TextEncoder().encode(text);
        try {
          await characteristic_A.writeValue(arrayBuffe);
        } catch (error) {
          console.log("Argh! " + error);
        }
      }

      async function handleNotifications(event) {
        if (characteristic_B) {
          try {
            let value = event.target.value;
            const text = new TextDecoder().decode(value);
            textarea.textContent = text;
            console.log(text);
          } catch (error) {
            console.log("Argh! " + error);
          }
        }
      }
    </script>
  </body>
</html>

この中で設定している 3つの UUID は、冒頭に掲載していた以前の記事を書いた際に調べたものです。

双方向の通信を試す

上記の HTMLファイルをブラウザで開き、ページ内の「接続」ボタンを押して M5Stack Core2 とのペアリングを行ってください。

その後、ブラウザで開いたページ内の「テスト書き込み」ボタンを押すと、M5Stack Core2 の画面の背景の文字・テキストが変化します。なお、M5Stack Core2 の Cボタンを押すと、背景の色が最初の色に戻ります。

また、M5Stack Core2 の Aボタン・Bボタンをそれぞれ押すと、ブラウザで開いたページ内のテキストエリアに文字が表示されます。この文字は、M5Stack Core2 から送信された内容を表示しています。

実際に動作している時の様子は、以下のとおりです。

うまく動作しました!

ただ、プログラムでは「データを受信」という文字を表示するようにしていたはずなのに、端末上の表示で「デタを受信」となってたりするので、マルチバイト文字の部分は要チェックなところがありそう。

【追記】 ブラウザ側で可視化をする処理を入れたものを作ってみた

M5Stack Core2 のタッチスクリーンの触れられた位置座標を送信データにして、それをブラウザ側でリアルタイムに描画する、というものも作ってみました。

●#UIFlow の BLE UART を使った #M5Stack_Core2 ( #M5Stack )からブラウザへのデータ送信とグラフ化 - Qiita
 https://qiita.com/youtoy/items/1bf6e9390b5dc5d2ba51

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

JavaScript【基本文法】~Part3~

初めに

今回はJavaScriptの中で、自分が大事だと思った配列メソッドについて書いていきたいと思います。

目次

・map()

・filter()

・findIndex()

map()

mapメソッドを使う事で新しい配列を作る事が出来る。

入力

const obj = {
    "size": {text: "big"},
    "shape": {text: "square"},
    "num": {text: "many"}
};

const Array = Object.keys(obj).map(key =>{
    let value = obj[key]
    value['id'] = key
    return value
});
console.log(Array);

結果

[
  { text: 'big', id: 'size' },
  { text: 'square', id: 'shape' },
  { text: 'many', id: 'num' }
]

filter()

filterメソッドを使う事で条件に合う要素を抽出する事が出来る。

入力

const obj = [
    {id: "size", text: "big"},
    {id: "shape", text: "square"},
    {id: "num", text: "many"}
];
const Result = obj.filter(object =>{
    return object.id === "shape"
});
console.log(Result);

結果

[ { id: 'shape', text: 'square' } ]

findIndex()

findIndexメソッドを使う事で要素の何番目かを知る事が出来る。

入力

const obj = [
    {id: "size", text: "big"},
    {id: "shape", text: "square"},
    {id: "num", text: "many"}
];
const Index = obj.findIndex(object =>{
    return object.id === "shape"
});
console.log(Index);

結果

1

参考記事

https://www.youtube.com/watch?v=VBuBGqPGALg

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