20200912のJavaScriptに関する記事は19件です。

見ているページのURLとタイトルをすぐにPukiWikiスタイルの書式に変換するブックマークレットを作った。

表題どおり。pukiwikiのために毎回URLやタイトルをコピペしてコリコリするっていうのがおっくうになったのでブックマークレットを作りました。

e128219b8e2f9242ba053cb240f32f89[1].png

URLをすぐHTMLリンク形式にする~みたいなやつをサクッと改造したやつです。

javascript: (function () { var ele = document.createElement('textarea'); ele.style.width = '100%'; ele.value = '[[' + document.title +'>'+ location.href + ']]'; document.body.insertBefore(ele, document.body.firstChild); ele.focus(); ele.select(); ele.ondblclick = function () { ele.parentNode.removeChild(ele); }; ele.oncopy = function () { setTimeout(function () { ele.parentNode.removeChild(ele); }, 1); return true; }})()

これをコピペしてお気に入り登録のURLのところに打ち込めば使えます。ブックマークバーに置いておくとすぐに使えると思います。

ソースコードは下。自分で改造したい場合はどうぞ。

javascript: (function () {
    var ele = document.createElement('textarea');
    ele.style.width = '100%';
    ele.value = '[[' + document.title + ">" + location.href + ']]';
    document.body.insertBefore(ele, document.body.firstChild);
    ele.focus();
    ele.select();
    ele.ondblclick = function () {
        ele.parentNode.removeChild(ele);
    };
    ele.oncopy = function () {
        setTimeout(function () {
            ele.parentNode.removeChild(ele);
        }, 1);
        return true;
    }
})()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでJavaScript(バニラ)が反応しない。

rails5では初期からjQueryのgemが導入されており、削除しない限りはデフォルトで利用できる状態です。
Railsを学び始めて、いつの間にか素のJavaScript(以降”バニラ”)よりもjQueryを使う方が慣れてしまっていたので、今回は改めてバニラを学習するためあえてjQueryを使わない方法を模索しました。

環境

Ruby 2.5.7
Rails 5.2.4

経緯

改めてバニラでコードを書き始めると割とハマるところが多くありました。
jQueryで書けるならそれでいいと言われればそれまでなのですが、そのままにしておくのは気持ち悪かったので少し向き合うことにしました。

即時関数が効かない

手始めにクリックイベントでコンソールログを確認しようとしました。
jQueryでは下記のように$(function() {処理});
という書き方をする必要があります。(理由は後述します。)

application.js
// jQuery
$(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});

jQueryを使わないバニラの即時関数は下記のような書き方になります。

application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());

上記二つのコードはどちらも処理は同じなのですが、バニラの方はなぜかエラーになり、コンソールログに出力されません。。。

以前はここでjQueryに切り替えていたのですが、どうしても解決したくて今回の模索に至りました。

なぜjQueryは動いて、バニラは動かないのか?

それは$(function() {});に隠されていました。(隠されていません。)
$(function() {});は省略された書き方で、本来の形は下記のようになります。

application.js
$(document).ready(function {
 //処理
});

readyメソッドはHTMLの読み込みが終わったタイミングで中の処理が行われるメソッドです。
つまりjsの処理を全て$(function() {処理});の中に書くことで、何も考えなくても処理を行うことができるということになります。

バニラではこの"htmlの読み込み待ち"をjQueryを使わずに書く必要があるということです。

DOMContentLoaded

バニラではaddEventListenerメソッドのイベントにDOMContentLoadedを指定することで、jQueryのreadyメソッドと同じことができるようになり、コードは以下のようになります。

application.js
window.addEventListener('DOMContentLoaded', function() {
  // 処理
});

バニラでクリックイベントをするには?

ここで最初のjQueryとバニラを比較したコードを振り返ってみます。

application.js
// jQery
$(function() {
  $("セレクタ名").on('click', function() {
    console.log('クリックされました。');
  });
});

このjQueryは省略せずに書くと

application.js
// jQery
$(document).ready(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});

という感じになります。

次にバニラをみていきます。

application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());

これは即時関数なのですが、実はこの書き方でも動作する場合もあります。

方法1.外部jsファイル読み込みのscriptタグをbody要素の一番下に記述する。

通常、Railsでは外部jsファイルの読み込みタグはheadタグの中に書かれていることが多いです。
しかし、headの中でjsファイルを読み込んでしまうと、bodyの中にあるエレメントをとる前にjsの関数が発動してしまうため、エラーになってしまいます。
なので、外部jsファイルの読み込みタグをbodyタグの最後に書く方法です。

application.html.erb
<head>
...
<%#= javascript_include_tag 'application' %>
...
<head>
<body>
...
<%= javascript_include_tag 'application' %>
</body>

こうすることで、bodyが読み込み終わったタイミングでjsファイルが読み込みを開始するので、要素の読み込みもできるということになります。
しかし、そのほかの外部ファイルやサービス、APIの読み込み設定などは全てheadタグ内に書かれるので、できれば外部jsファイルの読み込みもhead内で統一したいところです。

方法2.DOMContentLoadedを使う

先ほども触れましたが、addEventListenerメソッドのDOMContentLoadedイベントを使う方法です。
jsの外部ファイル読み込みタグはhead内のままに、jsファイルの方だけを書き換える必要があります。

application.js
// バニラ
window.addEventListener('DOMContentLoaded', function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
});

これでHTMLが読み込まれてからfunctionが実行される形になったので、問題なく動作するようになりました。

注意

ちなみに、DOMContentLoadedイベントはHTMLの読み込み完了を待つだけなので、cssや画像ファイルの読み込みは含まれていません。
画像やcssの操作がある場合はaddEventListenerメソッドのloadイベントを使えば、全ての読み込みが完了してからfunctionを発火させることができます。

まとめ

ここまで散々バニラで記述する方法をお伝えしてきましたが、jQueryと相性がいいならjQueryのままでも良いと、個人的には思います。
実際バニラを書いてみて、jQueryならどれほど簡単に書くことができるのかがわかったので・・・笑
これはRailsにも標準搭載されているという観点からも推察できます。

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

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

参考サイト

JavaScriptで即時関数を使う理由
【jQuery】$(function() {...}) について 「意味や実行されるタイミング」
DOMContentLoadedイベントとloadイベントの違い[タイミング]

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

Geolocation APIで現在位置を取得する

はじめに

Geolocation APIを使って現在位置を取得する方法の簡単なまとめです。

概要

  • Geolocation APIの概要
  • 実際に現在位置を取得する

Geolocation API

Geolocation APIとは、ユーザの位置情報を取得するためのAPIです。
GPS、IPアドレス、無線LAN、WiFiなどから位置情報を取得することができます。
位置情報を取得するためには、ユーザの許可が必要です。

Geolocation APIのオブジェクト

Geolocation APIのGeolocationオブジェクトはデバイスの位置を取得する機能を提供します
Geolocationオブジェクトを取得するには、Navigatorオブジェクトのgeolocationプロパティを使用します。
Geolocationオブジェクトのメソッドは以下です。

メソッド 説明
getCurrentPosition() デバイスの現在位置を取得する
watchPosition() デバイスの位置情報を監視する
clearWatch() watchPosition()メソッドで取得中のデバイスの監視状況をクリアする


getCurrentPosition()watchPosition()では、引数としてコールバック関数を指定できます。
指定できるコールバック関数は以下です。

コールバック関数 説明
PositionCallback 位置情報の取得が成功したときに呼び出されるコールバック関数
Positionオブジェクトを引数とする
PositionErrorCallback 位置情報の取得が失敗したときに呼び出されるコールバック関数
PositionErrorオブジェクトを引数とする


PositionCallbackの引数となるPositionオブジェクトは現在位置を表します。
Positionオブジェクトのプロパティは以下です。

プロパティ 説明
coords 現在位置情報を持つCoordinatesオブジェクトを取得する
timestamp 位置情報が取得された時間を取得する


Coordinatesオブジェクトから取得できる現在位置情報は以下です。
他にも取得できる情報はありますが、今回は省略します。

