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

Vue.jsのmodalにうまくCSSが効かなかった話

Vue.jsのmodal

こんにちは。最近Vue.jsを書いている学生のwebエンジニアです。
動的にsession管理するために、Vue.jsでmodal使いたいよねという話になり、ごねごね書いていましたが、いきなりcssが効かなくなり、こんな画面になってしまった。。(一応ぼかしいれますがご了承ください)

高さが足りなくて「作成」ボタンが埋もれてしまった、、、

CSSが効いていないときのコード

Modal.vue
<script>
import ~

export default {
  props: {
    省略
  },
  data() {
   return{};
  },
  methods: {
    showModal() {
      this.$modal.show(
        CreateUserModal,
        { UserId: this.UserId },
        { NameLists: this.NameLists },
        { name: "CreateUserModal", height: "800", scrollable: true }
      );
    }
  },
  created() {}
};
</script>

このコードだと最初みたいな画像のmodalになってしまう、

解決方法

Modal_.vue
<script>
import ~

export default {
  props: {
    省略
  },
  data() {
   return{};
  },
  methods: {
    showModal() {
      this.$modal.show(
        CreateUserModal,
        // ここを変える
        { UserId: this.UserId,
          NameLists: this.NameLists },
        { name: "CreateUserModal", height: "800", scrollable: true }
      );
    }
  },
  created() {}
};
</script>