プロパティ 説明
latitude 緯度を取得する
longitude 経度を取得する
altitude 高度を取得する

実際に現在位置を取得してみる

Geolocation APIを使って、緯度と経度を取得してみます。
今回は取得した位置情報を画面に表示するだけの簡単な画面を作成しますが、いずれは取得した位置情報を利用して、Mapと連携できるといいなと考えています。

まず、簡単な画面を作成します。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Geolocation Sample</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="center">
        <div class="btn-margin">
            <button id="btn" class="btn btn-outline-primary btn-lg">
                現在位置を取得する
            </button>
        </div>
        <div class="txt-margin">
            <p>緯度:<span id="latitude">???</span><span></span></p>
            <p>経度:<span id="longitude">???</span><span></span></p>
        </div>
    </div>
    <script src="geolocation.js"></script>
</body>
</html>

ブラウザで表示してみるとこのようになります。

次に、ボタンを押した時の処理を書きます。

ボタンがクリックされたときにgetCurrentPosition()メソッドを使用して位置情報を取得します。

navigator.geolocation.getCurrentPosition(successCallback, errorCallback);

取得に成功した場合、successCallbackコールバック関数が呼び出されます。

function successCallback(position){
};

successCallbackの中で、緯度と経度を取得します。
取得した緯度と経度を画面の<span>に表示します。

var latitude = position.coords.latitude;
var longitude = position.coords.longitude;

document.getElementById("latitude").innerHTML = latitude;
document.getElementById("longitude").innerHTML = longitude;

取得に失敗した場合、errorCallbackコールバック関数が呼び出されます。

function errorCallback(error){
};

errorCallbackの中で、画面にアラートを表示します。

alert("位置情報が取得できませんでした");



上記のコードをまとめると、このようになります。

geolocation.js
// ボタンを押した時の処理
document.getElementById("btn").onclick = function(){
    // 位置情報を取得する
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
};

// 取得に成功した場合の処理
function successCallback(position){
    // 緯度を取得し画面に表示
    var latitude = position.coords.latitude;
    document.getElementById("latitude").innerHTML = latitude;
    // 経度を取得し画面に表示
    var longitude = position.coords.longitude;
    document.getElementById("longitude").innerHTML = longitude;
};

// 取得に失敗した場合の処理
function errorCallback(error){
    alert("位置情報が取得できませんでした");
};

これを実際に動かしてみます。

現在位置を取得するボタンを押下すると、

現在位置の情報が画面に表示されています。
取得に失敗した場合は、

このようにアラートが表示されます。

おわりに

Geolocation APIをはじめて使ってみましたが、簡単に位置情報を取得できることにびっくりしました。
簡単なまとめですが、誰かの助けになれば幸いです。

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

【JavaScript】怖がらないで、引数分割束縛(argument destructuring)【Vue.js】

関数({ $axios })とか初見だと、ビビる引数の書き方でてきますよね

その名も引数分割束縛。なんか、恐い言葉です。

これの目的は、何回も記されるコードを因数分解して、省略しちゃおうというのが目的です。

asyncData

contextを省略するパターン

async asyncData(context) {
  const posts = await context.$axios.$get('/posts')
  return { posts }
},

async asyncData({ $axios }) {
  const posts = await $axios.$get('/posts')
  return { posts }
},

contenxtとresを省略するパターン

async asyncData (context) {
  const res = await axios.get('/posts/' + context.params.id)
  return { post: res.data }
}

async asyncData ({ params }) {
  const { data } = await axios.get('/posts/' + params.id)
  return { post: data }
}

Vuexのstore

export const actions = {
  async login (context) {
    await context.commit('switchLogin')
  })
}

export const actions = {
  async login ({ commit }) {
    await commit('switchLogin')
  })
}

つまり、何が起きているか。

まず分割代入の仕組みで、代入

{ a } = { a: 1 }
console.log(a)  // 1

オブジェクトのキーから、代入

let option = { a: 1 }
{ a } = option.a
console.log(a)  // 1

要はこんなことが起きている

export const actions = {
  async login (context) {
    await 〇〇('switchLogin')
  })
}

export const actions = {
  async login ({ commit }) {
    await 〇〇('switchLogin')
  })
}

いずれの場合も、「〇〇」と示せるようにすれば良いから

{ commit } = { commit: 〇〇 }
console.log(commit)  // 〇〇

let context = { commit: 〇〇 }
{ commit } = context.commit
console.log(commit)  // 〇〇
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】怖がらないで、引数分割束縛(argument destructuring)

関数({ $axios })とか初見だと、ビビる引数の書き方でてきますよね

その名も引数分割束縛。なんか、恐い言葉です。

これの目的は、何回も記されるコードを因数分解して、省略しちゃおうというのが目的です。

asyncData

contextを省略するパターン

async asyncData(context) {
  const posts = await context.$axios.$get('/posts')
  return { posts }
},

async asyncData({ $axios }) {
  const posts = await $axios.$get('/posts')
  return { posts }
},

contenxtとresを省略するパターン

async asyncData (context) {
  const res = await axios.get('/posts/' + context.params.id)
  return { post: res.data }
}

async asyncData ({ params }) {
  const { data } = await axios.get('/posts/' + params.id)
  return { post: data }
}

Vuexのstore

export const actions = {
  async login (context) {
    await context.commit('switchLogin')
  })
}

export const actions = {
  async login ({ commit }) {
    await commit('switchLogin')
  })
}

つまり、何が起きているか。

まず分割代入の仕組みで、代入

{ a } = { a: 1 }
console.log(a)  // 1

オブジェクトのキーから、代入

let option = { a: 1 }
{ a } = option.a
console.log(a)  // 1

要はこんなことが起きている

export const actions = {
  async login (context) {
    await 〇〇('switchLogin')
  })
}

export const actions = {
  async login ({ commit }) {
    await 〇〇('switchLogin')
  })
}

いずれの場合も、「〇〇」と示せるようにすれば良いから

{ commit } = { commit: 〇〇 }
console.log(commit)  // 〇〇

let context = { commit: 〇〇 }
{ commit } = context.commit
console.log(commit)  // 〇〇
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Alfred】自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた

消防士時代からQiita投稿を続け、累計投稿数は140記事を超えました。
投稿記事が増えると「あれ?これ前Qiitaに書いた気がするけどなんだっけな..?」となっても自分の過去投稿を探すのが一苦労です。
そんな手間を解消するため、Qiita APIとAlfredを使って自分のQiita記事をインクリメント検索するAflred Workflowを作ってみました。

結構便利なので、Qiitaヘビーユーザーの方々に使ってもらえると嬉しいです。

alfred-my-qiita

こちらのGIF画像のように、自分の投稿記事をインクリメンタル検索できます。
初回はAPIリクエストが走るので少し時間がかかりますが、その後はキャッシュが効くのである程度高速に動作します。

https://github.com/kawamataryo/alfred-my-qiita-post

ezgif.com-gif-maker (1).gif

インストール・環境変数の設定

以下コマンドでnpmからインストールできます(後述するAlfyで実現しています)。
npmインストールするだけでAlfredがWorkflowを認識し、すぐに使えるようになります。

$ npm i -g alfred-my-qiita

また、GitHubのリリース から直接alfred-my-qiita.workflowファイルをダウンロードし、Alfredに取り込むことも可能です。

スクリーンショット 2020-09-12 13.38.36.png

インストールが完了したら、AlfredのWorkflow設定画面から alfred-my-qiita を選択して、環境変数を設定してください。

スクリーンショット 2020-09-12 13.54.18.png

設定する環境変数は以下2つです。

名前
API_TOKEN Qiita APIのAPIトークン。こちらの案内にしたがって取得・設定してください。
USER_NAME Qiitaのユーザー名(@を除く)を設定してください。

スクリーンショット 2020-09-12 11.06.48.png

これで準備は完了です。
Alfredを開き.qiitaと打ち、あとは半角スペースを空けて検索したい文字列を入力すればOKです!!

ezgif.com-gif-maker (1).gif

実装

今回も以前こちらの記事で紹介したAlfy というNode.jsベースでAlfred Workflowを作れるフレームワークを使っています。

Alfyを使う利点は以下の通りです。

  • Node.jsでWorkflowを作れる
  • npmモジュールが使える
  • npmに公開し、手軽にインストールしてもらえるようになる

何より、Bashスクリプト書かなくてよいというのが最高便利です。

https://github.com/sindresorhus/alfy

今回のworkflowは以下2つのファイルで構成されています。

1つはQiita APIから投稿記事を取得する処理です。
最初に自分の投稿総数を取得して、その後Promise.allとmapを使い全投稿データを取得しています。

lib/fetchAllPosts.js
import alfy from "alfy"

export const fetchAllPosts = async (username, token) => {
  const options = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    json: true,
    maxAge: 60000
  };

  const user = await alfy.fetch(
    `https://qiita.com/api/v2/users/${username}`,
    options
  );

  const maxPage = Math.ceil(user.items_count / 100);

  let posts = [];
  await Promise.all(
    [...Array(maxPage)].map(async (_, i) => {
      const res = await alfy.fetch(
        `https://qiita.com/api/v2/authenticated_user/items?page=${
          i + 1
        }&per_page=100`,
        options
      );
      posts = [...posts, ...res];
    })
  );

  return posts
};

そして、index.jsがメインの処理です。AlfyのinputMatches APIを使い投稿記事をフィルタリングして出力しています。

index.js
import alfy from "alfy";
import { fetchAllPosts } from "./lib/fetchAllPosts";

const token = process.env.apiToken;
const username = process.env.username;

const createResponse = (title, subtitle, url) => {
  return {
    title,
    subtitle,
    arg: url,
    icon: {
      type: "png",
      path: "icon.png",
    }
  };
}

(async () => {
  const posts = await fetchAllPosts(username, token);

  if(alfy.input.length > 1) {
    const items = alfy.inputMatches(posts, "title").map(
        (p) => createResponse(p.title, p.body, p.url));

    if (!items.length) {
      alfy.output(
          [createResponse("The requested post was not found. ⚠️", "", "")]);
      return;
    }
    alfy.output(items);
  } else {
    alfy.output([createResponse("Loading...", "", "")])
  }
})()

終わりに

以上「自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた」でした。
完全自分用のツールとして作ってみたものなのですが、Qiita投稿数が多い方には何かと便利だと思うので、使ってもらえると嬉しいです。
(もし動かないとかTwitterGitHub Issue, この記事のコメントで教えてください :pray:

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

自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた

消防士時代からQiita投稿を続け、累計投稿数は140記事を超えました。
投稿記事が増えると「あれ?これ前Qiitaに書いた気がするけどなんだっけな..?」となっても、自分の過去投稿を探すのが一苦労です。
そんな手間を解消するため、Qiita APIとAlfredを使って自分のQiita記事をインクリメント検索するAflred Workflowを作ってみました。

結構便利なので、Qiitaヘビーユーザーの方々に使ってもらえると嬉しいです。

alfred-my-qiita

こちらのGIF画像のように、自分の投稿記事をインクリメンタル検索できます。
初回はAPIリクエストが走るので少し時間がかかりますが、その後はキャッシュが効くのである程度高速に動作します。

https://github.com/kawamataryo/alfred-my-qiita-post

ezgif.com-gif-maker (1).gif

インストール・環境変数の設定

以下コマンドでnpmからインストールできます(後述するAlfyで実現しています)。
npmインストールするだけでAlfredがWorkflowを認識し、すぐに使えるようになります。

$ npm i -g alfred-my-qiita

また、GitHubのリリース から直接alfred-my-qiita.workflowファイルをダウンロードし、Alfredに取り込むことも可能です。

スクリーンショット 2020-09-12 13.38.36.png

インストールが完了したら、AlfredのWorkflow設定画面から alfred-my-qiita を選択して、環境変数を設定してください。

スクリーンショット 2020-09-12 13.54.18.png

設定する環境変数は以下2つです。

名前
API_TOKEN Qiita APIのAPIトークン。こちらの案内にしたがって取得・設定してください。
USER_NAME Qiitaのユーザー名(@を除く)を設定してください。

スクリーンショット 2020-09-12 11.06.48.png

これで準備は完了です。
Alfredを開き.qiitaと打ち、あとは半角スペースを空けて検索したい文字列を入力すればOKです!!

ezgif.com-gif-maker (1).gif

実装

今回も以前こちらの記事で紹介したAlfy というNode.jsベースでAlfred Workflowを作れるフレームワークを使っています。

Alfyを使う利点は以下の通りです。

  • Node.jsでWorkflowを作れる
  • npmモジュールが使える
  • npmに公開し、手軽にインストールしてもらえるようになる

何より、Bashスクリプト書かなくてよいというのが最高便利です。

https://github.com/sindresorhus/alfy

今回のworkflowは以下2つのファイルで構成されています。

1つはQiita APIから投稿記事を取得する処理です。
最初に自分の投稿総数を取得して、その後Promise.allとmapを使い全投稿データを取得しています。

lib/fetchAllPosts.js
import alfy from "alfy"

export const fetchAllPosts = async (username, token) => {
  const options = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    json: true,
    maxAge: 60000
  };

  const user = await alfy.fetch(
    `https://qiita.com/api/v2/users/${username}`,
    options
  );

  const maxPage = Math.ceil(user.items_count / 100);

  let posts = [];
  await Promise.all(
    [...Array(maxPage)].map(async (_, i) => {
      const res = await alfy.fetch(
        `https://qiita.com/api/v2/authenticated_user/items?page=${
          i + 1
        }&per_page=100`,
        options
      );
      posts = [...posts, ...res];
    })
  );

  return posts
};

そして、index.jsがメインの処理です。AlfyのinputMatches APIを使い投稿記事をフィルタリングして出力しています。

index.js
import alfy from "alfy";
import { fetchAllPosts } from "./lib/fetchAllPosts";

const token = process.env.apiToken;
const username = process.env.username;

const createResponse = (title, subtitle, url) => {
  return {
    title,
    subtitle,
    arg: url,
    icon: {
      type: "png",
      path: "icon.png",
    }
  };
}

(async () => {
  const posts = await fetchAllPosts(username, token);

  if(alfy.input.length > 1) {
    const items = alfy.inputMatches(posts, "title").map(
        (p) => createResponse(p.title, p.body, p.url));

    if (!items.length) {
      alfy.output(
          [createResponse("The requested post was not found. ⚠️", "", "")]);
      return;
    }
    alfy.output(items);
  } else {
    alfy.output([createResponse("Loading...", "", "")])
  }
})()

終わりに