show()は`第一引数にプロパティ、第二引数にcssを書かないと適用されないらしい。引数追加しすぎて、第三引数に書いていたのが問題でした。

こんな感じに修正!

参考
vue-modal公式ドキュメント

まとめ

公式ドキュメントをしっかり読もう

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

JavaScriptでundefinedの大小比較はつねにfalse。その理由を、仕様を引用して解説する

JavaScriptでは、undefinedの大小比較は常にfalseです。

undefined > 0 // false
undefined >= 0 // false
undefined < 0 // false
undefined <= 0 // false

undefined同士の大小比較でも常にfalseです。

undefined > undefined // false
undefined >= undefined // false
undefined < undefined // false
undefined <= undefined // false

なぜなのか、仕様を確認してみましょう。

大小比較 共通手順

大小比較のアルゴリズムは、仕様のAbstract Relational Comparisonの章に記載があります。

x < ytruefalseまたはundefinedを返します。undefinedが返る時は、少なくとも片方がNaN(非数)であることを表します。

x < yの判定のうち、片方(または両方)がundefinedのときのステップを中心に、以下に記載します。

  1. xをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
  2. yをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
  3. 変換した結果、両方ともStringであれば、
    1. xyを辞書順で並べ、xの方が先に来るならばtrue、そうでないならばfalseを返す(詳しくは仕様を参照してください)
  4. それ以外であれば、
    1. xをさらにNumberに変換する
    2. yをさらにNumberに変換する
    3. xが`NaNであればundefinedを返す
    4. yが`NaNであればundefinedを返す
    5. xyを数値として比較し、xの方が小さければtrue、そうでないならばfalseを返す(詳しくは仕様を参照してください)

undefinedの大小比較 共通手順を追う

例として、undefined < 0のときの処理を追ってみましょう。
上の手順に当てはめると、x = undefinedy = 0です。

  1. xをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
    undefinedをプリミティブ型に変換するとundefined(変化なし)です。
  2. yをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
    0をプリミティブ型に変換すると0(変化なし)です。
  3. 変換した結果、両方ともStringであれば、
    → 条件に当てはまらないので次に進みます
  4. それ以外であれば、
    1. xをさらにNumberに変換する
    2. yをさらにNumberに変換する
      Numberへの変換表はこちらです。undefinedNaNに変換され、00のままです。
データ型 返却値
Undefined NaN
Null +0
Boolean trueならば1、falseならば+0
Number 元の値
String 文字列が数字ならばその数字、それ以外はNaN。詳細はToNumber Applied to the String Typeを参照
Symbol TypeError exceptionを投げる

nulltruefalseが数値に変換されるのも興味深いです。これらに言及した記事を参考資料としてリンクしました。

次に、 3. xNaNであればundefinedを返す
xの値を変換した結果がNaNなので、undefinedが返されます。

さて、ここまででundefined < 0の評価値はundefinedであることがわかりました。
しかし、実際はundefined < 0の評価結果はfalseです。何が間違っているのでしょうか。
それを解き明かすには、もうひとつの仕様を読む必要があります。

大小比較の実行手順

大小比較の実行手順はRuntime Semantics: Evaluationに記載があります。

主要な部分だけ抜粋します。

x < yの評価方法は、
1. 大小比較 共通手順でx < yを評価する
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す

x > yの評価方法は、
1. 大小比較 共通手順でy < xを評価する
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す

x <= yの評価方法は、
1. 大小比較 共通手順でy < xを評価する
2. 結果がtrueまたはundefinedであればfalse、それ以外であればtrueを返す

x >= yの評価方法は、
1. 大小比較 共通手順でx < yを評価する
2. 結果がtrueまたはundefinedであればfalse、それ以外であればtrueを返す

undefinedの大小比較の実行手順を追う

undefined < 0のときの処理を追ってみましょう。
x < yの評価方法を見ます。
1. 大小比較 共通手順でx < yを評価する
→ 上で確認したように、undefined < 0の評価結果はundefinedです。
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す
undefinedなので、falseが返ります。

x > yx <= yx >= yのそれぞれについても、undefined < 0の結果がundefinedなので、全ての比較においてfalseが返ることがわかりました。

x < yfalseだからといって、x >= yが必ずしもtrueにならないことがあるのですね。

参考資料

あとがき

意外にも、undefinedの大小比較についての記載が見当たらず苦労しました。
数値同士や文字列同士以外を比較するようなコードを、実際に仕事で書くことがあるとしたら多分アプリの設計ミスなのですが、undefinedが紛れ込むことはあるかなーと思い、気をつけようと思いました。

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

Math.random()を使って確率操作してみる(おみくじ)

qiita.js
'use strict';

{
  const btn = document.getElementById('btn');

  btn.addEventListener('click', () => {
    // const results = ['大吉', '中吉', '凶', '大凶'];
    // const results = ['大吉', '中吉', '中吉', '中吉', '凶'];
    // btn.textContent = results[Math.floor(Math.random() * results.length)];
    const n = Math.random();
    if (n < 0.05) {
      btn.textContent = '大吉'; // 確率は5%
    } else if (n < 0.2) {
      btn.textContent = '中吉'; // 確率は15%
    } else {
      btn.textContent = ''; // 確率は80%
    }
  });
}

if (n < 確率)と記述するし、確率操作が可能になる。

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

iPad ProでPWAして全画面表示する~最終的に運用でカバー~

fs.jpg

おことわり

今回iPad Proにて全画面表示を行う必要があったため、PWAを試してみたが、結構時間がない中での実装だったうえに、iPadがレンタルだったため、細かい検証を行うことができなかった。スクショも取れていないので写真で失礼させていただく。
PWAはまだまだ機能として開発途中な感じがあり、イマイチ挙動が掴めきれなかったので、もしかしたら言葉のニュアンスの違いなど間違ったことを言っている部分があるかもしれません、そういったところは、指摘いただければできるだけ直します。よろしくお願いします。
本来なら記事にするのレベルにも達していない内容だと思いますが、数少ない同じ用途で悩んでいるエンジニアのヒントになればと思い、書くことにしました。なので優しくしてください!w
また、今回先人たちの記事を大量に参考にさせていただいたので、ここで感謝を述べておきたい。

製作背景

今回ある案件で、展示されたiPadからアプリを介して情報を入力する機能を作成することになった。エンドユーザーが使用するため、必要とされる機能は、

  • ネイティブアプリのように全画面表示できる
  • 関係のない機能に触らせないようする
  • ユーザーの操作でホーム画面に戻らないようにする

など。
swiftを学習する時間はなかったので、ブラウザで全画面表示すればいけるだろうと高をくくって実装を始めたけど、結構クセが強くてハマったので記録しておく。

[参考]https://qiita.com/tmtysk/items/2c5da83feec45b4ee36f
[参考]https://qiita.com/umamichi/items/0e2b4b1c578e7335ba20

結果

結論から言うと、自動で全画面表示にすることはできなかった。
起動時に画面上部に帯が一本出てしまう状態で、上にスライドすると消える。というところまでしかできなかったので、現場でスタッフさんに朝イチでアプリを起動したら、画面を上にスライドしてもらう。
という運用カバーになってしまった。

使用した機材

iPad Pro 12.9 インチモデル(第三世代)
iOS 13.2.2

実装方法

1.manifest.jsonを設置する

short_nameはブラウザのタイトルバーとかに出る名前。
nameはアプリ名っぽい。
iconsの192x192に設定した画像がアイコンになるけどこれは罠で、ここだけに書いても反映されない。
後述するタグも埋め込まないといけないらしい。

あと余談だが、このshort_nameに設定した名前を後で変更した場合 safariのキャッシュをクリアしても反映されない ここは最後までどうやって更新するのかわからなかった。
全画面表示にするには、"display"のバリューを"standalone"、もしくは"fullscreen"とすればよさそうだが、手元の環境ではどちらに設定しても挙動は変わらなかった。(キャッシュの影響かもしれない)

manifest.json
{
  "short_name": "TEST APP",
  "name": "TEST",
  "icons": [
    {
      "src": "./icon.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "./icon.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/start",
  "background_color": "#CDCDCD",
  "display": "standalone",
  "theme_color": "#000000"
}

[参考]https://developers.google.com/web/fundamentals/web-app-manifest

2.Service Workerを有効にする

下記のようなservice-worker.jsを設置する。

service-worker.js
self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
});
self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
});
self.addEventListener('fetch', function(event) {});

[参考]https://qiita.com/umamichi/items/0e2b4b1c578e7335ba20

3.headerにタグを入れる

index.html
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="icon.png" sizes="192x192"/>
<link rel="manifest" href="manifest.json">
<script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register("js/service-worker.js").then(function() { console.log('Service Worker Registered'); });
   }
</script>

上の二つのタグに関しては、かつてこれらのタグを入れることで透明になったらしいが、iOS13からblack-translucentgが削除された?というような情報もある。
とにかく、手持ちのiPhone7(iOS11.4.1)では効いたが、iPad(iOS13.2.2)ではこれだけで透明にならなかった。
PWA化するとスライドしたときに消えるので、効いているような気もするが挙動がバグっぽい。。
真ん中のiconのリンクタグが、manifestのところで言っていたもの。これを埋め込まないとアイコンが反映されない。
あと、下の二つのタグで、manifest.jsonとservice-worker.jsを読み込んでいる。

4.ホーム画面に追加からアプリ化する

青で消してるのはキャッシュが消えなくてshort_nameが変更できなかったため。
これを行うと、ホーム画面にアプリっぽいものが作成される。
fs3.jpg

で、ホーム画面に作成されたアイコンから起動すると以下のような画面になる

fs2.jpeg

この上の白いバーの存在が謎。。。
これについては、何かタグが入っているのではないかと思い、document.body.clientHeightで出力してみたが、本来の高さが返ってきたのでおそらくsafari側で入れられている何かかと思われる。
この状態から一度上にスライドすれば以降このバーは消えるのだが、そもそもkioskとしての運用なので、スライド操作自体を無効化したい。

5.一度だけ上方向へのスライドを有効にする。

[参考]https://gist.github.com/violetyk/5343883

    function no_scroll(){
        document.addEventListener('touchmove', handleTouchMove, { passive: false });
    }
    function scroll(){
        document.removeEventListener('touchmove', handleTouchMove, { passive: false });
    }
    let start_pos = 0;
    $(window).scroll(function(e){
      var current_pos = $(this).scrollTop();
      if (current_pos > start_pos) {
        no_scroll();
      }else{
            // 
        }
      start_pos = current_pos;
    });

ここからが力業。アプリ起動後一度だけ上方向へのスクロールを許すようにする。
これで一応全画面表示ができるようになった。
スクロールの復帰関数も用意してあるのは、アプリを再起動すると上の謎のバーが再び出現するため、画面内に隠しボタンを設置し、そこを押すと再びスクロールできるように作ったため。
そしてこのスクロール、これもなかなかハマりポイントで、古い記事の方法とかだと動かなかったりする。
[参考]https://qiita.com/yukiTTT/items/773356c2483b96c9d4e0

アクセスガイドを設定してホーム画面に戻れないようにする

設定からアクセスガイドをオンにする。
ホームボタンがある機種はホームボタンを、ない機種はサイドの電源ボタンを三回押しすると起動する。
これで完全なフルスクリーンになる。

こちらの機能は、タッチ不可の領域を設定したりできるが、設定した範囲はグレーになるので、見栄えがあんまりよろしくない。

[参考]https://qiita.com/ousaan/items/5464f99bf15675ccc84b

他に試したことなど

フルスクリーンAPIを使う

フルスクリーンAPIというのがある。
こちらも試してみたが、自動で全画面にするっていうのが無理で、何かしらのユーザーの操作をトリガーにしないといけないっぽい。(セキュリティ的に?)
かつ、全画面にしても左上に大きなxボタンが出るので、ちょっと今回の用途には合わなかった。
fs4.jpg

[参考]https://qiita.com/annnews23/items/728a87a256cd0cf6e3a5
[参考]https://qiita.com/uzuki_aoba/items/ea7f35bc2833dd58b9ef

そもそもタブレットで全画面表示するのは難しい

PCの場合、chromeにkioskモードというのがある。
これを使えば全画面表示で最前面に表示されkioskとして使用できる。
(タッチデバイスの場合はこまごま他の設定が必要だが。。)
PCはキーボードというインターフェースがあるのでf11で一発で復帰できるが、
そもそもタブレットは物理的なインターフェースが少ない。
全画面表示で操作不能となるような事態を避けるため、メーカー側でそう簡単には完全な全画面にならないように設計していると思われる。
案件状況によっては、タッチ機能の付いていないモバイルディスプレイで全画面表示するように製作したほうがはるかに簡単なので、こだわりがなければこちらに切り替えることをお勧めする。

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

ElectronでcontextBridgeによる安全なIPC通信(受信編)

はじめに

Electronにおけるメインプロセスとレンダラープロセス間のやり取りに関して、セキュアなIPC通信にはcontextBridge1を使おう、という記事を前回書いたらそれなりに読んでもらえているみたいです。ありがとうございます。

その時の例として、レンダラープロセスからメインプロセスへの送信を扱いましたが、受信についてもリクエストがあったので紹介します。基本的にはStackOverFlow2からの引用です。

基本的にElectronにおけるメニュー操作はメインプロセスでハンドルすることになるので、それをレンダラープロセスへ送る際には、メインプロセスからチャンネル付きで信号を送信し、レンダラープロセスで受信時にチャンネルに従って処理を分ける、ということをするでしょう。これを目的としたcontextBridgeの利用法です。

前回からの改修点

まずは前回の記事の方法3までをお読みください。今回は前回の方法3からの改修点として次の様にしました。

  • レンダラーからメインプロセスへの送信時にはチャンネルを設定して複数の信号の送信に対応した。
  • メインプロセスから送信してレンダラープロセスで受信する部分では:
    • アプリのメニュークリックで送信(メニュー操作はメインプロセスの範疇)
    • レンダラーで受信したらHTMLに反映

方法4:レンダラーでの受信

メインプロセスのコードはmain.jsとします。preloadファイルはpreload.js、レンダラープロセスで読み込むhtmlファイルはindex.htmlとします。各ファイルの中身は次のようになります。

/* main.js, case 4(extend: send and recv) */
"use strict";
const {electron,BrowserWindow,app,ipcMain,Menu} = require('electron');
let mainWindow = null;
const CreateWindow = () => {
  mainWindow = new BrowserWindow({width: 800, height: 600,
    webPreferences: { 
      nodeIntegration: false,
      contextIsolation: true,
      preload: __dirname + '/preload.js'
    } });
  mainWindow.webContents.openDevTools(); 
  mainWindow.on('closed', function() {
    mainWindow = null;
  });

  /*menu creation*/
  const template = [ {
    label: 'File',
      submenu: [{
        label: 'Open',
        click: (menuItem, browserWindow, event) => { 
          // メニュークリック時に実行される関数//

          //ここでファイルを開いたりする(今回は暫定)//
          const openedPath = "./hogehoge.txt";
          const readData = "ファイルの中身だよ";
          //ここまででファイルは読み込んだものとする//

          //この関数でIPC送信(main to renderer)//    
          browserWindow.webContents.send(
            "openfile", //送信チャンネル名(自分で区別できるように)//
            {   //送信したいデータ一覧//
               filePath: openedPath,
               dataText: readData
            }
          ); 
        } 
      }]  
    }];

    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu);

    mainWindow.loadURL('file://' + __dirname + '/index.html');
}
app.on('ready', CreateWindow);

//IPC受信部//
ipcMain.on("msg_render_to_main1", (event, arg) => {
  console.log(arg); //printing "good job"
});
ipcMain.on("msg_render_to_main2", (event, arg) => {
  console.log("We are the", arg.teamName, "!");
  //printing "We are the Victories !"
});
/* preload.js, case 4 (extend)*/
const { contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld(
  "api", {
    send: (channel, data) => {//rendererからの送信用//
        ipcRenderer.send(channel, data);            
      },
    on: (channel, func) => { //rendererでの受信用, funcはコールバック関数//
        ipcRenderer.on(channel, (event, ...args) => func(...args));
    }
  }
);
<!--index.html, case 4 (extend)-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Test</title>
  </head>
  <body>
    <button id="button1">test1</button>
    <button id="button2">test2</button>
    <div id="previewF">受信ファイル名</div>
    <div id="previewD">受信データ</div>
  </body>
  <script type = "text/javascript">
      //適当なプログラム
    const button1 = document.getElementById("button1");  

    //送信用(チャンネル名指定)//
    button1.addEventListener("click", (e)=>{
      window.api.send("msg_render_to_main1", "god job");
    });

    //送信用(チャンネル名指定)//
    button2.addEventListener("click", (e)=>{
      window.api.send("msg_render_to_main2", {teamName: "Victories"});
    });

    //受信部(チャンネル名指定)//
    window.api.on("openfile", (arg)=>{
      document.getElementById("previewF").textContent = arg.filePath;
      document.getElementById("previewD").textContent = arg.dataText;
    });
 </script>
</html>

まず、レンダラープロセスからメインプロセスへ送信する部分ですが、ボタンを二つ配置し、チャンネル名を変えて二種類の信号が送れるようになっています。メインプロセスではipsMain.on(チャンネル名, コールバック関数) でチャンネル名を指定することで、処理を分けて行えるようになります。

次に、本目的のレンダラープロセスでの受信ですが、preload.jsでのonの部分の記述がポイントです。コールバック関数名をfuncとしておいて、 ipcRenderer.on()の中ではスプレッド構文...argsを使っています。これにより、メインプロセスから送られてきた引数のうち、eventだけを取り除いてコールバック関数へ渡しています。コールバック関数はindex.html内のwindow.api.on("openfile",(arg)=>{ ... })にて記述できるので、実質的にipcRenderer.on()を直接書いていた時代と同様に利用できます。

注意点

contextBridgeはとっても良さそうなAPIですが、Electronのドキュメント3には次のように書かれています。

"The contextBridge API has been published to Electron's master branch, but has not yet been included in an Electron release."

一応、私の環境のversion7.1.9では使えていますが、いつから使えるようになったのかはちょっと不明なので、気を付けてください。

感想

コールバック関数という表現が合っているのか不安ですが、Javascriptライト勢なのでご勘弁くだされ。

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

ReactのuseEffectの簡単サンプルまとめ

はじめに

React のHooksのUseEffectのメモです。
ご指摘、感想、お待ちしています。。。

目次

  1. まずはクラスで普通に書いてみる
  2. 関数コンポーネントで書き換え
  3. コンポーネントが削除される時は?

1. まずはクラスで普通に書いてみる

まずはクラスでチュートリアルにあるようなカウンターを書いてみます。

コンポーネントがマウントされた際に
componentDidMount()でカウントをコンソールログに出力しています。
componentDidUpdate()で更新が行われる度に同じくログを出力しています。

これを関数コンポーネントで書き換えてみましょう。

ClassCounter
import React, { Component } from 'react'

class ClassCounter extends Component {
    constructor(props) {
        super(props)

        this.state = {
            count: 0
        }
    }

    componentDidMount() {
        console.log(`Class Counter Mount !! ${this.state.count}`)
    }
    componentDidUpdate() {
        console.log(`Class Counter Update !! ${this.state.count}`)
    }

    render() {
        const { count } = this.state
        return (
            <div>
                <button onClick={() => this.setState({ count: count + 1 })}>
                    Click {count} times
                </button>
            </div>
        )
    }
}


export default ClassCounter

2. 関数コンポーネントで書き換え

ポイントはuseEffectの第2引数です。
空のリストの場合は、初回マウント時のみ
リストの中にstateを指定すると、そのstateが更新された時のみ
それぞれログが出力されるようになります。
また、リストには複数のstateを指定できます。
その場合は、どちらかが更新されるとログが出力されます。

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

export default function FunctionCounter() {

    const [count, setCount] = useState(0)
    const [text, setText] = useState('')
    const [isShow, setShow] = useState(true)

    // レンダリングされた時にのみ実行
    useEffect(() => {
        console.log(`Function Component Mount`)
    }, [])

    // countが更新された時に実行
    useEffect(() => {
        console.log(`count state is updated ${count}`)
    }, [count])

    // textが更新された時に実行
    useEffect(() => {
        console.log(`text state is updated ${text}`)
    }, [text])

    // count または textが更新された時に実行
    useEffect(() => {
        console.log(`state text and count are updated count=${count} text=${text}`)
    }, [text, count])

    return (
        <div>
            <input type='text' value={text} onChange={e => setText(e.target.value)}></input>
            <button onClick={() => setCount(count + 1)}>
                Click {count} times
            </button>
        </div>
    )
}

3. コンポーネントが削除される時は?

ボタン操作でClassChildComponentを表示を切り替えてみます。

ClassComponent
import React, { Component } from 'react'
import ClassChildComponent from './ClassChildComponent'

class ClassComponent extends Component {
    constructor(props) {
        super(props)

        this.state = {
            isShow: false
        }
    }

    render() {
        const { isShow } = this.state
        return (
            <div>
                <button onClick={() => this.state.isShow ?
                    this.setState({ isShow: false }) : this.setState({ isShow: true })}>
                    isShow is {isShow}
                </button>
                {isShow && <ClassChildComponent />}
            </div>
        )
    }
}


export default ClassComponent

ClassChildComponentの方でコンポーネントが削除される際の
componentWillUnmount()を定義しています。
これをuseEffectで書き換えます。

ClassChildComponent
import React, { Component } from 'react'

export default class ClassChildComponent extends Component {

    componentWillUnmount(){
        console.log('Child Class Component will Unmount !!')
    }

    render() {
        return (
            <div>
                <h3>Child Component</h3>
            </div>
        )
    }
}

まずは、同じように表示を切り替えるボタンのみのコンポーネントを準備します。

FunctionComponent
import React, { useState} from 'react'
import FunctionChildComponent from './FunctionChildComponent'

export default function FunctionComponent() {

    const [isShow, setShow] = useState(false)

    return (
        <div>
            <button onClick={() => isShow? setShow(false) : setShow(true)}>
                isShow is {isShow}
            </button>
            {isShow && <FunctionChildComponent />}
        </div>
    )
}

呼び出すコンポーネントはこんな感じ
useEffectの中でreturn()を定義してあげると、componentWillUnmountと同じ動きになります。

FunctionChildComponent
import React, {useEffect} from 'react'

export default function FunctionChildComponent() {

    useEffect(() => {
        return () => {
            console.log('Child Function Component will Unmount !!')
        };
    }, [])

    return (
        <div>
            <h3>Child Component</h3>
        </div>
    )
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Spark AR】Native UI SliderでSkin Smoothing

MaterialのSherder Type>Retouching>Skin SmoothingをNative UI Sliderで使いたいくてちょっといじってみたんだけど、公式のソースをコピペしたらエラーが出たので修正。

Adding the Native UI Slider

Exampleの1行目がNUIなのに8行目でNativeUI使ってるので、それ揃えるのが正解。

const NUI = require("NativeUI");
const M = require('Materials');
const Time = require('Time');

// Slider
var lastSliderValue = 0.5;
const mat = M.get('angery_mat');
const nativeUISlider = NativeUI.slider;

上記を修正したらmat.opacityskinSmoothingFactorに。

Scripting Object ReferenceからたどってってRetouchingMaterial参照。

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

setTimeout()を使ったループ処理

qiita.js
'use strict';

{
function showTime() {
console.log(newDate());
setTimeout(showTime, 1000);//繰り返す値と、1000ミリ秒(1秒)を引数に渡す
};

showTime();
}

結果として現在時刻を1秒毎に出力する。

上のコードだとループ処理になるため、指定した数字で止める処理は↓

qiita.js
'use strict';

{
 let i = 0;
 function showTime() {
    console.log(newDate());
    const timeoutId = setTimeout(showTime, 1000);
    i++;
    if(i > 3){  //3より大きい値になるまで処理
      clearTimeout(timeoutId)//clearTimeoutにsetTimeoutの返り値を定数に入れる
    }
  };

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

【JS】インクリメント�/デクリメントでつまずいたところ

無限ループにハマったので調べました...

// 普通に足してみる
let n = 5
console.log(n)
// 5
console.log(n + 1)
// 6 元の値には影響しない
console.log(n)
// 5
// 後述の場合
let n = 5
console.log(n)
// 5
console.log(n++)
// 5 nの値を返した後に、1プラスする
console.log(n)
// 6
// 前述の場合
let n = 5
console.log(n)
// 5
console.log(++n)
// 6 nの値をプラス1した後に、その値を返す
console.log(n)
// 6
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google App Script を V8 に切り替えたらものすごい速くなった

この記事は衝動的な動機によって書かれており、事実を正確に捉えるのに必要な証拠に欠けていることを予め断っておきたい。

また、分かりやすい数字というものは人間の良からぬ想像力を掻き立てる災いのもとになることも思い出して欲しい。この記事にはなるべく事実だけを書くことにして、自分の推測は「〜かも知れない」とか「〜と思う」と書くことにする。この記事を読まれたみなさんが何らかの推論をコメントやツイッターに書き散らしたい衝動にかられた時は、ぜひ同じことを実践して欲しい。

GAS のランタイムで V8 エンジンが使えるようになった

ソース
https://developers.google.com/apps-script/guides/v8-runtime?hl=ja#enabling_the_v8_runtime

Google App Script (以降 GAS) とは、Google スプレッドシートや Gmail など複数の Google サービスのデータにアクセスできるスクリプト実行環境のプラットフォームである。スクリプトとは、実際には JavaScript のことである。ユーザーが .gs という拡張子で JavaScript を書くと、それを Google のサーバー上で実行してくれる。これが無料で使えるとあって、昔から業務効率化などの目的でよく利用されてきた。

しかし、つい先日までの GAS は大きな欠点を抱えていた。使える JavaScript の文法が古いのだ。ここ数年で JavaScript (正確には ECMAScript というべきかも知れないが)の文法は大きく変わっている。例えば元々 var しかなかった変数宣言は var let const の三つに増え、用途に応じて使い分けられるようになった(そして今では var はアンチパターンとすら言われるようになった!)。2010年代前半はこれらの機能に対応しているブラウザとそうでないブラウザとがあり、開発者はブラウザの差異に大いに苦しめられたが、6 to 5 (現 Babel) の普及によって状況は一変した。モダンブラウザの普及を待つまでもなく、開発者らは新しい文法の JavaScript を書く自由を手に入れたのだ。そうして Babel は現代 JavaScript を語る上で欠かせないエコシステムの根幹となった。少し話が逸れたが、要するに、開発者はとにかくモダンな JavaScript を書きたくて仕方ないのだという性質を理解していただければ問題ない。

GAS の話に戻る。以前の GAS は Rhino という Mozilla 製の JavaScript インタプリタを利用していたそうだ。インタプリタとはプログラミング言語を実行してくれるソフトウェアの一形態である。一方、昨今モダンブラウザと呼ばれるブラウザに採用されているのは V8 (ブイエイトと読めばいいと思う)という Google 謹製の JavaScript エンジン(こちらはインタプリタと呼ぶべきではない)である。ここでは V8 と Rhino が実際にどう違っているかを詳しく説明することはしない。ここで重要なのは次の一点だけだ。 Rhino は新しい JavaScript の文法に対応していないのである。つまり、開発者は GAS のために古い文法の JavaScript を書かなければならなかった。勘の言い方は、前段に出てきた Babel の存在を思い出すだろう。確かに Babel を使えば GAS の開発でも新しい文法の JavaScript を書くことが可能だ。しかし、GAS の大きな特徴であるウェブ上でコードが書けることと Babel が相入れなかったのだと思う。他の開発者たちがどうしていたのか僕はよく知らないのだが、少なくとも僕は Babel の導入を諦めてしまった意志の弱い開発者のうちの一人だ。

そんな中、熱いニュースが飛び込んできた。ついに、GAS のランタイムで V8 エンジンが使えるようになったのだ。具体的にいうと、実行環境を V8 にするモードが提供され、任意でオンに出来るようになった。今はまだ、新規作成された App Script はデフォルトでこのモードがオフになっているが、これは近い将来変更されるかも知れない。互換性の問題ですぐにはオンにできないプロジェクトもあるだろうが、互換性がないことにゆるぎない自信を持てるほどにプロジェクトが小さければ、後述する理由によって V8 モードをオンにすることを推奨したい。さて、ここまでが前置である。

実行速度は速くなるのか?

この件に関して知り合いのプログラマーの方が「実行速度も速くなるのかな?」というツイートをしているのを見かけて、僕はこう思った。全体の実行速度は JavaScript を実行しているインスタンスにもよるから一概には言えない。確かに V8 は様々な面で JavaScript の実行を最適化している(と聞いたことがある)ので、純粋なコードの実行速度を比較すれば有利になるはずだ。

この時、僕は何か不思議な引っ掛かりを感じていた。うまく言語化出来ないのだが、それは実験してみたら面白い結果が出るんじゃないか、という期待感のようなものだったと思う。 App Script の編集画面から次のメニューを使ってランタイムを切り替えられたので、実際に速度比較を行ってみることにした。

Image from Gyazo

function speedTest() {
  var now = Date.now();
  var sum = 0;
  for (var i = 0; i < 1000 * 1000; i++) {
    sum += Math.random();
  }
  var time = Date.now() - now;
  Logger.log('time ' + time);
}

簡単なコードだが一応説明しておくと、1M 回乱数を生成して足し合わせるのにかかった処理時間を出力している。Date.now() は現在時刻をミリ秒単位の Unix エポックで返してくれる組み込み関数で、もちろん GAS でも使える。 Logger.log は GAS の標準的なログ出力関数だ。

さて、結果は次のようになった。

V8 オフ:5900ms

Image from Gyazo

V8 オン:65ms
Image from Gyazo

いろいろ思うところはあるが、 確かに V8 に切り替えたらものすごい速くなっている。 その後、ランタイムを切り替えながら2〜3回試してみたが、およそ 50% 程度の誤差はあるものの、そこまで大きな解離はなかった。平均や分散をちゃんと求めてはいないので、興味のある人は実験してみて欲しい。

しかし、仮にも同じコードで、ランタイムが違うというだけで、これだけの違いが生まれるだろうか?

僕がパッと思いついたことは試してみたが、他の要因が思いついた人はぜひレスしてみて欲しい。

実は sum を計算してないのでは?

最初のコードでは sum を出力していなかった。だから計算をまるごとスキップしていたのではないか?という疑惑だ。しかし、これは sum を出力に含めたところすぐに間違いだと分かった。計算時間は両者ともほとんど変わらなかったのだ。

Date.now() が意図的に正しくない値を返しているのでは?

セキュリティ的な理由でそういう感じのことをしているというのを聞いたことがあった。数千ミリ秒単位で誤魔化すとは思えないが、一応次のコードを試した。

var now = Date.now();
var time = Date.now() - now;
Logger.log('time ' + time);

しかし、これはどちらのランタイムでも 0ms という結果になった。

インスタンスのスペックが違うのでは?

要するに V8 モードをオンにすると、より高級なインスタンスが起動するのではないかという疑惑だ。これはもはや言いがかりのような理屈だが、かといって否定する証拠もないので、この可能性は捨て切れないと思う。インスタンスのスペックが Math.random()for の実行速度にどのように影響を及ぼすのかを僕はよく知らないが、スペックが良いなら実行速度も単純に上がるのではないかと予想できる。実際に GCP アカウントでインスタンスを立てれば再現できるのかも知れないが、それは App Script と同じ環境と言っていいのかよく分からない。

ここからは本当に僕の私見で、邪推と呼んでもいい理屈だが、もし仮に僕が GAS の V8 移行を指揮する立場にあったとしたら、実際の V8 の性能がどうであれ、ただ V8 モードをオンにするだけでコードの実行速度が明らかに改善されるという結果が望ましいものだと考えるだろう。それは直接的に顧客の利益に繋がるし、計算時間が短くて済むなら Google にとっても嬉しいはずだ。なぜなら GAS は無料だからである。

おわりに

長々と書いてきたが、Qiita に残すべき事実としては、表題の一行だけで十分だったのかも知れない。僕はこの記事で事実だけを述べていると自負しているが、ある人が読めば風評被害を起こそうとしているかのように、またある人が読めば Google のステマをしているかのようにも読めるかも知れない。あるいは、全ては何の有用性もない戯言だったのかも知れない。それではなぜ僕がこの記事を書く衝動に駆り立てられたかと言えば、V8 モードをオンにするだけでものすごい速くなるのは何故かという、その謎に惹かれたからだ。たった 4000ms の違いと思われるかも知れないが、その違いは、40分以上の時間を執筆に向かわせるに十分なものだったのだ。

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

v-forを使ってselect optionを生成

<div id="app">
  <select name="user" v-model="selected">
    <option disabled value="">選択して下さい</option>
    <option v-for="user in users" v-bind:value="user.name" v-bind:key="user.id">
      {{user.name}}
    </option>
  </select>
</div>
new Vue({
  el: '#app',
  data: {
    users: [
      {id:1, name:'佐々木'},
      {id:2, name:'鈴木'},
      {id:3, name:'高木'}
    ],
    selected: ''
  }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS / Androidでスクロール禁止にするが一部だけはスクロールできるようにする

表題の通りですが、スクロール禁止にするが一部だけはスクロールできるようにする場合の対応。

基本はhtmlにoverflow: 'hidden'を掛けて、スクロールしたいところにoverflow: 'scroll'を掛ければいいのですが (あまり確認していないので違うかも…)、iOSだとJavaScriptを使う必要があります。

スクロール禁止にする場合はpreventDefault()(と、{passive: false}も第三引数に必要)をtouchmoveイベントの時に実行すればいいのですが、注意するポイントが2つあります。

  1. スクロール禁止を解除する場合
    • removeEventListnerを実行すればいいのですが、addEventListnerを実行したときと同じ関数を渡さなければなりません。この時アロー関数だとaddの時とremoveの時で違うものとなりますのでアロー関数は使わないようにします。
  2. 表題の通り一部だけはスクロールできるようにする場合
    • スクロールしたい場合はpreventDefault()の代わりにstopPropagation()を実行すればよいです。下記のコードではtouchmoveイベントが発生した時の要素の親にjs-can-scrollクラスがあれば、スクロールできるようにstopPropagation()を実行するようにしています。
        // スクロールを無効化
        $('html').css({
            overflow: 'hidden'
        });
        document.addEventListener(
            'touchmove',
            scrollControll,
            {
                passive: false
            }
        );

        // スクロールを有効化
        $('html').css({
            overflow: ''
        });
        document.removeEventListener(
            'touchmove',
            scrollControll,
            {
                passive: false
            }
        );

    // add/removeEventListenerでアロー関数として定義するとイベントが削除されない
    var scrollControll = function(event) {
        if ($(event.target).closest('.js-can-scroll').length > 0) {
            event.stopPropagation();
        } else {
            event.preventDefault();
        }
    };

参考: iOS でページ全体はスクロールを無効にし、個別の要素(textarea など)では有効にする方法 - Qiita

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

GASのデバッグモードで、連想配列に空欄があると、デバッガーにはその要素が表示されないメモ

連想配列にあるべきキーと要素(空欄)が確認できない???

GASのデバッグモードでブレークポイントを使って各変数の状態を確認するなんてことはよくあると思いますが
連想配列にセットした値が空欄('')のような場合は、プロパティエリアには表示されなかったのでメモします。

test.gs
  var list = {'mango':100, 'orange':200, 'apple':'', 'pineapple':'pen'};
  var breakPoint = 1; //←ここでブレイクポイントを打つ

デバッグモードで実行した結果、以下のようにappleは表示されない。
image.png

他のIDEもこれが普通?大層な開発環境使ってないのでわかりませんが
これだけのことで2時間ぐらいハマりました…

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

How to print Qiita document

Run the code in your browser console.

Windows: Ctrl+Shift+I

Mac: Cmd+Opt+I

function remove_ele(selector){
  try {
    $(selector).remove();
  }catch(error) {}
}


$('body').append($('.p-items_article'))

do {
  var e=$('body>*');
  if(e.className.indexOf('p-items_article')==-1){
    e.remove();
  }else{
    e.OK = 1;
  }
} while (e.OK != 1)

remove_ele('.u-flex-center-between');
remove_ele('.ai-Container');
remove_ele('.it-Footer');
remove_ele('.it-DeprecationAlert_one');

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

How to print Qiita and Zhihu document

Run the code in your browser console.

Windows: Ctrl+Shift+I

Mac: Cmd+Opt+I

/* for Qiita */

function remove_ele(selector){
  try {
    $(selector).remove();
  }catch(error) {}
}


$('body').append($('.p-items_article'))

do {
  var e=$('body>*');
  if(e.className.indexOf('p-items_article')==-1){
    e.remove();
  }else{
    e.OK = 1;
  }
} while (e.OK != 1)

remove_ele('.u-flex-center-between');
remove_ele('.ai-Container');
remove_ele('.it-Footer');
remove_ele('.it-DeprecationAlert_one');

window.print();
/* for Zhihu */

function remove_ele(selector){
  try {
    $(selector).remove();
  }catch(error) {}
}


$('body').append($('article.Post-Main'))

do {
  var e=$('body>*');
  if(e.className.indexOf('Post-Main')==-1){
    e.remove();
  }else{
    e.OK = 1;
  }
} while (e.OK != 1)

remove_ele('.Sticky');
remove_ele('.Post-topicsAndReviewer');
remove_ele('.Post-Author');

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

Node.js と Babel で ES6

なんか、いつも、Node.js で、ちょっとコード書いて試したり、勉強がてらコード書いたりするときに、 ES6なコード書きたい時どうすんだっけ? と悩んでしまうので、ここにメモしておきます。

実際に確認した時のそれぞれのバージョンは以下になります。

$ node --version
v12.14.1
$ npm --version
6.13.4
$ npx --version
6.13.4

あと、このメモ作成時にインストールされる Babel 関連パッケージのバージョンは、以下の通り。

$ grep babel package.json
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.8.4",
    "@babel/node": "^7.8.4",
    "@babel/preset-env": "^7.8.4"

作業用のディレクトリを作る

mkdir work

npm init を実行

npm init -y

babel 関連をインストール

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

.babelrc を追加

{
  "presets": ["@babel/preset-env"]
}

スクリプトを実行するには

index.js を実行するには以下のようにする。

npx babel-node index.js

最後に自分に向かって一言

これ、シェルスクリプトにしとけば良いよね? > 自分

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

Material-UI Pickers×Moment.jsをReact(TypeScript)で使う際のtips

業務でMaterial-UI PickersをMoment.jsで使いたいと思った際に
マテリアルUIのサイトはあてにならなかったので参考になればと思います。

環境/パッケージのversion

react 16.12.0
typescript 3.7.3
moment 2.24.0
@material-ui/pickers 3.2.10
@date-io/moment 1.3.13

material-ui-pickers v3ではdate-io系のversionは1.x.xを使わないとうまくいかないみたいなので1.3.13いれてます。
最初これよく読まなかったので、エラーがでまくりで死にそうでした
Material-UI Pickersサイト

実装

実装の詳細について書いていきます。

全体

これで一つのコンポネントです

import React, { FC } from 'react'
import MomentUtils from '@date-io/moment';
import {
  MuiPickersUtilsProvider,
  KeyboardTimePicker,
  KeyboardDatePicker,
} from '@material-ui/pickers'
import moment, { Moment } from 'moment'

const DatePicker: FC = () => {
  const [selectedDate, setSelectedDate] = React.useState<Moment | null>(
    moment()
  );

  const handleDateChange = (date: Moment | null) => {
    setSelectedDate(date)
  };

  return (
    <MuiPickersUtilsProvider utils={MomentUtils}>
      <KeyboardDatePicker
        disableToolbar
        variant="inline"
        format="YYYY/MM/DD"
        value={selectedDate}
        onChange={handleDateChange}
      />
      <KeyboardTimePicker
        value={selectedDate}
        onChange={handleDateChange}
      />
    </MuiPickersUtilsProvider>
  )
}

export default DatePicker

import

import React, { FC } from 'react'
import MomentUtils from '@date-io/moment'
import {
  MuiPickersUtilsProvider,
  KeyboardTimePicker,
  KeyboardDatePicker,
} from '@material-ui/pickers'
import moment, { Moment } from 'moment'
  • React, { FC } FunctionCompornentをいれてます。
  • MomentUtils アダプターです。こちらいれないとMomentのオブジェクトを日付と認識してくれません。
  • MuiPickersUtilsProviderこいつでかこってあげないとMomentUtilsを反映できません。
  • KeyboardTimePicker KeyboardDatePicker keybordでも日付を操作したいので。
  • moment これがないと日時を簡単に扱えません。
  • Moment momentの型interfaceです。typescriptでは定義してあげないと怒られます。

state

stateで行き交うデータは全てMoment型なのを注意してください。

const [selectedDate, setSelectedDate] = React.useState<Moment | null>(
  moment()
);

初期stateにmoment()で現在時刻をsetします。
useState<Moment | null>で型をMoment型にしてあげないとエラーがでます。

const handleDateChange = (date: Moment | null) => {
  setSelectedDate(date)
};

メゾットに関してもpickerから引数で渡されるdateの型はMomentなので定義してあげましょう。

render

return (
    <MuiPickersUtilsProvider utils={MomentUtils}>
      <KeyboardDatePicker
        disableToolbar
        variant="inline"
        format="YYYY/MM/DD"
        value={selectedDate}
        onChange={handleDateChange}
      />
      <KeyboardTimePicker
        value={selectedDate}
        onChange={handleDateChange}
      />
    </MuiPickersUtilsProvider>
  )

<MuiPickersUtilsProvider utils={MomentUtils}> これで囲わないと正しくMomentに変換できずにエラーがでます。utils内にアダプタ名を記述します。

ここで地味につまずいたのはformatで、全部大文字にしないと正しく表示されないので大文字で。
多分Momentが大文字しか認識しないのかと。

まとめ

@date-ioのversionミスしたりstateの型をDateでやってしまったりとなかなかつまずいたので、参考になれば幸いです。
Muiは日本語のtipsが少ないからハマったら長い。。。
改善、修正点ございましたら気軽にコメントくださいませ。

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

Axiosで特定のヘッダしか取れない場合の対処法(response.headersにlocation等が入らない場合)

概要

Axios使用時に、response.headerscontent-type 等、特定のヘッダしか取れない場合にそれ以外をとれるようにする。

対応

サーバのレスポンスヘッダに↓を追加する。

Access-Control-Expose-Headers: *

参考

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

【初学者実践録】分数を計算するJSに挑んだお話

こんにちは。ばーんです。

今回はJSで分数の計算をしていきます!

はじめに

出会いは唐突でした…(↓ドンッ)

これは仕方ない…もぅめっちゃ楽しそうやん‼️

前回のズンドコキヨシの記事書いてまもなくのツイートで、やりたくてうずうずしてましたw)

思い立ったらやらずにいらRenta!

前回の振り返り

そもそもこういう時にどのような思考回路で進めたらいいのか?の答えをメンターと擦り合わせてました。それがこちら↓

1. 仕様を理解する

2. 構成要素を分解する

3. コードのメインの処理の流れを書く(完全に実装するのではなく、コメントと関数名で全体の流れがわかれば良い)

4. 実際に動くコードにする

5. リファクタリングする

なんとなくわかる気がする。なんとなく(知らんけど)

それじゃこれも踏まえてやってみますか!

仕様把握〜構成要素の分解

今回少し難しいのが、問題文の捉え方かなって第一印象で感じました。
例えば4/12(十二分の四)を認めちゃうと相当な種類のパターンが出てくるので。
ですので、切り分けて考えます。

1、分数を計算するJS
2、計算結果を判定して=1ならtrue
3、数字を当てはめていき全部のOKパターンを出すJS
(もしくは、数値を打ち込んで真偽値を返す)

そして、1からやっていきますがここでも思いつくのが何通りか。

A、純粋に分数を計算する(簡単なもので試す)
B、小数点で乗り切る

Bは今回の例だと98/99 = 1はダメで、1/3 + 1/3 + 1/3 = 1はOKなので、
98 ÷ 99 = 0.9898~ なので0.99~1ならOKということであればいける!(というか多分一瞬)

ただまぁB面白くなさそうなのでAの純粋に分数で計算にします。

1、分数を計算するJS

「JS 分数」っと……まぁないですね(ライブラリあったけど本筋から逸れるので却下)

というか分数の計算ってどうすんだっけ?w
いや、簡単な計算はできるんすよ? 1/2 + 1/2 = 1 とかは流石に。
ただ約分はともかく通分のアルゴリズムを覚えていないので検索します。
(約分は分子分母を共通の1〜2桁素数で割っていって、割れなくなったらループ抜けでいけるはず)

ん〜ん〜また出てきたな…最初公倍数とかすだれ算とか。

もっと原始的な方法でできないかな…

と模索してたらできました。
21334.jpg
ぼくがかんがえたさいきょうのあるごりずむ!

はいこれをコードに起こしていきまーす。

コードのメインの処理の流れを書く

まずは、ざっくりの大枠を作っていきます。

// molecule分子,denominator分母。数値は後でランダムに変更
const molecule1 = 2;
const molecule2 = 3;
const molecule3 = 1;
const denominator1 = 5;
const denominator2 = 5;
const denominator3 = 1;

// 関数を定義する。通分のアルゴリズム。
let tsubun = (moleculeAnswer,denominatorAnswer) => {

}
console.log(tsubun);

// 後で使う。ループ回すよう
while(moleculeAnswer == denominatorAnswer){

}

ざっくりこんな感じのはず。まずは通分を動かしてみましょー!

実際に動くコードにする

最初に動くコード書こうとしますが…

// // 関数を定義する。通分のアルゴリズム。
let tsubun = (moleculeAnswer,denominatorAnswer) => {
  moleculeAnswer = (molecule1 * denominator2) + 
  (molecule2 * denominator1);
  denominatorAnswer = denominator1 * denominator2;
}
console.log(tsubun[1,2]);

あーダメダメ。なんか違うのはわかる。

というかあれですね。基礎を思い出さないと。まずは、超簡単なコードを出力させます。

console.log(molecule2);

そうそうそう。こういうのでいいんすよ。

よし。心が潤った。まずは細かく分けます(ドンっ)

function moleculeCalculator(){
  console.log((molecule1 * denominator2) + (molecule2 * denominator1));
}

moleculeCalculator();

よき。これで分子を計算する関数はできたので、同じ容量で分母も(アロー関数についでに書き換え)。

let moleculeCalculator = () => {
  console.log((molecule1 * denominator2) +
  (molecule2 * denominator1));
}

let denominatorCalculator = () => {
  console.log(denominator1 * denominator2);
}

moleculeCalculator();
denominatorCalculator();

よし。これで分子と分母は計算できましたね。あとは、
1、この値を比較する(テスト)
2、正常に動けばループに組み込む

1、この値を比較する(テスト)

簡単そうに見えたんですが…

// 実装コード
if(moleculeCalculator() - denominatorCalculator() == 0){
  console.log("OK");
} else {
  console.log("NG");
}

// 動作確認
if(10 - 9 == 0){
  console.log("OK");
} else {
  console.log("NG");
}

あってそうなんですが上のコードだとチェックできない…
あれ?ifって計算結果で比較できないっけ?と思って下のコード書いたらこっちは正常に動いてます。
つまり数値の計算結果は判断してくれます。が、上のコードだと数値は返ってきてないということになります。

んー戻り値はifで比較できないのか…
とここで過去学んだこと振り返っていると、テストコードのお話が出てきました。
https://qiita.com/ysktsuna/items/6b8b824e444030070754)

引数ならワンチャン…?と思って試すと…

// molecule分子,denominator分母。数値は後でランダムに変更
const molecule1 = 2;
const molecule2 = 3;
const molecule3 = 1;
const denominator1 = 5;
const denominator2 = 5;
const denominator3 = 1;

// 関数を定義する。通分のアルゴリズム。
let moleculeCalculator = (molecule1,molecule2,denominator1,denominator2) => {
  return ((molecule1 * denominator2) +
  (molecule2 * denominator1));
}

let denominatorCalculator = (denominator1,denominator2) => {
  return (denominator1 * denominator2);
}

// 実装コード
if(moleculeCalculator(molecule1,molecule2,denominator1,denominator2) - denominatorCalculator(denominator1,denominator2) === 0){
  console.log("OK");
} else {
  console.log("NG");
}

できた!

2、ループを作る

ここで最初の方に悩んでた、「約分の場合は」とか、「叩いた数値を判定するのか?」を決めました。
ランダムに数値を出力していき、正解の時に止まるでいこうかなと思います。
※問題の解釈間違えてたらすいません

ですので、前回同様ランダムの値を入れていきます。参考はこちら

そして、↑で書いたコードの正解・不正解の出力を分かりやすく修正したのがこちら↓
Screen Shot 2020-02-10 at 13.43.29.png

ランダムもうまく書けたので、後は
・全部の数値をランダムに(分子は一桁、分母は二桁)
・3つの分数にアルゴリズムを書き換える
●最後はループの中に入れる

で完成のはず!

上2つはパパっといけました(ドヤっ)

ループがちょっと躓きました…(1hぐらい)
前回で学習したので、ちょっとイキって「while」使おうとしたら…

つまったToT

言ってても仕方ないのでズンドコの時に使ったループを当てはめると…

for(;;){
  const molecule1 = Math.floor( Math.random() * 8 ) + 1;
  const molecule2 = Math.floor( Math.random() * 8 ) + 1;
  const molecule3 = Math.floor( Math.random() * 8 ) + 1;
  const denominator1 = Math.floor( Math.random() * 88 ) + 10;
  const denominator2 = Math.floor( Math.random() * 88 ) + 10;
  const denominator3 = Math.floor( Math.random() * 88 ) + 10;

  // 関数を定義する。通分のアルゴリズム。
  let moleculeCalculator = (molecule1,molecule2,molecule3,denominator1,denominator2,denominator3) => {
    return ((molecule1 * denominator2 * denominator3) +
    (molecule2 * denominator1 * denominator3)
    +(molecule3 * denominator1 * denominator2));
  }

  let denominatorCalculator = (denominator1,denominator2,denominator3) => {
    return (denominator1 * denominator2 * denominator3);
  }

  if(moleculeCalculator(molecule1,molecule2,molecule3,denominator1,denominator2,denominator3) - denominatorCalculator(denominator1,denominator2,denominator3) == 0){
    console.log("正解!" + denominator1 + "分の" + molecule1 + "たす"
    + denominator2 + "分の" + molecule2 + "たす"
    + denominator3 + "分の" + molecule3 + "は「1」です!");
    break;
  }
  }

(一応)できた!

リファクタリングする

※先に言います。リファクタリングはできませんでしたToT

ここでいろいろ悩んだのですが、リファクタリングどうしていいのかさっぱり分からん…
このままだと処理落ちしそうなので、メンターとわくわく会にご教授いただきました。以下がその内容。

・まず、関数化しよう。処理の流れが見えづらい。
・分母と分子を持つclassを定義したらいいと思う。
・letとconstの使い方調べた方がいい。
・これそもそも「1〜9の数字被りなしじゃない?」
・moleculeは化学の方の分子です。分数の方はnumerator

and more...

ちょっとまって溺れる(というかFBの海に沈んだ)

ということであれですね。そもそも仕様の理解が間違ってましたねToT

そして、その後画面共有しながら実際に解説付きでコード書いて頂きました。
(こんな神イベント無料で行ってるわくわく会に感謝です!)

ちなみに、正解の一例はこちら↓
https://codesandbox.io/embed/thirsty-ptolemy-quc1h?codemirror=1)
割り算なので小数点出ますが、割り切れるんや…

さいごに

最後までやり切りたかったですが、自身の都合もあるのでここで投稿させて頂きます。
今回の学びは、
・ズンドコの時と違い2hでできた!(ズンドコは10hぐらいw)
・アルゴリズム考えるのがすごく楽しい
・class(オブジェクト指向)が全然理解できていない

という点でした。

なので、次回JSに取り掛かる時はclass(オブジェクト指向)を使ってやっていきたいと思います!

最後までご覧いただきありがとうございました!

ばーんm_ _m

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

JavaScriptのキーボードイベント、キー判定にどれつかう?

概要

ブラウザxJavaScriptで扱うキーボードイベント、キー判定にどのプロパティを使うかのまとめ です

ざっくりいうと event.key または event.code のどちらかを使うべし

  • 「●●が押されたら」のような判定をしたいときは event.key または event.code のどちらかで判定する。
  • キー判定には、いろんなプロパティがあって、どれ使ったらいいの?と悩むことがある
  • 以前ふつうにつかっていた event.keyCode等はdeprecatedになっている
キー取得方法 ステータス 概要 参考
event.key オススメ
(正確にはWD)
キー属性値≒入力された文字 を取得する
・"5"を押したらフルキーボードの5でもNumPadの5でも"5"
・ロケールやシステムレベルキーマップの影響を受ける<br>
W3C
event.code オススメ
(正確にはWD)
物理キーを取得する。
・5を押した場合、フルキーボードの5(Digit5)とNumPadの5(NumPad5)は区別される
・上位のキーマップの影響は受けない。
・IEは対象外
W3C
event.keyCode Deprecated
(今後は使わん)
キーコードを数値で取得する
・ロケールやシステムレベルキーマップの影響を受ける
MDN
event.which Deprecated
(今後は使わん)
キーコードを数値で取得する
・event.keyCodeと同じ
MDN
event.keyIdentifier Deprecated
(今後は使わん)
"key identifier"文字列を取得する
・標準ではない→同様の機能をもつevent.keyを使う
MDN
event.charCode Deprecated
(今後は使わん)
キーコード値(unicode)を取得する
・標準ではない
MDN

修飾キー

キー状態取得方法 概要
event.ctrlKey boolean ctrl(command)キーが押されているか否か
event.shiftKey boolean shiftキーが押されているか否か
event.altKey boolean altキーが押されているか否か
event.metaKey boolean metaキーが押されているか否か

サンプルコード

Ctrl+Vを検出するサンプル

document.body.addEventListener('keydown',
    event => {
        if (event.key === 'v' && event.ctrlKey) {
           alert("Ctrl+Vが押されました")
        }
    });

See the Pen qBdOLjw by Tom Misawa (@riversun) on CodePen.

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

Sora JavaScript SDKを使ってみた

はじめに

時雨堂さんのSoraを使ったライブ配信を試しました。
(ウソ穴という個人開発のシステムで使えるか試してみたかった→動いた!)

今回は、ライブ配信するところまでを紹介します。

参考サイト

こちらのサイトを参考にさせて頂きました。

環境

  • Webサーバー
    • Windows10 / Nodejs v12.13.1 (オレオレ証明書あり)
  • 映像配信の端末
    • Windows10
    • iOS 13.3.1
  • 映像受信の端末
    • Windows10

作りかた

では、作っていきます。

1.Sora Laboにログイン

サイト(https://sora-labo.shiguredo.jp/)にアクセスしアカウントを登録し、以下3つの情報を取得します。

  • シグナリング URL(wss://ではじまるもの)
  • シグナリングキー(signaling_key)
  • channelId ※ {{github-id}}@sora-labo

2.sora.min.jsを取得

git bash で取得します

$ curl -OL https://raw.githubusercontent.com/shiguredo/sora-js-sdk/master/dist/sora.min.js

3.配信/受信サイトを用意

こちらのサイトからupstream.htmldownstream.htmlを取得する。channelIdとsignaling_keyを自分のものに修正します。

  • 配信サイト
    • 例: https://{{WebサーバーのIPアドレス}}:{{ポート番号}}/upstream.html
  • 受信サイト
    • 例: https://{{WebサーバーのIPアドレス}}:{{ポート番号}}/downstream.html

これで完成です。

動作確認

  • 配信端末で、配信サイトhttps://{{WebサーバーのIPアドレス}}:{{ポート番号}}/upstream.htmlを開く
  • 受信端末で受信サイトhttps://{{WebサーバーのIPアドレス}}:{{ポート番号}}/downstream.htmlを開く

配信端末のカメラ映像を受信端末で表示できたら成功です。
配信端末がiPhoneの場合は、配信開始ボタンをタップすると配信が開始されます。

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

ジェネレーターを簡易的にCPS変換してみた

ジェネレーターをモナド用の DSL として使いたかったので、簡易的にパースして CPS 変換してみました。

See the Pen CPS Transformation by 七誌 (@7shi) on CodePen.

概要

JavaScript の関数は .toString でソースを文字列として取得できます。

> function test(x) { return x + 1; }
> test.toString()
'function test(x) { return x + 1; }'

ソースの文字列をパースして yield の後の行をコールバックとして切り出すことで、yield を区切りとした CPS 変換となります。(今回は yield しか変換しません)

具体例を示します。見やすいようにインデントを整えています。(実際の変換ではインデントは消えます)

変換前
function* () {
    let x = yield [1, 2];
    let y = yield [3, 4];
    return [x, y];
}
変換後
(function () {
    return bind([1, 2], x => {
        return bind([3, 4], y => {
            return [x, y];
        });
    });
})

今回のパースは手抜きで、スペースを除いた行頭に yield または let 変数 = yield があるケースしか見ていません。

変換前 変換後
yield 式; return bind(式, () => { 次の行以降 });
let 変数 = yield 式; return bind(式, 変数 => { 次の行以降 });

let に複数の変数が列挙されているケースは想定していません。

パース対象の関数は文法的なチェックが済んでいることを当てにしています。

【参考】opol - JavaScriptで演算子オーバーロード

関数としてコードを書くのがオススメ。そうする事でブラウザーが変換前の段階で構文チェックしてくれる。

制限

ブロックの中で yield は使えません。具体的には iffor などです。

ダメな例
  if (cond) {
      yield m1;
  } else {
      yield m2;
  }
  for (let i = 0; i < 10; i++) {
      yield m;
  }

if は三項演算子へ書き換え、for はループの補助関数を用意するなどの対策が必要です。

書き換え例
  yield cond ? m1 : m2;
  yield loop(10, m);

リストモナド

ジェネレーターを DSL として使って、ある種のモナドの動きを模倣できます。ただしこの方法ではリストモナドは模倣できませんでした。

同じ方式でリストモナドを実装できないか考えたのですが、多重ループとなる場合に変数の値を変えて同じコードを何度も実行する必要があり、実現する方法が思いつかずに断念しました。

今回の CPS 変換により bind にコールバックが渡せるようになったため、リストモナドも模倣できるようになりました。

function listMonad(g) {
  function bind(list, f) { return list.flatMap(f); }
  return eval(cpsTransform(bind, g))();
}

let test = listMonad(function*() {
  let x = yield [1, 2];
  let y = yield [3, 4];
  return [x, y];
});
log(test);
実行結果
[1,3,1,4,2,3,2,4]

状態系モナド

ジェネレーターからモナドの変換は関数で行い、値をモナドで包む return(Haskell の意味)は new で表現します。

Haskell JavaScript
test = do
    a <- get
    return a

let test = stateMonad(function*() {
    let a = yield get;
    return new stateMonad(a);
});
関数モナド State モナド
function functionMonad(g) {
  if (this.constructor == functionMonad)
    return () => g;
  function bind(m, f) {
    return state => {
      let value = m(state);
      let r = f(value);
      return r(state);
    };
  }
  let cps = eval(cpsTransform(bind, g));
  return state => cps()(state);
}
function stateMonad(g) {
  if (this.constructor == stateMonad)
    return state => [g, state];
  function bind(m, f) {
    return state => {
      let [value, newState] = m(state);
      let r = f(value);
      return r(newState);
    };
  }
  let cps = eval(cpsTransform(bind, g));
  return state => cps()(state);
}

これらはどちらも状態系モナドとして同じ構造になっています。

  1. bind(m, f) において、モナド m から値(と状態)を取り出す: m(state)
  2. 取り出した値 valuef に引数として渡す: f(value)
    ※ 引数として渡すことを変数への束縛と見なすので bind
  3. f からモナド r が返ってくるので値(と状態)を取り出す: r(state)

return 簡略版

Haskell の return に見た目を似せるため、ジェネレーターの最後の return でモナドに包まない生の値を返せるようにした実装です。

Haskell JavaScript
test = do
    a <- get
    return a

let test = stateMonad(function*() {
    let a = yield get;
    return a;
});

モナドをコンテナとして実装してはいませんが、値と区別するため monad 関数で印を付けます。

関数モナド State モナド
function functionMonad(g) {
  function monad(m) {
    m.monad = functionMonad;
    return m;
  }
  if (this.constructor == functionMonad)
    return monad(() => g);
  function bind(m, f) {
    return monad(state => {
      let value = m(state);
      let r = f(value);
      if (!r.monad) r = new functionMonad(r);
      return r(state);
    });
  }
  let cps = eval(cpsTransform(bind, g));
  return state => cps()(state);
}
function stateMonad(g) {
  function monad(m) {
    m.monad = stateMonad;
    return m;
  }
  if (this.constructor == stateMonad)
    return monad(state => [g, state]);
  function bind(m, f) {
    return monad(state => {
      let [value, newState] = m(state);
      let r = f(value);
      if (!r.monad) r = new stateMonad(r);
      return r(newState);
    });
  }
  let cps = eval(cpsTransform(bind, g));
  return state => cps()(state);
}

モナドではなく値が返されたときはモナドで包みます。

      if (!r.monad) r = new functionMonad(r);

包まずに直に返すことも可能です。この方がオーバーヘッドは少ないです。

関数モナド State モナド
      if (!r.monad) return r;
      if (!r.monad) return [r, newState];

状態書き換え版

参考までに state をモナド内のローカル変数として書き換える実装を示します。bindstate をリレーしないため、実装が簡単になっています。値をモナドで包む処理は省略しました。

関数モナド State モナド
function functionMonad(g) {
  let state;
  function bind(m, f) {
    let value;
    value = m(state);
    return f(value);
  }
  let cps = eval(cpsTransform(bind, g));
  return s => {
    state = s;
    return cps();
  };
}
function stateMonad(g) {
  let state;
  function bind(m, f) {
    let value;
    [value, state] = m(state);
    return f(value);
  }
  let cps = eval(cpsTransform(bind, g));
  return s => {
    state = s;
    return [cps(), state];
  };
}

JavaScript の感覚では、ここから始めた方が分かりやすいかもしれません。

補助関数

State モナド用のループの補助関数の例です。

let loop = (n, m) => state => {
  if (n < 1) return [, state];
  let [_, newState] = m(state);
  return loop(n - 1, m)(newState);
};

トランスパイルするとキャプチャした n,m への参照が失われるため、内部構造を直接扱っています。

参考

出力の log() は次の実装を使っています。

今回の CPS 変換は、Haskell での do ブロックから >>= による表記への書き換えに対応します。

こちらのブログにちょうどその話題が言及されていました。

こうして変換していく過程を見ていると、ほとんどモナドのdo記法から>>=を使った記法への変換と同じであることが分かりますね。

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

Vue Composition API を使ってリアクティブに値を更新する方法

書かないこと

  • Vue Composition APIの導入方法
  • TypeScriptではなく、Vueのみ
  • refとreactiveの使い分けについて

Vue3.0より実装予定の Composition API を使ってstateの値を更新します。

setup関数内で更新する。

index.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click='increment'>+</button>
  </div>
</template>

<script>
  import { ref } from '@vue/composition-api'
  export default {
    setup(){
      const count = ref(0)

      function increment () {
        count.value++
      },
      return{
       increment,
       count
      }
    }
  }
</script>

公式にもある通りですが、シンプルですね。
Vueで値の更新を検知するにはrefを使う必要があります。

Composition Functionで切り出した時の更新

index.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click='increment'>+</button>
  </div>
</template>

<script>
  import { ref } from '@vue/composition-api'
  const useIncrement = () => {
    const count = ref(0)

    const increment = () => {
      count.value++
    }
    return {
      increment,
      count
    }
  }
}

  export default {
    setup(){
      const {increment, count } = useIncrement()
      return{
       increment,
       count
      }
    }
  }
</script>

従来のVueではdataやmethodごとに記述箇所が決まっていましたが、
Composition APIを使うことで関数の切り出しが可能になりました。

setup内のstateを更新する。

index.vue
<template>
  <div>
    <p>{{ state.count }}</p>
    <button @click='increment'>+</button>
  </div>
</template>

<script>
  import { ref } from '@vue/composition-api'
  const useIncrement = (state) => {
    const increment = () => {
      state.count++
    }
    return {
      increment
    }
  }
}

  export default {
    setup(){
    const state = reactive({
      count: 0
    })
      const {increment } = useIncrement(state)
      return{
       state,
       increment
      }
    }
  }
</script>

Composition Functionにstateを渡して更新すればOK

VueComposition APIはさわり始めたばかりですが、柔軟コードが書けそうな反面難しそうですね。

参考資料
Vue Composition APIのコラムっぽいもの集

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

GoogleのCloud Text-to-Speechを使ってDiscordの読み上げbotをサクっと作った

Discordのメッセージ読み上げbot

Discordのボイスチャットで、特定のチャンネル内のメッセージを自動で読み上げてくれるbotを作りました。

Discordの読み上げbotとしては喋太郎という有名なものが既に存在しますが、自分で作ってみたくなったので作りました。

GitHub

https://github.com/kotofurumiya/helmholtz

作った背景

作ってみたくなったからです。以上。

というのも味気ないのでちゃんとした話をします。

Discordのボイスチャットは便利ですが、どうしても「今の時間帯は声出せない」とか「飯食ってる」とかでマイクをミュートにせざるをえない人が発生します。あるいは単純に「聞き専」な人も。

そういった人たちとコミュニケーションをとろうとするとなかなか難しく、「聞き専」用のテキストチャンネルを作り、そこにメッセージを書き込んでもらうことでなんとか双方向のやりとりを成り立たせていました。しかし会話に熱中にしていると聞き専チャンネルへの書き込みにどうしても気づけなかったり、ゲームの対戦中なんかだとメッセージが来たのがわかっていてもチラ見するのも難しかったりします。

そんな中で「聞き専チャンネルに書き込まれたメッセージを読み上げるbotがあればいいんじゃね?」と思いついたのがきっかけです。テキスト読み上げがあればメッセージが来たのに気がつくし、チャット欄に目線を動かさなくても何を言っているのか把握しやすくなります。読み上げbotさえあれば全部解決するんだろうなーと思い作り始めました。

名前の「ヘルムホルツ」はグラブルの武器から取りました。なんか音出しそうな感じの武器の名前ないかな〜と探してたらヘルムホルツが目についたので採用しました。

なお喋太郎のことを知ったのは作り終わってからです。まあいい勉強になったので良しとします。

機能

基本的な機能としてはテキスト読み上げしかありません。あとは自動入室・自動退室周りの機能ぐらいです。

  • マイクミュートの人が発言するとそのボイスチャットチャンネルに自動参加
    • 指定された特定のテキストチャンネルのみ対応
  • マイクミュートの人の発言を自動で読み上げてくれる
  • ボイスチャットに誰もいなくなったら自動退室

思想としては「ユーザは一切の操作不要。勝手に動く」を中心としています。

技術

使った技術自体はシンプルです。

私にしては珍しくTypeScriptを採用せず生のJavaScriptを115行ゴリゴリ書いて動かしてます。中身もだいぶ雑です。

Discordの制御に関してはdiscord.jsという便利なライブラリがあったので使わせていただきました。テキスト読み上げ音声はCloud Text-to-Speechを使用しています。デプロイはDockerでコンテナ化してContainer Registoryにpushし、GCEにデプロイしています。ずっと動き続けるタイプのbotなのでGCEを選びました。

discord.js

discord.jsはDiscordを簡単に操作するためのライブラリです。チャンネルやメッセージの操作、ボイスチャットへの参加や発話などができます。DiscordのAPIについて調べなくてもbotを動かすことができたので、大変助かりました。

https://discord.js.org/

困った点としては、discord.jsの音声再生周りが新しめのNode.jsと相性がよろしくないらしく、音声が途中で途切れるという問題が発生しました。結局、音声再生問題の出ないNode.js v8までバージョンを下げることになったのですが、サポート期間とか考えるとだいぶ不安です。

Cloud Text-to-Speech

GoogleのCloud Text-to-Speechはテキストから音声合成を提供してくれるサービスです。Speech-to-Text(音声から文字起こし)の逆ですね。

https://cloud.google.com/text-to-speech?hl=ja

1ヶ月あたり400万文字まで無料で使えるので、ちょっとした使い方なら無料枠をオーバーすることはありません。ガンガン使っていけます。ただし他のText-to-Speechサービスと同じく日本語はあまり流暢ではなく、少し機械的な感じになってしまいます。そこは我慢しましょう。

制作期間

ライブラリが大体揃っていたので1日ぐらいでできました。先述の音声周りのバグには苦しめられましたが、あとはそんなに詰まるポイント無かったです。

実際に導入してみて

このbotを導入してから2ヶ月ほど経ちましたが、メンバーからの評判は良いです。今まで断絶が起こっていた聞き専に近い人たちとの交流も活発になりました。喋れない/喋りたくないという人でもボイスチャットに参加できるようになって、だいぶ賑やかになりました。

喋太郎を導入しても同様の効果は得られたとは思いますが、やはり自分で作るのは楽しかったです。皆さんも「車輪の再発明」とか気にせず作りたいものをガンガン作っていけばいいと思います。その方がきっと楽しいです。

さいごに

Discordって機能複雑だしボイチャ周りとかややこしそ〜というイメージがありますが、今は各種ライブラリが整備されているので簡単に作ることができます。発想次第で色々できると思うので、bot作りに手を出してみてはどうでしょう。

こうやって日常生活の片手間に小さな問題を解決していけると、とても楽しいです。このbotは規模としては非常に小さなものですが、ちょっとだけでも誰かの役に立てているという実感はとても大きく響いてきます。

実は手を出していないだけで、小さな労力で自分が活躍できる領域というのはきっといろんな場所に散らばっていると思うので、そういう細かな課題をどんどん見つけてどんどん解決していきましょう。

それでは、プログラミング楽しんでいきましょう〜。

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

React基本

JSX

FaceBookが開発したjavascript拡張機能。HTMLタグをjavascriptの中に書ける。実際はBabelを使ってHTMLの部分をjavascriptのプログラムに置き換えている

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>

  <body>
    <div id="app"></div>
    <script src="app.js"></script>
  </body>
</html>
// jsxを使用できるようにreactのモジュールをimport
import React from "react";
import ReactDOM from "react-dom";

// 描画関数のReactDom.renderにh1タグを渡して'app'に描画する
const app = document.getElementById('app');
ReactDOM.render(<h1> Hello </h1>, app);

注意点

  1. コンポーネント名は大文字から始める
  2. class属性は「className」と書く
  3. returnで戻すタグは一つ。複数返したい場合はdivとかでくくる
  4. 閉じタグがない場合はエラー
  5. 属性値は""でくくる。テンプレート文字列(``)は指定できない
  6. {}でくくるとjavascriptが使用できる
import React from "react";
import ReactDOM from "react-dom";

// 関数コンポーネント。大文字から始める
const App = () =>{
  // class属性はclassName
  // 属性値は””でくくる
  // 戻すタグはdivなどで一つにまとめる
  return (
    <div>
      <h1 className="greeting"> Hello </h1>
    </div>
  )
}

const app = document.getElementById('app');
ReactDOM.render(<App>, app);

コンポーネント

独立した再利用可能な部品として分けられたもの

関数コンポーネント

// JSXの項で描いたやつ
const App = () =>{
  return (
    <div>
      <h1 className="greeting"> Hello </h1>
    </div>
  )
}

クラスコンポーネント

// importしたReactモジュールのComponentを継承する
// renderメソッドに描画したい内容を定義する
class App extends React.Component {
  render(){
    return(
      <div>
        <h1 className="greeting"> Hello </h1>
      </div>
    )
  }
}

renderメソッドについて

renderは以下のタイミングでReactが自動的に呼び出す

  • javascriptがブラウザにロードされた直後
  • コンポーネントのpropsが変更された時
  • コンポーネント内でsetState()メソッドを実行してstateが変更された時

※setState()を複数回呼んだからといって毎回render呼び出しされるわけではないみたい

setStateを複数回実行してもrenderは1回しか呼ばれない
【翻訳記事】関数型setStateはReactの未来だ

State

コンポーネントが保持する状態、値のこと

注意点

  • stateはthis.stateというインスタンス変数に格納される
  • stateの更新は必ずthis.setState()メソッドを使用する
  • this.setState()はrender()メソッドで呼び出してはいけない。this.setState()によってrender()が呼び出され再帰呼び出しエラーとなってしまうため
class Human extends React.Component {
  constructor() {
    super();
    // stateの初期処理
    this.state = { name: '' }
  }

  setName(){
    const name = this.getName()

    this.setState({ 
      name: name
    });
  }
   ...

  render(){
    return(
      <div>
        <h1>{this.state.name}</h1>
      </div>
    )
  }
}

Props

コンポーネントに渡されるパラメータのこと

class Human extends React.Component {
   ...

  render(){
    return(
      // Greetingコンポーネントのnameにパラメータを渡す
      <Greeting name={this.state.name}>
    )
  }
}

const Greeting = (props) => {
  const greeting = this.currentTimeGreeting()

  return (
    <div>
      <h1>私は{props.name}です{greeting}!</h1>
    </div>
  )
}

参考

公式ドキュメント
setStateを複数回実行してもrenderは1回しか呼ばれない
【翻訳記事】関数型setStateはReactの未来だ

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