以上「自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた」でした。
完全自分用のツールとして作ってみたものなのですが、Qiita投稿数が多い方には何かと便利だと思います。使ってもらえると嬉しいです。
(もし動かない場合は、TwitterGitHub Issue, この記事のコメントで教えてください :pray:

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

packsファイルの.jsに記載したのに反応しない

【概要】

1.結論

2.どのように使うか

3.なぜjsファイルに書き込まなかったのか

4.ここから学んだこと

1.結論

直にhtml.erbファイルで"script"を記述しscript内にjavascript言語を記載する!

2.どのように使うか

任意のhtml.erb内で

****.html.erb
<script>
.
.
.

</script>

と記載してJavascriptを記載するだけです!
注意点としてapplication.html.erbで
<%=yiled%>を使用してヘッダーフッダーを適用している場合は反映させる順番に注意です。
HTMLが読み込まれるよりも前にJavascriptを適用させてしまうとブラウザの検証ツールのconsoleでエラーがでます。


3.なぜjsファイルに書き込まなかったのか

結論からいうと、jsファイルを読み込んでくれなかったり、リロードを一回挟まないと読み込んでくれませんでした。
内容としてはパスワード表示非表示を行っていましたが、検索するとたくさん出てくるのでここでは割愛します。

Rails6.0
Vscode
を使用しています。

app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("./security") #➡︎該当のファイル

と記載し、secrurity.jsにjavascript言語を記載し、

app/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

と記載してあるのでjsファイルが適用されるはずと思いきや全く反映されませんでした。

また、jsファイルにてパスワードの表示非表示をプログラムしていましたが反応がないのでいろいろ参考にしました。下記のプログラムを最初に記載しました。

app/javascript/packs/security.js
document.addEventListener("DOMContentLoaded", function(){}

パスワードの表示非表示はできましたが、一度リロードを押さないと反映がされませんでした。なのでjsファイル問題は解決していませんが、とりあえずやりたいことはできたのでよしとします。(可読性はよろしくないですが)


4.ここから学んだこと

このエラーの中でhtml/css/javascript/の読み込む順番やDOMがどの段階で形成されていくのかが大変勉強になりました。tutbolinksやdocumentloadedについてはまだまだ勉強が足りていないですが、流れだけでも把握できたのはかなり大きいです。scriptにしたとたんすぐに解決したのも、書く順番を把握していたおかげでした。

参考にしたURL
"DOMContentLoaded周りの処理を詳しく調べてみました"
"【JS】addEventListenerが機能しない理由についてご教示ください"
"DOMContentLoadedイベントとloadイベントの違い[タイミング]"

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

react-bootstrap-table2ならDOM要素1つでテーブルを書ける

Reactでテーブルを書くためのパッケージ、react-bootstrap-table2を使ってみたので備忘録です。
react-bootstrap-table2 https://github.com/react-bootstrap-table/react-bootstrap-table2

テーブルの作成

まずはただデータを表示するだけのテーブルを作成します。
BSTable1.PNG

データとカラム定義はそれぞれ配列として用意し、react-bootstrap-table2のpropsに渡すだけです。
このパッケージはその名の通りbootstrapを使用しています。あらかじめbootstrapを導入しておきます。

App.jsx
import React from 'react';
import BootstrapTable from "react-bootstrap-table-next";
import 'react-bootstrap-table-next/dist/react-bootstrap-table2.min.css';
import { Container } from 'reactstrap';

const data = [
  { id: 1, name: "フシギダネ", type: "くさ/どく" },
  { id: 2, name: "フシギソウ", type: "くさ/どく" },
  { id: 3, name: "フシギバナ", type: "くさ/どく" },
  { id: 4, name: "ヒトカゲ", type: "ほのお" },
  { id: 5, name: "リザード", type: "ほのお" },
  { id: 6, name: "リザードン", type: "ほのお/ひこう" },
  { id: 7, name: "ゼニガメ", type: "みず" },
  { id: 8, name: "カメール", type: "みず" },
  { id: 9, name: "カメックス", type: "みず" },
]

const columns = [
  { dataField: "id", text: "ID", sort: true, editable: false },
  { dataField: "name", text: "Name", sort: true, editable: false },
  { dataField: "type", text: "Type", sort: true, editable: false },
]

const App = () => {
  return (
    <Container style={{ width: "600px" }}>
      <BootstrapTable
        data={data}             // データ
        columns={columns}       // カラム定義
        keyField="id"           // キー
        bootstrap4={true}       // Bootstrap4を指定。デフォルトではBootstrap3
        bordered={true}         // 表のボーダー
      />
    </Container>
  );
}
export default App;

データ編集可能にする

編集機能も用意されているので使ってみます。
任意のセルをクリックすると編集モードに切り替わります。
BSTable2-1.png

カラム定義でeditableプロパティを有効にします。
これによりデフォルトでTextボックスによる編集が有効化されます。IDは編集されたくないのでそのままです。

また、今回データ例としているポケモンのタイプは選択肢から選ぶようにしたいと思うので、
editorプロパティで編集モードがSelectボックスになるように指定します。

最後にreact-bootstrap-table2要素のpropsとしてcellEditに
関連パッケージであるreact-bootstrap-table2-editorの関数を渡します。
blurToSaveをtrueにしておくことで編集後にコンポーネント外にフォーカスが移った場合にも変更が保存されるようになります。

App.js
// ommit
import cellEditFactory, { Type } from "react-bootstrap-table2-editor";

const data = [
  { id: 1, name: "フシギダネ", type: "くさ" },
  { id: 2, name: "フシギソウ", type: "くさ" },
  { id: 3, name: "フシギバナ", type: "くさ" },
  { id: 4, name: "ヒトカゲ", type: "ほのお" },
  { id: 5, name: "リザード", type:"ほのお" },
  { id: 6, name: "リザードン", type: "ほのお" },
  { id: 7, name: "ゼニガメ", type: "みず" },
  { id: 8, name: "カメール", type: "みず" },
  { id: 9, name: "カメックス", type: "みず" },
]

const types = [
  "くさ", "ほのお", "みず", "ひこう", "どく", "かくとう", "あく", "こおり", "むし", "でんき",
  "ノーマル", "ドラゴン", "フェアリー", "じめん", "いわ", "はがね", "エスパー", "ゴースト"
]

const columns = [
  { dataField: "id", text: "ID", sort: true, editable: false },
  { dataField: "name", text: "Name", sort: true, editable: true },
  {
    dataField: "type", text: "Type", sort: true, editable: true,
    editor: {
      type: Type.SELECT,
      getOptions: () => types.map((type) => { return { value: type, label: type } })
    }
  },
]

const App = () => {
  return (
    <Container style={{ width: "600px" }}>
      <BootstrapTable
        data={data}             // データ
        columns={columns}       // カラム定義
        keyField="id"           // キー
        bootstrap4={true}       // Bootstrap4を指定。デフォルトではBootstrap3
        bordered={true}         // 表のボーダー
        cellEdit={cellEditFactory({ mode: "click", blurToSave: true })}  // セルの編集を有効にする
      />
    </Container>
  );
}
export default App;

エディタをカスタマイズする

編集はできるようになりましたが、
ポケモンは一匹で複数のタイプを持つ場合が数多くあり、現状のSelectボックスでは機能が足りません。(下画像)
BSTable2-2.png
しかし調べた限り、react-bootstrap-table2には複数選択のSelectボックスにする機能がないようなので、
かわりにエディタのカスタマイズ機能を使って別のSelectボックスを表示するようにします。

Selectボックスにはreact-selectを使っていきます。
react-select https://github.com/jedwatson/react-select

まずは元データのタイプを配列にしておきます。
カラム定義のformatterプロパティで通常表示時のフォーマットを指定できるので、配列を文字列に変換して表示するようにします。

続いて、カラム定義のeditorRendererプロパティで、react-selectを使ったコンポーネントを指定します。
複数選択を有効にする場合、配列の更新となるためちょっと厄介で、実装これで合ってるのかはよくわかりません。
(とりあえず動いてる)

App.js
// ommit
import Select from "react-select";
import PropTypes from "prop-types";

const data = [
  { id: 1, name: "フシギダネ", type: ["くさ", "どく"] },
  { id: 2, name: "フシギソウ", type: ["くさ", "どく"] },
  { id: 3, name: "フシギバナ", type: ["くさ", "どく"] },
  { id: 4, name: "ヒトカゲ", type: ["ほのお"] },
  { id: 5, name: "リザード", type: ["ほのお"] },
  { id: 6, name: "リザードン", type: ["ほのお", "ひこう"] },
  { id: 7, name: "ゼニガメ", type: ["みず"] },
  { id: 8, name: "カメール", type: ["みず"] },
  { id: 9, name: "カメックス", type: ["みず"] },
]

const types = [
  "くさ", "ほのお", "みず", "ひこう", "どく", "かくとう", "あく", "こおり", "むし", "でんき",
  "ノーマル", "ドラゴン", "フェアリー", "じめん", "いわ", "はがね", "エスパー", "ゴースト"
]

const columns = [
  { dataField: "id", text: "ID", sort: true, editable: false },
  { dataField: "name", text: "Name", sort: true, editable: true },
  {
    dataField: "type", text: "Type", sort: true, editable: true,
    formatter: (cell, row) => {
      return row.type.join("/");
    },
    editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) => (
      <TypeSelect {...editorProps} value={value} row={row} options={types} />
    )
  },
]

const App = () => {
  return (
    <Container style={{ width: "600px" }}>
      <BootstrapTable
        data={data}             // データ
        columns={columns}       // カラム定義
        keyField="id"           // キー
        bootstrap4={true}       // Bootstrap4を指定。デフォルトではBootstrap3
        bordered={true}         // 表のボーダー
        cellEdit={cellEditFactory({ mode: "click", blurToSave: true })}  // セルの編集を有効にする
      />
    </Container>
  );
}
export default App;


class TypeSelect extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: this.props.row.type };
  }

  static propTypes = {
    value: PropTypes.array,
    onUpdate: PropTypes.func.isRequired
  }

  getValue() {
    return this.state.value;
  }

  // かならず一つはタイプを選択した状態にしたいので、
  // とりあえず配列が0にならないようにする。本来ならバリデーションで拾うべき。
  handleOnUpdate(event) {
    if (event) {
      console.log(event)
      this.setState({
        value: event.map(x => x.value)
      })
      return event.map(x => x.value);
    } else {
      return this.state.value;
    }
  }

  render() {
    const { value, onUpdate, ...rest } = this.props;
    return (
      <Select
        {...rest} isMulti isClearable={false}
        key="type" name="Type"
        onChange={(event) => { onUpdate(this.handleOnUpdate(event)) }}
        className="basic-single" classNamePrefix="select"
        defaultValue={this.props.row.type.map((type) => { return { value: type, label: type } })}
        options={this.props.options.map((option) => { return { value: option, label: option } })}
      />
    )
  }
}

タイプの複数選択が可能になりました。
ダウンロード.gif

と、上記のようにエディタのカスタマイズができたり、デザインの指定やバリデーションチェックの実装などもできるうえ、
関連パッケージを導入するとページネーションも楽に追加することができます。
比較的柔軟にやりたいことがやれるパッケージでした。

コンポーネントそのもののコードもまあまあ読みやすいままを保てる一方で、
カラム定義を弄りはじめると結構ごちゃごちゃしてきちゃうところが懸念点でしょうか。

参考

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

レンダリングと描画の違いを整理する

ブログ:
https://in-thepink.com/rendering/

はじめに

レンダリングと描画を同じ意味で使っていませんか?
会話などカジュアルな場合には気にする必要はありませんが、厳密には違うので整理して理解しましょう!!

DOMの種類

DOMはリアルDOMと仮想DOMがあります。
また、そもそもDOMとはHTMLを操作するためのAPIです。
DOMの紹介

  • リアルDOM
    • 実際に表示されているDOM
    • 変更すると画面がそのまま変わる
    • 変更しようとする場合、負荷が大きい処理になる
  • VDOM
    • 単なるデータ
    • 変更しても画面が変更されない
    • 負荷が小さい処理で変更できる

レンダリングとは

state(props)変更前のVDOMと変更後の差分を比較し、差分を検知することでVDOMを再構築すること

レンダリングフロー

  1. 変更前と変更後の差分を比較するための仮想DOMを二つ用意する
  2. 変更を反映させるため、VDOMをJavascriptで操作(一般的にリアルDOMを操作するより速い)
  3. 変更前と変更後のVDOMの差分を比較
  4. 差分だけをリアルDOMに反映する

リアルDOMを操作するよりなぜ早いか

  • DOMを操作するより負荷の小さい処理だから
    • jQueryなどを使用してDOM操作を行う場合、関係のないところも再描画されるから
  • 変更前のVDOMと変更後のVDOMの差分のみを反映させるから
    • 「差分のみ」というのがキモです

描画とは

上記レンダリングフローで反映されたリアルDOMをブラウザに表示させること

再描画のタイミング

  • 関数が再生成された時
  • 変数が再生成された時
  • stateの変更があった時
  • 親コンポーネントが再描画された時
  • 親コンポーネントから引き渡されているpropsが変化した時
  • コンポーネント内でuseStateで定義している変数が変化した時
  • カスタムフックより受け取っている変数が変化した時

React.memo,useCallbackなどを使用することで、無駄な再レンダリングを防ぐことができます。
無駄な再レンダリングを防ぐことで、パフォーマンス向上につながります。

figmaのパフォーマンス向上についての記事

参考

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

テスト02

テスト02です

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

Node.jsのバージョンあげたら「Node Sass could not find a binding for your current environment」とでた場合

概要

Node.jsのメジャーバージョンをあげると、node-sassがエラーをはくことがある

「Node Sass could not find a binding for your current environment」

そのときは、node-sassをリビルドして自分のnode環境に合った状態にする

対処法

npm rebuild node-sass

これでOK

そのほか対処法

それでもだめなら

npm rebuild node-sass --force

それでもだめなら、いったんnode-sass消してから。いれなおす。
(入れ直しでも、環境にあった適切なbinaryが生成される)

npm uninstall node-saas
npm install node-sass --save-dev
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

addEventListenerの第三引数ってなんだ?(自分用めも)

下記の参考書を進めてきて、ついに7章に突入し読了が近づいてきた。
JavaScript本格入門 山田祥寛著

そんな中ずっと気になっていたことがあった。

//記入例
document.getElemetById('hoge').addEventListener('click',function() {
},false);

そう、addEventListenerの第三引数、falseってなんだ??

「7章まできて、そんなこともわかっていなかったのか!!」

本書では、addEentListenerメソッドについて下記のように記載されていた。

elem.addEventListener(type, listener, capture)

elem:要素オブジェクト
type:イベントの処理
listener:イベントに応じて実行すべき処理
capture:イベントの方向
引用元:JavaScript本格入門 p299 山田祥寛著

今回知りたいのは、第三引数、captureについてだ。

captureはイベントの方向を指定するためのものらしい。

「イベントの方向とはなんぞや」と言うことで、本書で指定されたp347を見ると、イベントの伝搬つまり、イベント処理が呼び出されるまでのプロセスが関係しているっぽい(むずよー)。

まず、イベントの伝搬は次のようなフェーズを辿っているそうだ。

1.キャプチャフェーズ
2.ターゲットフェーズ(イベント発生!)
3.バブリングフェーズ

通常(第3引数がfalse)の場合だと、「イベントの発生元から上位ノード」へと処理が進む、つまりバブリングフェーズでイベントは処理されるそう。

だから、この第三引数をtrueにすることで、「上位ノードからイベントの発生元」と方向が逆になり、
キャプチャフェーズでイベントが処理されるようになると言うことだそうだ。

今回の目的であった、addEventListenerメソッドの第三引数の意味は最初よりはイメージできたが
実際に現場でこれらをどういった場面で使い分けるのかと行ったことは、まだイメージできなかった。
まあ、それは今後わかっていけばいいかなー笑。これだから最近の若者は...。

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

jQueryプラグインでオリジナルのダイアログを表示する方法

デフォルトで表示

特にカスタマイズせず、confirm()で表示すると下記のような表示になります。
初期ダイアログ.png
OK/キャンセルの2択だけ選択させたい場合であれば、初期ダイアログで事足りますが、
ボタンや選択肢を追加したい場合、カスタマイズする必要があります。

あと、個人的に初期デフォルトだとIPが表示されてかっこ悪い印象を持ちます:sweat_smile:
このあと、カスタマイズする方法を説明します。

準備

オリジナルのダイアログを表示するにはjQuery UIのDialogプラグインを用います。
jQuery UIに必要なファイルは下記3つになります。

  • jQueryのJavaScriptライブラリ
  • jQuery UIのJavaScriptライブラリ
  • jQuery UI用のCSSテーマファイル
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- jQuery UI -->
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">

オリジナルのダイアログで表示

今回はダイアログ上に、選択項目を追加していきます。

HTML側の記述

HTML側ではダイアログを呼び出すボタン、ダイアログの内容を記述します。

<!--ダイアログを呼び出すボタン-->
<button id="btn">ダイアログ表示</button>

<!--ダイアログの内容-->
<div id="dialog" title="同意書">
  <!--ラジオボタンを配置-->
  <label><input type="radio" name="test" value="1" checked> 選択肢1</label><br />
  <label><input type="radio" name="test" value="2"> 選択肢2</label><br />
  <!--チェックボックスを配置-->
  <label><input type="checkbox" value="1">上記記載事項に同意します</label>
</div>

Dialogのオプション項目

JavaScriptでダイアログを呼び出す処理について記述していきますが、
呼び出し時には必ずオプション項目を定義する必要があります。

ダイアログのオプション項目は下記の通りです。

オプション 内容
autoOpen 初期表示にダイアログを開くか設定(初期値:true)
Width ダイアログの横幅を指定。単位はピクセル(初期値:300)
modal モーダルダイアログにしたい場合はtrueに設定する(初期値:false)。背景が少し暗くなる。
buttons 記述形式は次の通り。
{text:(ボタン名), click:(処理)}
click以外のイベントも定義可能

JavaScript側の記述

$(function(){
    //ダイアログの初期設定
    $("#dialog").dialog({
        autoOpen: false, //自動で開かないように設定
        width: 500,       // 横幅のサイズを設定
        modal: true,  //モーダルダイアログにする
        buttons: [
            {
                text: "戻る",
                click: function(){
                    // ダイアログを閉じる
                    $(this).dialog("close");
                }
            },
            {
                text: "次へ",
                click: function(){
                    // ページ遷移
                    window.open('https://qiita.com/tamakiiii', '_blank');;
                }
            }
        ]
    });

    $("#btn").click(function(){
        //ダイアログの呼び出し
        $("#dialog").dialog("open");
    })
});

表示したときのダイアログはこんな感じです
オリジナルダイアログ.png

参考

jQuery UI を用いてオリジナルのダイアログを実装する方法!独自のボタンや選択肢も簡単に追加できる!

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

localStrageで半永久保存のやり方

webブラウザの機能のlocalStrageについてです。

まず、localStrangeをなんなのか見てみましょう

見方についてはchromeの検証ツール→アプリケーション→localStrage→fileで見ることができます。

スクリーンショット (8).png

では、この機能について見ていきましょう

開いたらキーと値というものがあると思いますが、これは、keyとvalueでこれらをここに格納できるんです。

格納するだけだったらオブジェクトと変わりませんよね。ですが、これはページを閉じたりリロードをしたりしてもずっと保存しておきたい値を格納できるんです。

使い道は(どのページを見ただとか、どの商品にお気に入りボタンを押したなど)など様々です。

cookieとあんまり変わらないですが、容量や保存期間が少し違います

ですが、ここに格納するには、一つ条件があります。

それは、すべて文字列で書かなきゃいけないんです。

例えば、配列やオブジェクトをそのままlocalstrageに保存すると
"[Object object]"のように意味不明な形で保存されます。

これを解決するにはjsonでオブジェクトを文字列にしなければいけません。

やり方は

json.stringify([1,2,3])

とすると

"[1,2,3]"

と文字列になりlocalStrageに保存できるようになります。

なお、json.stringifyは配列の中身が多すぎて見れないときなどに見れるようにするためにもつかわれます。

ちなみに、json型が使いづらくて元の形に戻したい場合は
js
json.parse

でもとに戻せます。

localStrageに文字列を保存する方法

localStrage.setItem("key","value")
//その2:オブジェクトのプロパティとして保存するパターン
localStorage.saveKey = '保存する値';

これで格納できます。

また、値を返したいときは

localStrage("key")

複数格納の場合

var obj = {
      last : tarou,
      first : yamada
    };
var obj = JSON.stringify(obj);
localStorage.setItem('Key', obj);

取り除く場合

localStorage.removeItem('Key');

初期化

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

【JavaScript】超基礎的なメソッドの紹介 その2

JavaScriptで行うDOM(Document Object Model、要はHTML等のマークアップ言語にアクセス・編集しやすい標準的に仕組み)の操作において、基礎的なメソッドを学んだため、備忘として記します。
前回は、「1 HTML要素の取得」、「2 テキストデータの取得」を紹介しましたが、その続きです。

3 セレクタ式による要素の取得とgetメソッドとの比較
 (1)セレクタ式による要素の取得

<ul id="listsSports">
  <li class="list">サッカー</li>
  <li class="list">野球</li>
  <li class="favorite">テニス</li>
</ul>
<ul id="listsDrinks">
  <li class="favorite">ビール</li>
  <li class="favorite">ワイン</li>
  <li class="list">焼酎</li>
</ul>
<script>
  const lists = document.querySelectorAll(`.list`); //class属性がlistに該当する要素すべて取得し、定数listsに代入。
  console.log(lists); //「サッカー」、「野球」、「焼酎」の要素をコンソールに出力。 
  const list = document.querySelector(`.list`); //class属性がlistに該当する要素のうち、最初に該当したもののみを取得し、定数listに代入。
  console.log(list); //「サッカー」の要素をコンソールに主力。 
</script>

 (2)getメソッドとの比較
    例えば、上記リストのうち以下の要素のみを取得したいとする。

  <li class="favorite">ビール</li>
  <li class="favorite">ワイン</li>

    この場合、getメソッド(getElementByIdとgetElementsByClassName)だと一度定数にいれたものを再度取得する必要が出てくる。

<script>
  const lists = document.getElementById(`listsDrinks`);//まず、id属性がlistsDrinksの要素をすべて取得。
  const favoriteDrinks = lists.getElementsByClassName(`favorite`); //そこからclass属性がfavoriteの要素を取得。
  for ( let i = 0; i < favorite.Drinks.length; i++) {
  console.log(favoriteDrinks[i]); //繰り返し文による出力。
}
</script>

    一方で、セレクタ式(queryメソッド)だと、要素の取得に関して1行で記述を済ませられる。

<script>
  const favoriteDrinks = document.querySelectorAll(`#listsDrinks .favorite`);
  for ( let i = 0; i < favorite.Drinks.length; i++) {
  console.log(favoriteDrinks[i]);
}
</script>

    定義する変数や定数の数が増えるほど、getメソッドは記述が多くなる。
    比べて、セレクタ式(queryメソッド)は複数の条件を1つにまとめられる点で優れている。

    ただし、処理速度の点でいえばgetメソッドはqueryメソッドよりも速いため、コード記述のボリュームや煩雑さに合わせて使い分けが必要。

今回は以上です。

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

Vue.jsの特徴

プログラミング勉強日記

2020年9月12日
JSには色々なフレームワークがあって、開発の用途によって使用するフレームワークが違うと思うので、まとめてみようと思う。ReactについてAngularについてまとめたので、今回はVue.jsについてまとめる。

Vue.jsとは

 Vue.jsは、UIを構築するためのJavaScriptのフレームワーク。Vue.jsでコードを書くときは、無駄な長い記述を少なくできる。Vue.js独自の規格やルールも少ないので、他のフレームワークよりも自分の好きな方法でアプリ開発を行うこともできる。

特徴

コンポーネント指向である

 ReactとAngularと同様で、Vue.jsもコンポーネント指向である。コンポーネント指向は、ソフトウェアを機能ごとに部品(コンポーネント)として分割して、必要に応じて組み合わせて使う考えのことである。
 機能を小さい部品にして持たせているので、状態の管理もしやすくて再利用も容易にできる。再利用性が高くなり、デザインと技術の共同作業を簡単にすることができ、開発スピードの向上ができる。

学習コストが低い

 様々なフレームワークがあり、開発現場ごとに新しい技術を取得しないといけないことも珍しくないが、その中でもVue.jsは学習コストが低く、初心者や経験者問わずすぐに開発することができるという特徴がある。Vue.jsがシンプルであり、非常に人気が高いので日本語での技術ブログや記事が充実している。

ディレクティブ

 Vue.jsはDirectiveというView要素に付加できる独自要素がある。ディレクティブを使用することで、DOMを簡単に操作することができ、HTML要素の表示・非表示を柔軟に変化でき、開発スピードを速められる。

双方向データバインディング

 Vue.jsもデータバインドに特化したフレームワークである。
 アプリでユーザ画面(View)で操作した内容をデータ(Model)の反映、データ(Model)の内容が変わったときに画面(View)に反映する必要がある。なので、Viewを操作するときはModelに反映するために、画面からHTMLの要素を取得して要素をデータとして処理、処理したデータを画面に反映するプログラムを書く必要がある。

クラスを利用可能

 JSでは使えなかったクラスを利用することができる。Vue.jsでは連想配列の形でクラスを作成して、様々なデータの入れ物として活用することができる。

TypeScriptも利用可能

 Vue CLIにはJestやMochaを使って簡単に単体テストを行うためのプラグインオプションがある。公式のVue Tesyユーティリティもあり、作成したアプリの動作を検証することができる。

URLのルーティングができない

 ルーティングとは同じページ上で疑似的なページ遷移すること。
 Vue.jsではURLのルーティングを単体で行うことができる。ルーティングを必要とする場合には管理が複雑になることがあり、単一のページ以外が表示されない扱いになり、アクセス解析やSEO上の不都合が生じることがある。

古いブラウザには非対応

 Vue.jsを利用して作ったサイトは古いブラウザでは見ることができない。

参考文献

【Vue.js入門】特徴や他のフレームワークとの比較などを紹介!

Vue.jsとは?特徴とできることを具体例を交えて紹介

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

[JavaScript] 日本語を含んだ文字配列を大量に比較する場合「localeCompare」と「Intl.Collator の compare」のどちらがパフォーマンス良いか検証した

フロントエンドで日本語を含んだ文字配列を比較する手段として、

  • localeCompare
  • Intl.Collatorcompare

の2つがあります。
どちらが、パフォーマンスが良いか検証した際のメモを残します。

localeCompareIntl.Collatorcompare

本記事記載(ES2020)時点において、
文字列を比較するlocaleCompareというメソッドが提供されています。

localeCompareで配列の内容を五十音順ソートの記事にもある通り、
漢字を含めた日本語をソートしたい場合、localeCompareを使用する手があります。

が、MDNのlocalCompareの説明を読み進めると、
以下の記載が見つかりました。

パフォーマンス
巨大な配列のソートなど大量の文字列を比較する場合は Intl.Collator オブジェクトを作成し、 compare プロパティで提供される関数を利用すると良いでしょう。

日本語を含めた文字配列を作成し、検証してみました。

CodeSandboxで検証

から、1000個の名前ダミーデータ(英字500個, 日本語500個)を用意し、
CodeSandboxで検証用のロジックを書いてみました。

image.png

  • Intl.Collator Sortを押下すると、Intl.Collatorcompareを使った比較および時間計測
  • localCompare Sortを押下すると、localCompareを使った比較および時間計測

が行われ、コンソールへ以下のように出力されます。

// 1回目
Object {used: "Intl.Collator", time: 1.8749999981373549, localSamples: Array[1000]}
Object {used: "localeCompare", time: 3.025000001769513, localSamples: Array[1000]}

// 2回目
Object {used: "Intl.Collator", time: 1.8199999976204708, localSamples: Array[1000]}
Object {used: "localeCompare", time: 3.224999996717088, localSamples: Array[1000]}

MDNのドキュメント通り、Intl.Collatorの方が高速にソートできるようです。

結論

日本語を含む文字配列を大量に比較する場合は、
localeCompareよりIntl.Collatorを使うとパフォーマンスが良いとわかりました。

が、仕様上、一二三のような漢字も一三二とソートされますので、
注意して使用してください。1

おわり

以上です。
おかしい、気になった点などありましたら、
フィードバックいただけると助かります。 :pray:

参考記事


  1. 名前に紐づくふりがなのような項目がある場合は、ふりがなでソートした方が正確に並び替えられます。 

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

React Nativeをいちから始める<公式ドキュメントを読んでいく> Part.1 The Basics

こんにちは。

スマホアプリを作成するフレームワークであるReact Nativeをいちから勉強しようと思います。
せっかくなので、やってみたことの要点をまとめてあとから復習できるように記事にまとめました。

React Nativeをざっと知りたい方、基礎をざっと復習したい方は参考にしてみてください。

本記事の位置づけ

基本的に、公式ドキュメントの解説を上から順番に見て、ざっとまとめた記事です。
また、本ページは、下記公式ドキュメントの一番最初「The Basics」の部分をまとめた記事となります。

https://reactnative.dev/docs/getting-started
- The Basics ←今ココ★
- Environment setup
- Workflow
- Design
- Interaction
- Inclusion
- Performance
- JavaScript Runtime
- Connectivity
- Native Components and Modules
- Guides (Android)
- Guides (iOS)

なお、本ページに記載のソースコードはすべて上記サイトから引用したものとなります。

React Nativeとは何か

Facebookが作成したオープンソースのモバイルアプリケーションフレームワークのこと。
iOSおよびAndroidのプラットフォーム向けに共通のコードでアプリを作成できる。

言語、フレームワークのベースはJavaScript、React。

用語

  • View … UIを構成するブロックであり、画像を表示するImageView、テキストを表示するTextViewなどがある。
  • Native Component … AndroidやiOSのプラットフォームのコンポーネントのこと
  • Core Component … React Nativeが扱うコンポーネントのことで、各プラットフォームのネイティブコンポーネントと一対一対応している。 https://reactnative.dev/docs/intro-react-native-components#core-components

まずはHello World

コード

import React from 'react';
import { Text } from 'react-native';

const Cat = () => {
  return (
    <Text>Hello, I am your cat!</Text>
  );
}

export default Cat;

上記はfunctionとしての定義だが、classとしても書ける(functionとclassはどちらで書いてもよい)。
ただfunctionのほうが機能追加の点で将来性があるとのことで、公式ドキュメントはfunctionをメインで書かれている。

プログラムの構造

  • importでReactコンポーネントやReact NativeのText コアコンポーネントをインポートしている。
  • 「const Cat = () => {};」の部分がfunctionの定義。これによりCatコンポーネントが定義できる。
  • 「export default Cat;」でCatファンクションをプログラム内で呼び出せるようにする。
  • <Text>~</Text>の部分は、JavaScript内にエレメントを記載するJSX記法。JSX記法自体はJavaScriptのため、変数を中括弧とともに内部に記載することが可能。
  • JSX記法内で {getFullName("Rum", "Tum", "Tugger")} のように関数を呼ぶこともできる。

Props

propertiesの略で、関数に値を渡す機構。いわゆる、関数の引数。

...

const Cat = (props) => {
  return (
    <View>
      <Text>Hello, I am {props.name}!</Text>
    </View>
  );
}

const Cafe = () => {
  return (
    <View>
      <Cat name="Maru" />
      <Cat name="Jellylorum" />
      <Cat name="Spot" />
    </View>
  );
}

...

関数で「Cat = (props)」のように記載することで、propを受け取ることができるようになる。

値の渡し方は、JSX記法内に「name="Maru"」のように記載する。
関数内では、「{props.name}」の記載で値を参照できる。

propsではJSオブジェクトを受け渡せるので、下記のような記載も可能。

      <Image
        source={{uri: "https://reactnative.dev/docs/assets/p_cat1.png"}}
        style={{width: 200, height: 200}}
      />

curly bracesが二重になっている理由:内側の{}はJSオブジェクトの記載方法。外側の{}はJSXで受け渡すための記載。

State

関数の状態を保持する。いわゆる内部変数。
持てる値の型は、strings, numbers, Booleans, arrays, objects。

stateを使う3ステップ

1.useStateをインポートする。

import React, { useState } from 'react';

2.useStateを使い、getter、setter、初期値をセットする。

[<getter>, <setter>] = useState(<initialValue>).

const [isHungry, setIsHungry] = useState(true);

3.getter、setterで値を読み書きする。

setterの例

<Button
  onPress={() => {
    setIsHungry(false);
  }}
/>

getterの例

<Button
  disabled={!isHungry}
  title={isHungry ? 'Pour me some milk, please!' : 'Thank you!'}
/>

あれ?isHungryはconstで定義したのになぜ値が書き換えられるの?
⇒setterが実行されると大元の関数が再度実行され、関数自体がリセットされている。なので値を書き換えているわけではない。

上記全体を含めた例

import React, { useState } from "react";
import { Button, Text, View } from "react-native";

const Cat = (props) => {
  const [isHungry, setIsHungry] = useState(true);

  return (
    <View>
      <Text>
        I am {props.name}, and I am {isHungry ? "hungry" : "full"}!
      </Text>
      <Button
        onPress={() => {
          setIsHungry(false);
        }}
        disabled={!isHungry}
        title={isHungry ? "Pour me some milk, please!" : "Thank you!"}
      />
    </View>
  );
}

const Cafe = () => {
  return (
    <>
      <Cat name="Munkustrap" />
      <Cat name="Spot" />
    </>
  );
}

export default Cafe;

<>~</>って何?
⇒JSXの断片(fragments)。エレメントを複数並べて書く場合は必ず別のエレメントでくくる必要があるが、Viewなどを使うとネストしてしまうので、単にエレメントをまとめたい場合はこのように書くことができる。

PropsとStateの使い分け

Propsは関数に値を渡すことで動作を変えたいとき、Stateは関数に状態(値)を保持したいときに使う。

Core Component:Text Input

文字列を入力できるコアコンポーネント。

よく使うprop

  • onChangeText … テキストが変更されたときに発火する。
  • onSubmitEditing … テキストがsubmitされたときに発火する。

import React, { useState } from 'react';
import { Text, TextInput, View } from 'react-native';

const PizzaTranslator = () => {
  const [text, setText] = useState('');
  return (
    <View style={{padding: 10}}>
      <TextInput
        style={{height: 40}}
        placeholder="Type here to translate!"
        onChangeText={text => setText(text)}
        defaultValue={text}
      />
      <Text style={{padding: 10, fontSize: 42}}>
        {text.split(' ').map((word) => word && '?').join(' ')}
      </Text>
    </View>
  );
}

export default PizzaTranslator;

このプログラムは、入力された単語の数だけピザアイコンを画面表示するもの。
onChangeTextで、setterであるsetTextを呼び出し、入力された値をTextにセットしている。

TextInputの詳細は下記を見るべし。
https://reactnative.dev/docs/textinput

Core Component:ScrollView

スクロールができるコンテナ。
複数の異なる種類のコンポーネントやビューを含むことができる。
horizontalプロパティで、垂直スクロール、水平スクロールかを指定する。

...

export default App = () => (
  <ScrollView>
    <Text style={{ fontSize: 96 }}>Scroll me plz</Text>
    <Image source={logo} />
    <Image source={logo} />
  </ScrollView>
);

pagingEnabledプロパティで、スワイプジェスチャーを可能にする。
maximumZoomScale、minimumZoomScaleプロパティで、拡大縮小ができるようにする。

ScrollViewとFlatListどっちを使う?

データ数が少ない場合は、スクロールの際リスト全体を再描画するScrollViewのほうが軽量。

データ数が多い場合は、スクロールの際リスト全体ではなく画面に表示されている部分のみを再描画するFlatListが高速。

Core Component:List Views

複数の項目をリストで表示するコンポーネント。
FlatList、SectionListの2種類ある。

FlatList

FlatListは下記2つのプロパティを指定する必要がある。

  • data … 表示するリストデータ
  • renderItem … リストの各項目に適用する処理

FlatListの例

import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 22
  },
  item: {
    fontSize: 18,
    height: 44,
  },
});

const FlatListBasics = () => {
  return (
    <View style={styles.container}>
      <FlatList
        data={[
          {key: 'Devin'},
          {key: 'Dan'},
          {key: 'Dominic'},
          {key: 'Jackson'},
          {key: 'James'},
          {key: 'Joel'},
          {key: 'John'},
          {key: 'Jillian'},
          {key: 'Jimmy'},
          {key: 'Julie'},
        ]}
        renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
      />
    </View>
  );
}

export default FlatListBasics;

FlatList
https://reactnative.dev/docs/flatlist

SectionList

もしセクションごとに分かれたデータを表示したい場合(セクションにヘッダを付けたいなどのケース)は、 SectionListを使うとよい。

        <SectionList
          sections={[
            {title: 'D', data: ['Devin', 'Dan', 'Dominic']},
            {title: 'J', data: ['Jackson', 'James', 'Jillian', 'Jimmy', 'Joel', 'John', 'Julie']},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item}</Text>}
          renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
          keyExtractor={(item, index) => index}
        />

dataの代わりにsectionsプロパティで、セクション付きの値を指定。
renderItemに加えて、renderSectionHeaderプロパティでセクションヘッダのスタイルも指定。
keyExtractorで、リストデータのキーとして使う値を指定。

SectionList
https://reactnative.dev/docs/sectionlist

Troubleshooting

React Nativeのセットアップ、実行時にエラーが出たら、下記を参照のこと。

https://reactnative.dev/docs/troubleshooting
https://github.com/facebook/react-native/issues/

プラットフォーム依存のコード

AndroidやiOSのネイティブコードを書きたいとき、下記2つの方法がある。

  • Platformモジュールを使用する方法
  • ファイルの拡張子を使用する方法

各モジュールには特定のプラットフォームでしか使用されないプラットフォーム依存のプロパティがあり、それらは @platform と指定されている。
使用方法は各プロパティ項目にリンクがはられているのでそれを参照するとよい。

Platformモジュールを使用する方法

ネイティブコードをごく少量使う場合は、Platform.OSを使った書き方で。

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  height: Platform.OS === 'ios' ? 200 : 100
});

Platform.OSの値は、「ios」または「android」になる。

ある程度のコード量になる場合は、Platform.selectを使った書き方で。

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        backgroundColor: 'red'
      },
      android: {
        backgroundColor: 'green'
      },
      default: {
        // other platforms, web for example
        backgroundColor: 'blue'
      }
    })
  }
});

Platform.selectの値は、'ios' | 'android' | 'native' | 'default'。
Webでの表示の場合は、defaultに入る。

iOS/AndroidとWebで処理を書き分けたければ、以下のようにnativeで判定するとよい。

const Component = Platform.select({
  native: () => require('ComponentForNative'),
  default: () => require('ComponentForWeb')
})();

<Component />;

iOS/Androidの場合は、nativeの処理が実行される。
Webの場合は、defaultの処理が実行される。

プラットフォームのバージョンを取得する

Android

import { Platform } from 'react-native';

if (Platform.Version === 25) {
  console.log('Running on Nougat!');
}

Platform.Versionにて取得。

iOS

import { Platform } from 'react-native';

const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 9) {
  console.log('Work around a change in behavior');
}

Platform.Versionには、-[UIDevice systemVersion]の実行結果が入っており、"10.3"のような値となっている。
そのため、parseIntなどを使って数値を抽出する。

ファイルの拡張子を使用する方法

プラットフォーム依存コードをファイルごとに書き分け、ファイル名で判定する方法。
.ios.または.android.という文字列が含まれるファイルを作成することで、実行するプラットフォームに応じて適切なほうが読み込まれる。

より多くのプラットフォーム依存コードがある場合はこちらの方法で。

BigButton.ios.js
BigButton.android.js

のようにファイル名で分けておき、

import BigButton from './BigButton';

とすることで、プラットフォームに合わせて適切なほうのファイルが読み込まれる。

ネイティブ環境とWeb環境で書き分けたいときは、「.native.」も使える。

Container.js
Container.native.js

のようにファイル名で分けておき、

import Container from './Container';

とする。

おわりに

ここまでが「The Basics」の章の内容となります。

引き続き順番に公式ドキュメントを読んで勉強していきます。
少しでもご参考になったならば幸いです。

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