20210301のJavaScriptに関する記事は28件です。

JavaScriptが動く仕組み(ざっくりしてます)

下記サイトをDeepLで訳しながら一生懸命読み、ざっくりとした理解を得ました。
https://dev.to/lydiahallie/javascript-visualized-the-javascript-engine-4cdf

ざっくりとした理解

JavaScriptのソースコードは一旦byte単位の小さいデータに分割される。
その後再び、文字列や記号(function,return,{},()など)が再生成される。
それらはパーサーによって解析される。パーサーは再生成された文字列や記号(function,return,{},()など)をもとに抽象構文木(AST)を作成する。
ASTとは、そのコードがどんなパーツで構成されていて、どんな役割を果たすものなのかをしめすもの。
作成されたASTをもとに、インタープリタが機械語を生成しCPUで処理が行われる。

おわりに

指摘、ツッコミお待ちしています。

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

JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事① ~はじめに~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事です!
この記事のターゲットは、以下のような方々です
- JavaScriptはわかったけど、TypeScriptってなんだっけ?
- TypeScriptというものがあるらしい...学んでみよう...!

今日から数日にわたって記事を書く予定なので、この記事が読み終わった方は、以下も是非お読みください。
TypeScriptをなんとなく理解する② ~型を組み合わせる: Union~
TypeScriptをなんとなく理解する③ ~型を組み合わせる: Generics~
・ TypeScriptをなんとなく理解する④ ~構造型~ (3/3更新予定)

※ もし誤りや改善点があればコメントで教えて下さい!

TypeScriptとは?

TypeScriptは、型定義ができるJavaScriptです。

JavaScriptでは、型を明確に宣言していません。
そこで、TypeScriptでは、この定数は数値型(number)として宣言!、あの定数は文字型(string)として宣言!
といったように型を宣言しつつJavaScriptを書いていくことができます。

型定義によって、何がうれしいのかというと、
コードの予期しない動作によるバグを減らすことが出来ます

例えば、以下のような場合です。

本当は、文字列として扱いたい定数が、なにかしらの処理によって、最終的に数値が入ってしまい、エラーが起きた。。。

TypeScriptを使えば「コーディング段階」でこのような型の矛盾を、検知することが出来ます。

型に関する概要

今回は、TypeScriptの基本的な機能である、1. 型推論、2. 型の定義について紹介します。

1. 型推論

型推論とは、何も型を定義しなくても、勝手に型を推論してくれる(型を生成してくれる)神の御業です。
たとえば、以下のようなコードを書いたとします。

let smartPhone = 'Android';

そして、この変数に数値を入れてみましょう。

let smartPhone = 'Android';
smartPhone = 11; // Problem: タイプ「数値」はタイプ「文字列」に割り当てることができません.ts(2322)

すると、数値を代入した行で「タイプ「数値」はタイプ「文字列」に割り当てることができません.ts(2322)」と警告されました。
つまり、最初に'Android'を代入した時点で、TypeScriptが自動的に型を推論し、割り当てるので、
このsmartPhone変数には文字列(string)以外を代入しようとすると、怒られてしまいます。

これが、型推論になります。

このように自動的に推論してくれるので、引数が文字列でないと駄目?な関数に誤って、数値を入れることもなくなります。
「引数は、文字列でないと駄目です!この変数は数値型です!」と勝手に怒ってくれるわけです。

let version = 11; // 数値(number)型

const upVersion = (device: string) => { // 引数は文字列(string)型
  console.log(`OSは${device}です`)
}

upVersion(version); // Problem: タイプ 'number'の引数をタイプ 'string'のパラメーターに割り当てることはできません。ts(2345)

2. 型の定義

型定義とは、字の通り型を(明示的に)定義することです。
簡単な例は、以下になります。

let version: number = 11; // versionは数値(number)型ですよ!
const smartPhone: string = 'Android'; // smartPhoneは文字列(string)型ですよ!

JavaScriptでは、様々なデザインパターンを利用てきますが、一部のデザインパターンでは、型を自動的に推測することは困難です。
例えば、以下のようなObjectです。

const fish = {
  name: "iwana",
  weight: 100,
}

これは、interface宣言を利用すると、事前にオブジェクトの形状を定義することが出来ます。
今回は動物である、Animal型を作ってみました。

interface Animal {
  name : string;
  weight: number;
}

このAnimal型は次のように利用します。

const penguin: Animal = {
  name: "Emperor penguin",
  weight: 10000,
}

仮に、Animal型と一致しないオブジェクトを指定すると、警告が出ます。

const plastic: Animal = {
  name: "pen",
  material: "oil", // オブジェクトリテラルは既知のプロパティのみを指定でき、「material」はタイプ「Animal」に存在しません。ts(2322)
  weight: 5,
}

また、クラスとオブジェクト指向プログラミングのサポート、関数の引数の型や返り値の型もサポートしています。

// Animal型
interface Animal {
  name: string;
  weight: number;
}

// class
class AnimalBook {
  name: string;
  weight: number;

  constructor(name: string, weight: number) {
    this.name = name;
    this.weight = weight;
  }
}

const user: AnimalBook = new AnimalBook("dog", 1000);

// 関数の返り値をAnimal型に
const getAnimalInfo = (): Animal => {
  return {
    name: "Emperor penguin",
    weight: 10000,
  };
};

// 関数の引数をAnimal型に
const deleteAnimalInfo = (animal: Animal):void => {
  ///...
}

他にも、JavaScriptのbooleanbigintnullsymbolobjectundefinedなども利用できます。
加えて、TypeScript特有のany型やunknown型、nevervoidもあります。

ちなみに今回は、interfaceを利用して、型をカスタマイズしましたが、Typeという構文を使って、型を作る事もできます。

おわりに

以上が、TypeScriptで重要な1. 型推論2. 型定義でした!
基本的に概要なので深くまで説明していませんが、TypeSciptについて理解が進めば幸いです。

参考文献

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

JavaScriptを知っている方がTypeScriptをなんとなく理解するための記事 ~はじめに~

この記事は何?

TypeScriptとは何か、何がいいのかを伝えようと頑張った記事です!
この記事のターゲットは、以下のような方々です
- JavaScriptはわかったけど、TypeScriptってなんだっけ?
- TypeScriptというものがあるらしい...学んでみよう...!

今日から数日にわたって記事を書く予定なので、この記事が読み終わった方は、以下も是非お読みください。
・ TypeScriptをなんとなく理解する ~型を組み合わせる~ (3/2更新予定)
・ TypeScriptをなんとなく理解する ~ジェネリック型~ (3/2更新予定)
・ TypeScriptをなんとなく理解する ~構造型~ (3/3更新予定)

※ もし誤りや改善点があればコメントで教えて下さい!

TypeScriptとは?

TypeScriptは、型定義ができるJavaScriptです。

JavaScriptでは、型を明確に宣言していません。
そこで、TypeScriptでは、この定数は数値型(number)として宣言!、あの定数は文字型(string)として宣言!
といったように型を宣言しつつJavaScriptを書いていくことができます。

型定義によって、何がうれしいのかというと、
コードの予期しない動作によるバグを減らすことが出来ます

例えば、以下のような場合です。

本当は、文字列として扱いたい定数が、なにかしらの処理によって、最終的に数値が入ってしまい、エラーが起きた。。。

TypeScriptを使えば「コーディング段階」でこのような型の矛盾を、検知することが出来ます。

型に関する概要

今回は、TypeScriptの基本的な機能である、1. 型推論、2. 型の定義について紹介します。

1. 型推論

型推論とは、何も型を定義しなくても、勝手に型を推論してくれる(型を生成してくれる)神の御業です。
たとえば、以下のようなコードを書いたとします。

let smartPhone = 'Android';

そして、この変数に数値を入れてみましょう。

let smartPhone = 'Android';
smartPhone = 11; // Problem: タイプ「数値」はタイプ「文字列」に割り当てることができません.ts(2322)

すると、数値を代入した行で「タイプ「数値」はタイプ「文字列」に割り当てることができません.ts(2322)」と警告されました。
つまり、最初に'Android'を代入した時点で、TypeScriptが自動的に型を推論し、割り当てるので、
このsmartPhone変数には文字列(string)以外を代入しようとすると、怒られてしまいます。

これが、型推論になります。

このように自動的に推論してくれるので、引数が文字列でないと駄目?な関数に誤って、数値を入れることもなくなります。
「引数は、文字列でないと駄目です!この変数は数値型です!」と勝手に怒ってくれるわけです。

let version = 11; // 数値(number)型

const upVersion = (device: string) => { // 引数は文字列(string)型
  console.log(`OSは${device}です`)
}

upVersion(version); // Problem: タイプ 'number'の引数をタイプ 'string'のパラメーターに割り当てることはできません。ts(2345)

2. 型の定義

型定義とは、字の通り型を(明示的に)定義することです。
簡単な例は、以下になります。

let version: number = 11; // versionは数値(number)型ですよ!
const smartPhone: string = 'Android'; // smartPhoneは文字列(string)型ですよ!

JavaScriptでは、様々なデザインパターンを利用てきますが、一部のデザインパターンでは、型を自動的に推測することは困難です。
例えば、以下のようなObjectです。

const fish = {
  name: "iwana",
  weight: 100,
}

これは、interface宣言を利用すると、事前にオブジェクトの形状を定義することが出来ます。
今回は動物である、Animal型を作ってみました。

interface Animal {
  name : string;
  weight: number;
}

このAnimal型は次のように利用します。

const penguin: Animal = {
  name: "Emperor penguin",
  weight: 10000,
}

仮に、Animal型と一致しないオブジェクトを指定すると、警告が出ます。

const plastic: Animal = {
  name: "pen",
  material: "oil", // オブジェクトリテラルは既知のプロパティのみを指定でき、「material」はタイプ「Animal」に存在しません。ts(2322)
  weight: 5,
}

また、クラスとオブジェクト指向プログラミングのサポート、関数の引数の型や返り値の型もサポートしています。

// Animal型
interface Animal {
  name: string;
  weight: number;
}

// class
class AnimalBook {
  name: string;
  weight: number;

  constructor(name: string, weight: number) {
    this.name = name;
    this.weight = weight;
  }
}

const user: AnimalBook = new AnimalBook("dog", 1000);

// 関数の返り値をAnimal型に
const getAnimalInfo = (): Animal => {
  return {
    name: "Emperor penguin",
    weight: 10000,
  };
};

// 関数の引数をAnimal型に
const deleteAnimalInfo = (animal: Animal):void => {
  ///...
}

他にも、JavaScriptのbooleanbigintnullsymbolobjectundefinedなども利用できます。
加えて、TypeScript特有のany型やunknown型、nevervoidもあります。

ちなみに今回は、interfaceを利用して、型をカスタマイズしましたが、Typeという構文を使って、型を作る事もできます。

おわりに

以上が、TypeScriptで重要な1. 型推論2. 型定義でした!
基本的に概要なので深くまで説明していませんが、TypeSciptについて理解が進めば幸いです。

参考文献

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

未経験からweb系自社開発企業内定までの過程の築き方と今後のキャリア形成

この記事を書くにあたり

今私は契約社員ではあるが、
ベンチャー企業にて開発部門にて戦力になるべく奮闘中。
少し客観的な視点でここまでの道のりと今後の目標などを踏まえ、
反省点なども含め時系列で整理していこうと思う。
また、未経験スキルでの転職のリアル感が伝わればどんな感じで転職の道が進んでいくのか、
よりわかりやすいと思うのでその辺も書いていきたいと思う。

簡単な経歴

  • 高校を卒業してインフラ系の会社に入社。

    • 数年間、機械や装置を扱うオペレーターを担当、簡単なシーケンスなども作成。
  • 1社目のインフラ系の会社を辞める半年前にプログラミングスクールに入校。

    • 選択した言語はJavaとSwift。
  • 空白期間を経て、コールセンターへ入社。

    • 担当はテクニカルサポート。
  • SES会社へ入社。

    • 緊急事態宣言の影響でどこへもアサインできず社内待機を過ごす。
  • コールセンターへ入社。

    • 経験を生かしてテクニカルサポートを担当。
  • テスター求人を見てベンチャー企業へ入社。

    • 現在に至る。

全くの未経験期間

プログラミングスクールを探すまで

今でこそ、転職のノウハウは良い意味でも悪い意味でも慣れた物だが、まず、10年もいた会社から転職するということが未知の世界であったため、何をして良いか分からない状態であった。
電車の中吊りで転職サイトの広告を見て気になるなと思う程度でその先はどうなっているのかすらわからないって感じ。
なので、転職について色々とググってみたりYouTubeを見たりしたものだった。

この状態では正しい情報を見つけ出すのって相当難しい。
少し端折るが、そんなこともありパソコンのスキルが相当落ちていたと実感し、先ずはスクールを探した。
結局は実際に校舎がある老舗のスクールへ入校した。
結果的には、当時の自分にとっては問題ない入校だった。
なぜなら、当時は分からなかったがパソコンのスペックもエンジニアが持つ物ではなかったので結果的によかったと思う。
その程度の知識であったのだった。

入校後の受講プログラム

当時受講したのが、JavaとSwift。
正確に言えばAndroidとiPhoneアプリ作成コース。
なぜこのコースを選んだかといえば、単純にアプリを作ってみたかったからということと、
メジャーな言語であるJavaも習うことができるということ。
結果的にこのコースを選んだのは少し遠回りだった気もした。
Web制作コースやPHP、JSなどの言語を選んでおけばとも後々思ったりもしたが、
今の仕事をしている段階では全く役に立たないとも思ってはいない。
幅広い視点を持つことは全然問題ないことでもあるし、
情報系などの大学も出ていない私にとってJavaは言語の基礎を教えてくれたと言っても過言ではないだろう。
その後で、自分で調べて学べば良いという思考になったので、あとは何も問題ない。
話を戻すと、Javaを中心に受講させてもらい、オマケ程度にアプリ作成といった感じで半年ほどで受講を修了した。
コンパイルとは何か。
2進数、16進数、、、
機械語とは。
などなど
少しアカデミックなことを触れてコーディングに入っていき、
演習問題の繰り返しだった。

初めての転職フェア

そんなこんなで1社目はさっさと辞めてしまわないと辞めづらく、精神的にももたないので、
転職ができなくても辞めるつもりでいた。
とはいえのんびり転職を待つのもよくないので、
先ずは転職フェアに参加してみることにした。
知っている人なら知っている転職フェア。
あれはいわゆるSESフェア。
まあ、わかるとは思うが行かない方が良い。
分からない人にとってはIT業界ってああいうところなんだと洗脳されかける。

面接を受けてみたり、エントリーをしてみるがが上手くいかない。

コールセンター(テクサポ)へ

そんなこんなで仕事をしないのもよくないので、
コールセンターへ流れ着く。
転職後はほとんどをコールセンターで過ごすことになる。

朝活でPHP

コールセンターにずっといるわけにもいかないので、
朝の喫茶店で毎日就業前にPHPの勉強をすることにした。

緊急事態宣言を抜けてのベンチャーへ

色々あってコールセンターを行き来するのだが、
ようやくベンチャーでテスター業務から携わることになるわけだった。
それができたのも、
結局はReactでFirebaseにデプロイまで行けた知識を持てたということ。
コードを書いているだけでは話が薄っぺらくなってしまう。
そして、入社して思うのは実務経験に勝るものはないということ。
お金を貰いながらその業務に専念することでのスキルアップは格段に違う。
ようやく、ポジションに来れたのだから、
コーディングだけでなく、テストケースの作成、サーバー、インフラ側の知識、RDBやAPIのことなど、
いままで自分だけでは時間を割くことができなかったことにもやっていくことができるのではないだろうか。
周囲の優秀なエンジニアに囲まれて仕事ができることがいかに幸せなことか、
昨今の転職の大変さを聞いてよく身に染みる。

今も少しずつコーディングをしているが、
EC2ぐらいを使って繋げられたらと思っている。

JenkinsやCircleciを使って自動テストの知識についても増やしていきたい。
AWSを学ぶのならDockerもやりたいわけだ。

学ぶことは多い。
フロントの勉強をしていると、
限界を感じることが多く、
向上心があればどうしてもインフラ周りにいきたくなる。
なるほど、フロント側にフルスタックが多いのも納得と思った今日この頃。

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

JavaScript、CSSのコード検証、整形ツールのまとめ

目的

開発に便利なJavaScript(JS)とCSSのコード検証、整形ツールに関する情報を要点を抑えて簡単にまとめる。
参考になりそうな記事もまとめて網羅的に調べやすい記事にしたい(更新していく)。

検証ツール?

検証ツールを使うことでコードが実行可能かどうかを検証できる。文法的に書き方が間違っているところなどが可視化されるため開発が捗る。

JSの検証ツール

npm trendsによると、主要なJSの検証ツールの中で現状ではほぼESLintが使われている。なのでESLintのみまとめました。

js_lint2021.png

すごい人気ダァ。

ESLint

概要

JSのコードの検証ツールとして2021年時点で一番人気。部分的に整形も可能。
2021年2月現在のバージョンは7.20.0。
エディタ上で検証結果をリアルタイムに表示させるためにエディタの拡張機能をインストールするのが良いでしょう。

主な機能、仕様としては以下

  • エラーとするかどうかの検証ルールを自由にon/off可能
  • 検証基準ルールが豊富、さらにたくさんのプラグインが公開されている
  • 限定的だが整形ツールとしても使える
  • 自分のプロダクトに合わせた独自ルールを作成することができる
  • ECMAScript2015(ES6)以降も標準サポート。
  • JSX記法もサポートしている。

ESLint参考記事

  1. ESLint 最初の一歩
    最初に読むならこの記事。ある程度の使い方はほぼわかるんじゃないかなって。

  2. Step by Stepで始めるESLint
    かなり詳しく書いてあるのでもっと詳しく調べたいならこの記事。設定ファイルの種類、ルール設定の記述の方法など丁寧に書いてある。

CSSの検証ツール

npm trendsを見ると主要なパッケージの中ではstylelintが圧倒的にダウンロードされている模様。てことでstylelintだけまとめていく。

css_lint2021.png

stylelint

概要

CSSのコードの検証ツールとして2021年2月時点で一番人気。
デフォルトの状態ではルールは設定されていない。ルールを一つ一つ設定するのも良いが、ざっくりオススメルールを導入したい場合は拡張機能のstylelint-config-standardを導入するのが良い。
ESLintと同様に、エディタ上で検証結果をリアルタイムに表示させるためにエディタの拡張機能をインストールするのが良いでしょう。

主な機能、仕様としては以下

  • CSSは当然として、SCSS、Sass、Less、SugarSSも可能。
  • 170を超える検証ルールがある
  • 豊富なプラグインによる機能拡張
  • プラグインを入れると自動整形も可能

stylelint参考記事

  1. stylelintの導入方法と各ルール解説
    タイトル通り導入方法や代表的なルールの解説の記事です。

  2. ここがすごいぞ! stylelint!
    stylelintの概要、良いところなどがまとまっている。ESLintに書き方が似ているのは確かに助かりますね。stylelint-config-standard、PostCSSについて、ルール違反時のメッセージのカスタマイズなどの情報もまとめてある。2016年の記事なので若干古い箇所もあるやも。

  3. stylelintのorderモジュール選定
    cssのプロパティを自動ソートする拡張機能についての記事。コードが見やすくなるので是非導入したいですね。
    選ぶモジュールとしてはstylelint-config-recess-orderが良いのでは、と締めてある。

  4. stylelintを使ってCSSプロパティのソートと整形を自動化する
    自動整形の手順が紹介されています。VSCodeの拡張機能stylelint-plusの導入が必要。

整形ツール?

設定したルールに基づいてコードを自動整形してくれるツールです。
と言ってもESLint,stylelintのどちらも自動整形できるのでは・・・??とここまで調べていて思ったのですが、整形ツールを用いてでしか整形できないコードがあり、手軽で確実というメリットがあるようです。
今のところNode.js上で動く一番人気の整形ツールはPrettierのようです。他の記事がほとんど見つからなかったのでこちらもPrettierのみまとめます。

Prettier

2021年2月最新バージョンは2.2.1。
ESLintを整形ツールとして利用するには細かく設定が必要なため、ファイルが膨れ上がり、メンテナンスも大変となります。そのため検証はESLint、整形はPrettireで行うのが良さそう。
ただし設定によってはESLintと干渉しあってうまく働かないので注意が必要。

主な機能、仕様としては以下

  • JavaScriptやCSSだけでなく様々な形式をサポートするコードフォーマッター。
  • 設定が少なく単一で使うならすぐに導入することができる。

Prettier参考記事

  1. Prettier 入門 ~ESLintとの違いを理解して併用する~ 導入方法の紹介、整形ツールを導入する理由やESLintにPrettierを併用する理由がまとめられています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npmとYarnのまとめ記事、Node.jsを添えて

目的

Yarnやnpm(node package manager)に関する情報を要点を抑えて簡単にまとめる。
Yarnやnpmに関する記事は既にたくさんあるので参考になりそうな記事もまとめて網羅的に調べやすい記事にしたい(いろいろ動きがあれば更新していく)。

npm?

  • npmはNode.js(後述)のパッケージ管理ツール。
  • 必要なパッケージをインストールする際に、そのパッケージが動くために必要な他のパッケージも合わせてインストールすることができる。
  • モダンな開発を行いたいなら必須。

参考記事

フロントエンド開発の3ステップ(npmことはじめ)
npmの導入方法などのシンプルでわかりやすい記事。

Node.js?

  • ブラウザ上でしか動けなかったJavaScriptをパソコン上で動かせるようにする。
  • JavaScript実行環境。プラットフォーム。Webサーバの役割を担うことができる。
  • 開発に便利なパッケージを使うのに必要。

参考記事

Node.jsとはなにか?なぜみんな使っているのか?
Node.jsを利用することでフロント側でできることが増え、開発を効率的に行えるようになった。
その背景を説明しています。

Yarn?

  • npmとほぼ同じような用途に使う。
  • npmと互換性があり、npmで使用していたプロジェクト設定ファイル(package.json)がそのまま使える。
  • npmと比較するとインストールが速い、セキュリティが高いという特徴がある。
  • npmとコマンドが似ているので学習コストは低い。

Yarnをインストールするためにnpmをインストールするってなんか変な感じですよね。

参考記事

npmとは yarnとは
npmとyarnの違いを箇条書きで簡潔に紹介しています。

yarnチートシート
Yarnのコマンドがまとめられてます。npmのコマンドと似ているので導入しやすい!2017年の記事。

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

DenoがTypeScriptの使用をやめる5つの理由

deno-will-stop-using-typescript.png

前書き

この記事は翻訳記事になります。 :point_up_tone1:

近年、JSで書かれてるプロジェクトをTSに書き直すことが業界内で一種の風潮になって、
この記事で敢えてTSからJSに戻そうとする事例が目新しいと思ったので、翻訳してみました。

出処: 5 reasons why Deno will stop using TypeScript - StartFunction
原作者: eliorivero
Denoの紹介:
V8 JavaScriptエンジン及びRustプログラミング言語に基づいた、
JavaScript及びTypeScriptのランタイム環境である。Node.jsの作者であるライアン・ダールによって作成され、セキュリティと生産性に焦点を当てている。 --ウィキペディア

DenoがTypeScriptの使用をやめる5つの理由

最近、Denoが内部コードでTypeScriptの使用を停止することを指摘する文書が浮上しました。
言及されている問題には、TypeScriptのコンパイル時間、構造化、コード編成などが含まれます。
今後、Denoは内部コードに純粋なJavaScriptを使用されるそうです。

TypeScriptを使用するDenoの既存問題

現在、DenoチームがTypescript使用上、好ましくないと思う主な理由は以下になります。

  • ファイルを変更するときのTypeScriptのコンパイル時間は数分かかるため、継続的なコンパイルは非常に遅くなります。

  • 実際のDeno実行可能ファイルを作成するソースファイルで使用しているTypeScript構造と、ユーザー向けAPIがランタイムパフォーマンスの問題を引き起こしています。

  • TypeScriptは、Denoコードの整理に役立ててない。それどころか、逆効果とも言える。
    言及された問題の1つは、2つの場所で重複した独立したBodyクラスになってしまったことです。

  • TypeScriptコンパイラはd.tsファイルの生成に役立たないため、内部コードとランタイムTypeScript宣言は手動で同期を保つ必要があります。

  • 2つのTSコンパイラホストを維持しています。
    1つは内部Denoコード用で、もう1つは外部ユーザーコード用ですが、どちらも用途としては近いです。

DenoコードからTypeScriptを削除する

Denoチームの目的は、ビルド時の全ての内部DenoコードのTSの型チェックを外すこと。
彼らは、すべてのランタイムコードをJavaScriptファイルに移動する。
ただし、タイプ定義とドキュメントを保持するために、d.tsファイルを引き続き使用します。

Denoチームは内部のコードに対してのみ、TypeScriptの使用を停止することに言及するが、
Denoのユーザーは引き続きTypeScriptを使用できますし、タイプチェックももちろんされます。

TypeScriptはJavaScriptの改良版と見なされることもありますが、実際はそうではない。
他の言語と同じように欠陥があります、最も重要なものの1つは、コンパイル時間が遅いことです。
小さなプロジェクトでは、純粋なJavaScriptからTypeScriptに切り替えるときにコンパイル時間が大幅に増加することはないかもしれませんが、複雑な、例えばReactのような大規模なプロジェクトでは顕著になります。
ランタイムのサイズが大きいことを考えると、DenoがTypeScriptを止めるのも当然のことです。

開発中の型チェックは、コンパイル時にコストがかかります。
TypeScriptプロジェクトに、コンパイル時間に対処して改善する方法に関する多くのドキュメントがあるのはそれなりの理由があります。
最も興味深いアプローチの1つは、プロジェクト参照を使用することです。
これにより、開発者はTypeScriptの大きなコードを小さなパーツに分割できます。

DenoがTypeScriptの使用をやめる理由についてもっと読む

TypeScriptを内部Denoコードから削除し、代わりにJavaScriptを使用するという決定についての完全な議論は、このドキュメントにあります。
そこには、Ryan Dahlと共同研究者が、問題、その解決策、およびその実装方法について説明しています。

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

Google Apps Script + LINE Messaging API + JavaScriptで誕生日リマインダーのLINE BOTを作成してみた。

こんにちは、フロントエンドエンジニアのうるです。
今回はタイトルにあるようにGoogle Apps Script + LINE Messaging API + JavaScriptで誕生日をリマインドしてくれるLINE BOTを作ってみました。

リポジトリはこちら

qiita8.jpg

作成の背景

ある日Qiitaで見つけた以下の記事を見てフロントエンドエンジニアながら、LINEのBOTを作ってみたいと思い挑戦してみることにしました。
プログラマーとして初めて親の役に立つものを作った話(LINEbot)

自分は全くGASやLINE Messaging APIに知識が無かったので、Udemyで良さげな講座を探していると、Taka ponさんの講座を発見し受講することでかなり参考にさせていただきました。

参考講座一覧

使用技術

  • Google Chrome
  • Google Apps Script(以下、GAS)
  • Google Apps Script GitHub アシスタント(Google Chrome拡張機能)
  • Google スプレッドシート(データベースとして使用)
  • LINE Messaging API
  • JavaScript(主にJavaScriptで記述するのですが、一部使えない操作やGASで使える有益な操作も含みます。)
  • GitHub, SourceTree

作成手順

①LINE Messaging APIの準備

LINE Developersのページに移動し、自身のLINEアカウントでログインしてください。
(LINEのアカウントを持っていなければ、多分作る必要があるかと思います。)
qiita1.jfif

色々な認証を終えると、以下のコンソール画面にたどり付きます。
スクリーンショット 2021-03-01 14.51.21.png

ここから
1. プロバイダの作成
2. チャンネルの作成
3. チャンネルアクセストークンの発行(実装時に使用)
4. webhookの有効化
などの作業が発生します。

ここら辺は参考講座一覧の講座を見てもらえると分かりやすいかも。

②Google Apps Script(GAS)の準備

今回はスプレッドシートをデータベースとして使用するので、スプレッドシートと紐づくようにGASを起動します。
Google Driveを開いて、左上の「新規作成」→「Google スプレッドシート」から新しいスプレッドシートを開きます。また、このスプレッドシートのシートIDというIDを実装時に使用します。

そして、以下の画像のように「ツール」→「スクリプトエディタ」からGASを開きます。
qiita2.jpg

③GitHubと連携

GitHubのアカウントを持っていない&知らないなど、必要を感じなければここの操作は必要ないかと思います。

(1)Chromeの拡張機能「Google Apps Script GitHub アシスタント」をダウンロード

Google Apps Script GitHub アシスタントを追加することで連携が可能になりますので、ダウンロードします。
Google Apps Script GitHub アシスタントはこちらから

(2)ダウンロードすると、ツールバーに「Login SCM」という項目が出てくるのでここから自身のGitHubと連携

(項目が出てこない場合はChromeを一度終了して再起動させるなどを試してみてください)
詳しくはこちらの記事を参考に。

連携が成功すると、以下の画像のようにツールバーの「Repository」や「Branch」から好きなリポジトリやブランチを選択できるようになります。

簡単な操作方法としては「Branch」の項目の横の「↑」でpush、「↓」でpullの操作ができます。

自分は連携させたリポジトリをSourceTreeでクローンして、差分の管理をしていました。
qiita3.jpg

④誕生日リマインダーのコードを記述

以下は、実際のコードです。
(至らない記述があるかと思いますが、その場合はご指摘などいただければと思います。)

(1)誕生日の追加・削除・一覧の表示機能(main.gs)

  1. doPost関数でユーザーの入力をJSONで受け取りparseして、必要なデータを取り出す。
  2. 現在の処理をconst cache = CacheService.getScriptCache();でキャッシュしておく。
  3. 誕生日の追加・削除・一覧の表示を行う。

また、コード最下部のreply関数でユーザーに返信しています。

main.gs
// 初期設定
const CHANNEL_ACCESS_TOKEN = 'チャンネルアクセストークンを入力';
const URL = 'https://api.line.me/v2/bot/message/reply';
const SHEET_ID = 'スプレッドシートのIDを入力';
const SHEET_NAME = 'birthdays';
const SPREAD = SpreadsheetApp.getActiveSpreadsheet();
const SHEET = SPREAD.getSheets()[0];
// 誕生年があるものとないもの
const dateExp = /1?\d\/[123]?\d/;
const dateExpYear = /[19|20]\d{2}\/1?\d\/[123]?\d/;

//doPost関数(Lineからメッセージを受け取る)
function doPost(e) {
  //メッセージ受信
  const data = JSON.parse(e.postData.contents).events[0];
  //ユーザーID取得
  const lineUserId = data.source.userId;
  //リプレイトークン取得
  const replyToken= data.replyToken;
  //送信されたメッセージ取得
  const postMsg = data.message.text;

  // リプライトークンが無かったら処理を止める
  if(typeof replyToken === 'undefined') {
    return;
  }

  // キャッシュを設定
  const cache = CacheService.getScriptCache();
  let type = cache.get("type");

  // 処理を分ける
  if(type === null) {
    if(postMsg === '誕生日の追加') {
      cache.put('type', 1);
      reply(replyToken, '追加する人の名前を入力してください');
    } else if(postMsg === '誕生日の削除'){
      cache.put('type', 3);
      reply(replyToken, '削除する人の名前を入力してください')
    } else if(postMsg === '誕生日の一覧'){
      reply(replyToken, showBirthdaysList());
    } else {
      reply(replyToken, '「誕生日の追加」、「誕生日の削除」、「誕生日の一覧」のいずれかを入力してください');
    }
  } else {
    // 処理途中で追加のキャンセル
    if(postMsg === 'キャンセル') {
      cache.remove('type');
      reply(replyToken, '誕生日の追加をキャンセルしました');
      return;
    }

    switch(type) {
      // 誕生日の追加処理
      case '1':
        cache.put('type', 2);
        cache.put('name', postMsg);
        reply(replyToken, '追加する誕生日を「1996/12/20」の形式で入力してください \n 誕生年は無くても構いません');
        break;
      case '2':
        if(postMsg.match(dateExp || dateExpYear)) {
          cache.put('date', postMsg);
          addBirthday(cache.get('name'), cache.get('date'), lineUserId);
          reply(replyToken, `${cache.get('name')}さんの誕生日を${cache.get('date')}で登録しました`);
          cache.remove('type');
          cache.remove('name');
          cache.remove('date');
          break;
        } else {
          reply(replyToken, '正しく入力してください。「キャンセル」で処理を中止します');
          break;
        }

      // 誕生日の削除処理
      case '3':
        if(checkName(postMsg) !== -1) {
          deleteBirthday(checkName(postMsg));
          reply(replyToken, `${postMsg}さんの誕生日を削除しました`);
          cache.remove('type');
          break;
        } else {
          reply(replyToken, `${postMsg}さんの誕生はありませんでした`);
          cache.remove('type');
          break;
        }
    }
  }
}

// 誕生日の追加
function addBirthday(name, date, lineUserId) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const splitedDate = date.split('/');
  let year, month, day;

  if(splitedDate.length === 3) {
    year = splitedDate[0];
    month = splitedDate[1];
    day = splitedDate[2];

    sheet.appendRow([
      name,
      year,
      month,
      day,
      lineUserId
    ]);
  } else {
    year = '';
    month = splitedDate[0];
    day = splitedDate[1];

    sheet.appendRow([
      name, 
      year,
      month,
      day,
      lineUserId
    ]);
  }
};

// 誕生日の検索
function checkName(name) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const values = sheet.getDataRange().getValues();
  const nameList = [];
  // 初期値に-1を入れておく、-1がそのまま返ってきたら該当するユーザーはいなかったということ
  let nameIndex = -1;

  // nameListに全ての名前を入れていく
  for(let i = 0; i < values.length; i++) {
    nameList.push(values[i][0]);
  }

  // 一つずつnameとマッチするか確かめていく
  nameList.forEach((e, i )=> {
    if(e == name) {
      nameIndex = i;
      return;
    }
    return;
  });

  return nameIndex;
}

// 誕生日の削除
function deleteBirthday(rowNumber) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  sheet.deleteRows(rowNumber + 1);
}

// 誕生日の一覧
function showBirthdaysList() {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const values = sheet.getDataRange().getValues();
  const ranges = sheet.getRange(2, 1, values.length - 1, 4).getValues();

  // 返信用のフォーマットに変換する
  const dataList = ranges.reduce((list, item) => {
    if(item[1] === '') {
      return `${list}\n${item[0]} : ${item[2]}${item[3]}日`;
    } else {
      return `${list}\n${item[0]} : ${item[1]}${item[2]}${item[3]}日`;
    }
  }, '名前 : 誕生日');

  return dataList;
}

// 返信機能
function reply(replyToken, message) {
  UrlFetchApp.fetch(URL, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': message,
      }],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

(2)誕生日のpush通知機能(push.gs)

続いては、誕生日をpush通知する機能です。

  1. トリガーを設定することで、毎日0時~1時の間にスプレッドシートを探して誕生日の人を探してくる。
  2. 誕生日の人がいれば、入力を行ったユーザーに対してpush通知を行う。
push.gs
//アクセストークン
var ACCESS_TOKEN = "チャンネルアクセストークンを入力";

//送信先の処理
function pushMessage() {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const index = findUser();

  // 誕生日が無い場合は早期リターン
  if(index === -1) {
    return;
  }

  const person = sheet.getRange(index + 1, 1).getValue();
  const to = sheet.getRange(index + 1, 5).getValue();
  const year = new Date().getFullYear();
  let theYear = sheet.getRange(index + 1, 2).getValue();
  let message = "今日は";
  let age;

  if(theYear) {
    age = year - theYear;
    message += `${person}さんの${age}歳の誕生日です!`;
  } else {
    message += `${person}さんの誕生日です!`;
  }

  //メッセージ送信処理
  return push(message, to);
}

function findUser() {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const values = sheet.getDataRange().getValues();

  // 比較するときの様式を合わせる
  const birthdaysList = values.map( row => {
    return `${row[2]}/${row[3]}`;
  });

  // 比較するときの様式を合わせる
  const today = new Date(); 
  const hour = today.getHours();
  const month = today.getMonth();
  const date = today.getDate();
  // timeZoneをAsia/Tokyoに変更
  const theDay = `${month + 1}/${date}`;

  // 記述方法がこれでないと動かないっぽい
  const BirthdayIndex = birthdaysList.findIndex((el) => el == theDay );

  // デバッグ用
  // const BirthdayIndex = birthdaysList.reduce((accu, curr) => {
  //   return `${accu}\n${curr}`;
  // }, theDay);

  return BirthdayIndex;
}

//PushMessageの作成
function push(message, to) {

    var url = "https://api.line.me/v2/bot/message/push";
    var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + ACCESS_TOKEN,
    };
    var postData = {
    "to" : to,
    "messages" : [{
      'type' : 'text',
      'text' : "Today is your friend's Birthday!!",
    },{
      'type' : 'text',
      'text' : message,
    }],
    };
    var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
    };
    return UrlFetchApp.fetch(url, options);
}

以上です!
丸々、コピーで動くかとは思います。(チャンネルアクセストークンやシートIDなどは設定する必要がありますが)

ちなみにデータベースとしてのスプレッドシートはこんな感じ
qiita4.jfif

個人的にハマった点

①Google Apps Script GitHub アシスタントをGoogleアカウントがブロックしてしまう

なぜか、Google Apps Script GitHubアシスタントを使用しようとすると「この拡張機能は危険なのでブロックしました」的なメッセージとともに使用できない期間がありました。
色々、ググって解決を試みるも上手くいかず。。
ここで1~2週間ほど作業が止まってしまい、諦めかけたある日突然使えるようになりました。

あれはなんだったんでしょうか、、??
解決方法を提供できないハマりポイントでした。。

②アメリカ時間を取得してくるGAS

push.gs内のJavaScriptで今日の日付を取得して、スプレッドシート内に該当の誕生日があればpush通知を送信する機能ですが、初期のころはなぜかアメリカ(東部?)時間を取得してくるので、日付が1日ズレる事象が起こりました。

解決方法

いつのタイミングかはハッキリとは分からないですが、appsscript.jsonというJSONファイルが生成されており、ここにtimeZoneが記述されていました。
timeZoneがAmerica/New_Yorkになっていたので、Asia/Tokyoに変更で無事解決。
設定しないとファイル自体が表示されないので詳しくはググってみてください。

appsscript.json
{
  "timeZone": "Asia/Tokyo",
}

最後に

今回の誕生日リマインダーの主な欠点としては、
データベースが1つのスプレッドシートを用いている点だと思います。
なので、一般公開をすることができません。
色んな人の誕生日がゴッチャに入ってしまいますからね、プライバシー的に。
そもそも、GASは小規模なサービス向けなので根本的に一般公開は難しそうです。

でも個人的には満足しているので問題なしです!!

そして、Udemyで受講させていただいたTaka ponさんに感謝です。

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

Google Sheet API(Node.js利用)で、複数のシートを順番に生成できずハマったので、解決したコードを晒してみる

Google Sheet API(Node.js利用)で、複数のシートを「1,2,3,4...」と並べて生成したいのに、
async,awaitで生成しても、シートが2,4,3,1みたいに順不同になってしまうのを、
どう回避できるかなかなかわかりませんでした。

辿り着いた解決策は、
array.reduceとPromiseを組み合わせて、thenでつないでいき、
Promise内でsetTimeoutをする、というものでした。

reduceに慣れていなかったので、
4つの引数の理解(特に第1,4引数)と、thenの理解、
また、初期値にPromice.resolve()を渡すあたりが難しかったです。

以下、実際に動くようになったコードです。
(SheetAPIのラッパークラスを自作していますので、よしなに読み替えてください)

import {Sheet} from './Sheet.mjs'//シート関連の処理を集めた自作クラス
import dotenv from 'dotenv'
dotenv.config()

const spreadsheetId = process.env.SOME_SPREADSHEET_ID

/**
 * 複数のシートを順番に作成する
 */
const main = async function() {
  const sheet = await Sheet.build()//auth取得などはここで処理している
  //複数シート作成のため連番を準備(スプレッド構文便利ね)
  const arr = [...(Array(10).keys())].slice(1)//0は排除

  //関数の中でPromiseオブジェクトを返す無名関数を準備。これをarr.reduceで逐次実行する
  const createWithSleep = (sheetName) => {
    return () => {//ここで関数化してPromiseを返すのがreduce.then実行のキモ。無いと動かなかった
      return new Promise(resolve => {
        setTimeout(()=>{
          createSheet(sheetName, sheet)//シート生成メソッド呼び出し
          resolve()//resolve()でreduce内のthenが発火
        }, 2000)//thenで実行していても、一定の時間差がシート順次生成に必要!500などではだめだった
      })
    }
  }

  //ここのarr.reduceでの逐次実行がとても重要!これがないとシートの生成が順不同になる(thenでの処理実行チェーン)
  //シート名は's2'などでは読み取りなどのAPI実行時にカラム名と間違えられ困ったので、'組'というダブルバイトにしている
  arr.reduce((pre, cur) => pre.then(createWithSleep('' + cur)), 
    Promise.resolve())//最初にthenを発火させるために空のPromise.resolve()をinitialValueとして配置
}

//シートの生成部分. setTimeoutで十分時間をとっているのでasync,awaitは設定せず
const createSheet = (sheetName, sheet) => {
  //ここのcreateSheetは普通のAPIメソッドの単なるラッパーです
  sheet.createSheet(sheetName, spreadsheetId).then(() => console.log('created'))
}

main()

Google Sheet APIは、サクッとspreadsheetを作れたり、値を書き込んだり読み込んだり、
sheetを作成したり、超便利ですね。
nodeでローカルから楽しく操作できるのが気に入りましたが、
ちょいちょい書き方に詰まったので、同じところで詰まった方に役に立てばと、
恥を忍んでコードを晒してみます。

どなたかの役に立てば幸いにて。
ではでは。

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

Google Sheet API(Node.js利用)で、複数シートを順番に生成できずハマったので、解決コードを晒してみる

Google Sheet API(Node.js利用)で、複数のシートを「1,2,3,4...」と並べて生成したいのに、
async,awaitで生成しても、シートが2,4,3,1みたいに順不同になってしまうのを、
どう回避できるかなかなかわかりませんでした。

辿り着いた解決策は、
array.reduceとPromiseを組み合わせて、thenでつないでいき、
Promise内でsetTimeoutをする、というものでした。

reduceに慣れていなかったので、
各引数の理解(特に第1とinitialValue)と、thenの理解、
また、初期値にPromice.resolve()を渡すあたりが難しかったです。

以下、実際に動くようになったコードです。
(SheetAPIのラッパークラスを自作していますので、よしなに読み替えてください)

import {Sheet} from './Sheet.mjs'//シート関連の処理を集めた自作クラス
import dotenv from 'dotenv'
dotenv.config()

const spreadsheetId = process.env.SOME_SPREADSHEET_ID

/**
 * 複数のシートを順番に作成する
 */
const main = async function() {
  const sheet = await Sheet.build()//auth取得などはここで処理している
  //複数シート作成のため連番を準備(スプレッド構文便利ね)
  const arr = [...(Array(10).keys())].slice(1)//0は排除

  //関数の中でPromiseオブジェクトを返す無名関数を準備。これをarr.reduceで逐次実行する
  const createWithSleep = (sheetName) => {
    return () => {//ここで無名関数化してPromiseを返すのがreduce.then実行のキモ。無いと動かなかった
      return new Promise(resolve => {
        setTimeout(()=>{
          createSheet(sheetName, sheet)//シート生成メソッド呼び出し
          resolve()//resolve()でreduce内のthenが発火
        }, 2000)//thenで実行していても、一定の時間差がシート順次生成に必要!500などではだめだった
      })
    }
  }

  //ここのarr.reduceでの逐次実行がとても重要!これがないとシートの生成が順不同になる(thenでの処理実行チェーン)
  //シート名は's2'などでは読み取りなどのAPI実行時にカラム名と間違えられ困ったので、'組'というダブルバイトにしている
  arr.reduce((pre, cur) => pre.then(createWithSleep('' + cur)), 
    Promise.resolve())//最初にthenを発火させるために空のPromise.resolve()をinitialValueとして配置
}

//シートの生成部分. setTimeoutで十分時間をとっているのでasync,awaitは設定せず
const createSheet = (sheetName, sheet) => {
  //ここのcreateSheetは普通のAPIメソッドの単なるラッパーです
  sheet.createSheet(sheetName, spreadsheetId).then(() => console.log('created'))
}

main()

Google Sheet APIは、サクッとspreadsheetを作れたり、値を書き込んだり読み込んだり、
sheetを作成したり、超便利ですね。
nodeでローカルから楽しく操作できるのが気に入りましたが、
ちょいちょい書き方に詰まったので、同じところで詰まった方に役に立てばと、
恥を忍んでコードを晒してみます。

どなたかのお役に立てば幸いにて。
ではでは。

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

VueCLI + javascript環境で、WebWorkerを使用する

web workerでの記事は、webpack環境(nuxt.js環境)や、typescriptでの記事が多く存在し、
所々詰まっていたので記事にします。
Web Worker の使用(MDN)
Vue.js で WebWorker を使う
laravel-mix + Vue.js (ES) + WebWorker (TS)
これらの記事を参考にしました。

今回作成したgitリポジトリ

導入

vue ui 等でvueを立ち上げたものとして、
プロジェクト直下で
npm install worker-loader
とし、worker-loaderを導入します。

そして、プロジェクト直下のvue.config.js(無ければ作成し)に、
下記の内容を記述します

vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('worker-loader')
      .test(/\.worker\.js$/)
      .use({
        loader: 'worker-loader',
        options: {
          inline: true
        }
      })
      .loader('worker-loader')
      .end()
  }
  //...
}

実行準備

次に、srcフォルダ直下に
workersというフォルダと、worker1というjsファイルを作成します。
(※名称はなんでも良いと思います。また、フォルダは存在していなくても良いと思います。)

src
 |-assets
 |-components
 |- ...
  |-workers
     |-worker1.js
 |-App.vue
 |-main.js

このような構成になるかと思います。
次に、worker1.jsの中身を

worker1.js
addEventListener('message', e => {
    const { data } = e
    if (data && typeof data == 'number') {
        return postMessage(data*data)
    } else {
        return postMessage(10)
    }
})

export default {}

としてみます。
workerに投げられた変数が、int型の場合、二乗を返し、
それ以外の場合10を返す関数です。

次に、実際にvueファイルでworkerを呼び出します。
試しに、vuecliで生成されるHome.vueに書いていきます。

Home.vue
<script>
import Worker1 from 'worker-loader!@/workers/worker1'
export default {
  name: 'Home',
  components: {
  },
  data () {
    return {
      reload: 0
    }
  },
  methods: {
    workerTest: function () {
      const worker = new Worker1()
      worker.onmeessage = e => {
        const { data } = e
        this.reload = data
        worker.terminate()
      }
      worker.postMessage(20)
    }
  }
}
</script>

このようにします。
import文に、worker-loader!をpath名の前に挿入することに気をつけ、また、
worker.onmessageで、workerの処理が終わった場合(つまりpostMessageが返った場合)の処理を記述します。
今回の場合は、処理が終わり次第、this.reloadに代入し他で参照できるようにします。
また処理を終えたあとにworkerが存在する意味はないので、terminateで削除します。
(※importがlintに怒られるかもしれないので、その場合はruleに記述しましょう...)

実行

Home.vue
<template>
  <div class="home">
    <button @click="workerTest()">worker!!</button>
    {{ this.reload }}
  </div>
</template>

実際に、このようにして試してみると...

スクリーンショット 2021-03-01 18.51.46.png

スクリーンショット 2021-03-01 18.51.23.png

実際に、workerを通して、値が変更されたのがわかります。
また、

Home.vue
workerTest: function () {
      const worker = new Worker1()
      worker.onmessage = e => {
        const { data } = e
        this.reload = data
      }
      worker.postMessage(20)
    }

とし、
開発者ツールのSourcesをみると
スクリーンショット 2021-03-01 18.53.44.png
実際にworkerが立っているのがわかります。

other

ループの中で実行

試しに

Home.vue
workerTestLoop: function () {
      for (let i = 0; i < 10; i++) {
        const worker = new Worker1()
        worker.onmessage = e => {
          const { data } = e
          this.reload += data
          worker.terminate()
        }
        worker.postMessage(20)
      }
    }

とし、先ほど同様に実行してみると、
実際に、先ほどの数字が4000(400 * 10)となっているのがわかります。
スクリーンショット 2021-03-01 18.59.49.png

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

== null よりも ===null と === undefined を使おう

Re: === undefined よりも == null を使おう - Qiita

=== undefined よりも == null を使おう - Qiita

こちらの記事の反対のスタイルでコード書いています。コメント欄に書くとだらだらと長くなるので記事にします。

等価演算子「==」をnullのときだけ使うという謎の風習が以前からあって、そうすると業界に曖昧コードが増えてしまうのは残念なので書きます。

nullとundefined - TypeScript Deep Dive 日本語版

== nullを使ってundefinedと nullを両方ともチェックすることを推奨します。一般的に2つを区別する必要はありません。

どこの一般なんだろうか。プログラマ以外の開発者ではない一般ピープルのことか?

曖昧判定「==」

JavaScript で多くのミス、不具合、バグ、が曖昧判定からきています。
「==」と「===」の違いです。
「==」は等価演算子。曖昧判定で、JavaScriptでは全ての場面で不要です。
「===」は厳密等価演算子。厳密判定で、JavaScriptではこちらを使いましょう。

ある値が、1 (数値)もしくは '1' (文字列) のどちらかが入っているかわからないけど、なんとなく動くコードというのは次のように書くことができます。

if (value == 1) {
  // value が数値でも文字列でも動く
}

これは、あまり推奨されません。valueが数値か文字列かどちらかわからないという状態では開発するのが難しいです。

if (value === 1) {
}
// もしくは
if (value === '1') {
}
// ちゃんと区別して書こう

null と undefined も同じ理由で区別する方が望ましいです。

null と undefined を一緒くたに扱うと発生するバグ

関数引数のデフォルト値指定や、分割代入のデフォルト値指定では、nullは無視され、undefinedだけがデフォルト値指定されるので、nullとundefinedは明確に区別しておいたほうがいいです。

簡単に例を書きます。

const result = funcA();
if (result == null) {
  funcB(result);
}

function funcB(value = '無効です') {
  console.log(value)
}

この場合、funcAで値やオブジェクトなどが返る場合はfuncBは実行されず、undefinedが戻るとき「無効です」と表示され、null の場合は「null」と表示されます。

何も実行されないのか、「無効です」か、の2種類ではなく「null」が表示される場合があります。よく知ってないと不具合につながるようなあぶない挙動です。

なので、funcAの戻り値に、nullを返すのかundefindeを返すのかは、重要な問題になります。

一文字一句間違えてはいけない正確にコードかかなければいけないのがプログラマの仕事です。null と undefined という別の値を似たようなものとして混ぜてしまうと、プログラムが正確に動きません。

「==null」で分岐しているコードを読んでも、判定後の値がnullなのかundefinedなのかを読み取ることはできません。わざわざ動かさないと本当の値がわかりません。リファクタリングするときに、result がnullかundefinedか不明なので、関数のデフォルト値判定で使えないことになるので、リファクタリングしにくいコードになります。

null と undefined は同じものではないのです。同じものではないのに、「==null」として同じものとして判定する必要ないです。

対策

大抵の場面は

if (result === null) {
  funcB();
}

if (result === undefined) {
  funcB();
}

どちらかで済みます。正しくどちらかを書きましょう。

厳密に「==null」と同じことをしたいのなら

if (result === null || result === undefined) {
  funcB();
}

このように書いておくとよいです。
この書き方は、冗長なのではなく書いた人の意図が正しく伝わるようになっているので、「==null」よりも読みやすくよい書き方です。文字数が少ないのがよいコードなのではありません(書くときにさぼってるだけ)。文字数が多かろうが少なかろうが読みやすいコードがよいコードです。

この書き方なら null か undefinedか、どちらかを判定しているのだな、と確実にわかるからです。

「==null」というコードを見ると下記のような迷いが起こります。

  • 間違って「==」を使っているのか、
  • あるいは、nullを見るために、undefinedも合わせて判定しているのか、
  • あるいは、undefinedをみるために、nullを判定しているのか、

どれを意図しているのかわからないコードになります。

参考

プログラミングでは、より短いコードで同じ目的を達成した方が、優れているのですか? - Quora

大事なこと

繰り返します。厳密等価演算子使え、というのはあらゆるところで言われていることです。

JavaScript で多くのミスが曖昧判定からきています。
「==」と「===」の違いです。
「==」は等価演算子。曖昧判定で、JavaScriptでは全ての場面で不要です。
「===」は厳密等価演算子。厳密判定で、JavaScriptではこちらを使いましょう。

単にこれを守っていればいいだけです。nullのときの例外なんて考える必要ありません。

バグだらけのプロジェクトだとコードに曖昧さがあると即死につながるので、這いつくばってでも前に進んで生き残るためには、こうした方がいいよ的な、現場からは以上です。

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

作品を評価し合うwebアプリ作ってみた[個人開発]

今回作ったもの

タイトルにもある通り、絵や音楽、プログラムなどの作品を評価し合うコミュニティサイト「PRORP」です。

4598734.png

なぜ作ったか

ヤフー知恵袋でこんな質問を見つけました。

塾に通ってるなら他人からの評価は簡単に受けれますが、独学の場合なかなかしっかりと評価を受けるときってありません。
以外に他人の評価って欲しがってる人が多いのかなと思い作りました。

工夫した点

投稿一覧ページをなくした
公開したばかりでユーザーも投稿も少ないのでユーザーが投稿が見る方法は検索だけにしました。
使いずらいですが最初はしょうがないんです。すみません(__)

★評価機能
25478.png

Raty.jsというものを使い実装しました。まあまあ簡単です。
平均値も求められます。

開発効率化のために

今回、初めて土台アプリを作ってみました。
というのもwebアプリ開発って結構同じことの繰り返しじゃないですか。CRUD、コメント機能、いいね機能、通知機能など
どんなアプリを作るにしろ必要な機能ってありますよね?
それを毎回毎回作ってたら非効率的だなと思いある程度、完成してる土台アプリを先に作りそれをコピーしてPRORPを作りました。

土台アプリでの開発の感想としてはとにかく早いです
当たり前ですが新しく1から作るよりカスタムするほうが早いので、土台アプリは作ってよかったなーと思ってます。

土台アプリに実装した機能としては

  • CRUD
  • コメント機能
  • いいね機能
  • 通知機能
  • ヘッダー、フッター、2カラムレイアウトなどのCSS
  • 新規登録、ログイン機能
  • ユーザーIDとの紐付け

くらいです。もちろん土台アプリなので汎用性重視です。

使ったgem

(省略)

gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem 'devise'
gem 'kaminari'
gem 'activeadmin'
gem 'rack-attack'
gem 'rails-i18n'
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'carrierwave'
gem 'fog-aws'
gem 'dotenv-rails'
gem 'rmagick'

まとめ

土台アプリは今度からも使っていこうと思います。
作りたいものがない、という方は土台アプリをまず作ってみるといいかもしれません。

まぁとにかくこのPRORP使ってくれ!(誘導下手)

ツイッターもやってます!
https://twitter.com/yamada1531

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

package.jsonから.envを参照したい場合

状況

package.jsonに記述したスクリプトからトークンを参照したい場合があり、以下のように直接記述するのではなく.envで管理したい。

// package.json

...
  "scripts": {
    "chromatic": "npx chromatic --project-token ABCDEFG1234"
  }
...

対応

package.jsonに埋め込めなさそうだったので、大人しく別のスクリプトファイルに切り出すことにした。

# .env

CHROMATIC_TOKEN=ABCDEFG1234
// scripts/chromatic-upload.js

const path = require("path");
const dotenv = require("dotenv");
const env = dotenv.config({path: path.join(__dirname, "../.env") }).parsed;
const exec = require("child_process").exec;

const res = exec(`npx chromatic --project-token ${env.CHROMATIC_TOKEN}`);
console.log(res);

package.jsonから呼び出すように修正。

// package.json

...
  "scripts": {
    "chromatic": "node scripts/chromatic-upload.js"
  }
...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript - file

■inputのtype=file選択時にjsでバリデーションする【vue.js】
https://taupe.site/entry/input-typefile-validation/

■JavaScriptのswitch文で正規表現を使う方法を現役エンジニアが解説【初心者向け】
https://techacademy.jp/magazine/35356

■JavaScript で URL やファイルの拡張子を取得する
https://loumo.jp/archives/23134

実際のコード

    fileTypeValidate() {
      switch(true) {
        case /.csv$/.test(this.uploadFile.name):
          console.log('csvです')
          this.getCsvData()
          break
        case /.xlsx$/.test(this.uploadFile.name):
          console.log('xlsxです')
          this.getXlsxData()
          break
        case /.xls$/.test(this.uploadFile.name):
          console.log('xlsです')
          this.getXlsData()
          break
      }
    }

this.uploadFile.nameでファイル名とれた!

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

『PythonとJavaScriptではじめるデータビジュアライゼーション』がとても良い本なので

この記事は?

題名に書いたオライリー・ジャパンから出ている本、とてもいいのですがいかんせん古い。
そのため、書籍通りにコーディングしても100%うまくいきません。

なので2021年2月時点でこう書けばうまく動く、というのを備忘録的に残しておきます。

PythonとJavaScriptではじめるデータビジュアライゼーション

対象の箇所

6章「Scrapyを使った重量スクレイピング」の
nwinners_list_spider.py
です。

wikipediaの構造が変わっているのでxpathが書籍の執筆時点と異なります。
それを考慮して、次のようにコーディングするとうまく動きました。(ノーベル賞受賞者のリストが取れた!)

import scrapy
import re

BASE_URL = 'https://en.wikipedia.org'

class NWinnerItem(scrapy.Item):
    name = scrapy.Field()
    link = scrapy.Field()
    year = scrapy.Field()
    category = scrapy.Field()
    country = scrapy.Field()
    gender = scrapy.Field()
    born_in = scrapy.Field()
    date_of_birth = scrapy.Field()
    date_of_death = scrapy.Field()
    place_of_birth = scrapy.Field()
    place_of_death = scrapy.Field()
    text = scrapy.Field()


class NWinnerSpider(scrapy.Spider):
    name = 'nwinners_full'
    allowd_domains = ['en.wikipedia.org']
    start_urls = [
        'https://en.wikipedia.org/wiki/List_of_Nobel_laureates_by_country'
    ]

    def parse(self, response):
        filename = response.url.split('/')[-1]

        h3s = response.xpath('//h3')

        for h3 in h3s:
            country = h3.xpath('span[@class="mw-headline"]/text()').extract()
            if country:
                winners = h3.xpath('following-sibling::ol[1]')
                for w in winners.xpath('li'):
                    wdata = process_winner_li(w, country[0])
                    request = scrapy.Request(
                        wdata['link'],
                        callback=self.parse_bio,
                        dont_filter=True
                    )
                    request.meta['item'] = NWinnerItem(**wdata)
                    yield request


    def parse_bio(self, response):
        item = response.meta['item']
        href = response.xpath("//li[@id='t-wikibase']/a/@href").extract()
        if href:
            url = href[0]
            request = scrapy.Request(href[0],callback=self.parse_wikidata,dont_filter=True)
            request.meta['item'] = item
            yield request


    def parse_wikidata(self, response):
        item = response.meta['item']

        property_codes = [
            {'name': 'date_of_birth', 'code':'P569'},
            {'name': 'date_of_death', 'code':'P570'},
            {'name': 'place_of_birth', 'code': 'P19', 'link':True},
            {'name': 'place_of_death', 'code': 'P20', 'link':True},
            {'name': 'gender', 'code':'P21', 'link': True}
        ]

        p_template = '//*[@id="{code}"]/div[2]/div/div[1]/div[2]/div[1]/div/div[2]/div[2]/div[1]/{link_html}/text()'

        for prop in property_codes:
            link_html = ''
            if prop.get('link'):
                link_html = '/a'
            sel = response.xpath(p_template.format(code=prop['code'], link_html=link_html))
            if sel:
                item[prop['name']] = sel[0].extract()

        yield item


def process_winner_li(w, country=None):
    wdata = {}
    wdata['link'] = BASE_URL + w.xpath('a/@href').extract()[0]
    text = ' '.join(w.xpath('descendant-or-self::text()').extract())

    wdata['name'] = text.split(',')[0].strip()

    year = re.findall('\d{4}', text)
    if year:
        wdata['year'] = int(year[0])
    else:
        wdata['year'] = 0
        print('Oops, NO YEAR in ', text)

    category = re.findall('Physics|Chemistry|Physiology or Medicine|Literature|Peace|Economics', text)
    if category:
        wdata['category'] = category[0]
    else:
        wdata['category'] = ''
        print('NO CATEGORY in ', text)

    if country:
        if text.find('*') != -1:
            wdata['country'] = ''
            wdata['born_in'] = country
        else:
            wdata['country'] = country
            wdata['born_in'] = ''

    wdata['text'] = text
    return wdata

以上です。

個人的な備忘録ですので、ご利用の際はご注意ください。

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

if文なしでじゃんけん javascript

if文なしでじゃんけん - Qiita を読んで、自分ならどうするか考えてみた。

解答

const battle = {
    "グー": {
        "グー": "あいこ",
        "チョキ": "あなたの勝ち",
        "パー": "あなたの負け",
    },
    "チョキ": {
        "グー": "あなたの負け",
        "チョキ": "あいこ",
        "パー": "あなたの勝ち",
    },
    "パー": {
        "グー": "あなたの勝ち",
        "チョキ": "あなたの負け",
        "パー": "あいこ",
    },
}

const battleResult = (myHand, yourHand) =>
    `[${myHand}] vs [${yourHand}] => ${battle[myHand][yourHand]}`

console.log(battleResult("グー", "チョキ"))
console.log(battleResult("グー", "グー"))
console.log(battleResult("グー", "パー"))

可読性 MAX !!

結果

[グー] vs [チョキ] => あなたの勝ち
[グー] vs [グー] => あいこ
[グー] vs [パー] => あなたの負け
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

if文なしでじゃんけん Javascript版

if文なしでじゃんけん を見ました。

事前に勝敗を定義するのは反則な気がしたので作ってみました。
勝敗は自分と相手の手の差に mod(3) を取ればわかります。

コード

const hands = ['', '', ''];
const results = ['あいこ', 'あなたの負け', 'あなたの勝ち'];

hands.forEach((myHand, myVal) => {
  hands.forEach((yourHand, yourVal) => {
    console.log(
      `[${myHand} vs ${yourHand}] ${results[(myVal - yourVal + 3) % 3]}`
    );
  });
});

(myVal - yourVal + 3) % 3 yourVal
✊0 ✌1 ✋2
myVal ✊0 0 draw 2 win 1 lose
✌1 1 lose 0 draw 2 win
✋2 2 win 1 lose 0 draw

結果

[✊ vs ✊] あいこ
[✊ vs ✌] あなたの勝ち
[✊ vs ✋] あなたの負け
[✌ vs ✊] あなたの負け
[✌ vs ✌] あいこ
[✌ vs ✋] あなたの勝ち
[✋ vs ✊] あなたの勝ち
[✋ vs ✌] あなたの負け
[✋ vs ✋] あいこ

応用

手の種類が奇数なら、じゃんけんの拡張も可能
例えば五行に見立てて

(myVal - yourVal + 5) % 5 yourVal
木0 火1 土2 金3 水4
myVal 木0 0 draw 4 lose 3 win 2 lose 1 win
火1 1 win 0 draw 4 lose 3 win 2 lose
土2 2 lose 1 win 0 draw 4 lose 3 win
金3 3 win 2 lose 1 win 0 draw 4 lose
水4 4 lose 3 win 2 lose 1 win 0 draw

手が増えれば増えるほど、事前にすべての勝敗を定義するのが困難になりますよね。

感想

法則性はないかと表を作ってみて気づきました
が、調べてみると一般的なアルゴリズムのようですね。

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

TypeScriptで文字列配列からCSVダウンロード

  • IE対応。
  • コンマや改行を含むセルは " で囲む。
function downloadCsvBlob (data: string[][], fileName: string) {
  const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
  const re = /[,\r\n"]/;
  const csv = data.map(record => record.map(value => re.test(value)
    ? `"${value.replace(/"/g, '""')}"` : value).join(',')).join('\r\n');
  const blob = new Blob([bom, csv], { type: 'text/csv' });

  if (window.navigator.msSaveBlob) { // for IE,Edge
    window.navigator.msSaveBlob(blob, fileName);
  } else {
    const url = URL.createObjectURL(blob);
    const el = document.createElement('a');
    el.href = url;
    el.setAttribute('download', fileName);
    document.body.appendChild(el);
    el.click();
    URL.revokeObjectURL(url);
    if (el.parentNode) {
      el.parentNode.removeChild(el);
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]クリックした要素の取得

クリックした要素を取得したくて、格闘したので、備忘録。
調べたら案外簡単に出てきました。

構文

event.target.innerHTML
event.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="event.js"></script>
    <title>Document</title>
</head>
<body>
    <p onclick="getText()">イベント1</p>
    <p onclick="getText()">イベント2</p>
    <p onclick="getText()">イベント3</p>
</body>
</html>
event.js
function getText() {
    var eventText = event.target.innerHTML;
    alert(eventText);
}

これで問題なく動くのですが、エディタの方では、eventの表記がなされ、「eventは非推奨です」と出ちゃいます。動くのでよしとしてますが、なんか気持ち悪い、、

あと、タグすべてにonclickイベント書かないといけないのもめんどくさい。
なんとかならないですかね、、

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

[TypeScript]文字列リテラルをテンプレートリテラルで分解し、型再帰で階層内の型を抽出する

必要なバージョン

今回の内容を実行するにはTypeScriptの4.1.2以降が必要です
型の変換等に関する定石はこちらを見てください

実現する内容

const value = { aaa: { bbb: { ccc: 'Test', ddd: 100 } } }

const ccc = getTreeValue(value, 'aaa/bbb/ccc') //型は自動的にstring
console.log(ccc) //Test
const ddd = getTreeValue(value, 'aaa/bbb/ddd') //型は自動的にnumber
console.log(ddd) //100

オブジェクトからスラッシュ区切りでデータを取り出し型推論させます。
この処理を行う方法は以降の内容を確認してください。

文字列リテラルを分解し、再帰的に型を取り出す処理

type PickTree<T, P> = P extends keyof T
  ? T[P]
  : P extends `${infer R1}/${infer R2}`
  ? R1 extends keyof T
    ? PickTree<T[R1], R2>
    : never
  : never

文字列リテラルをテンプレートリテラルを経由し、inferで取り出して分解しています。後続の文字列は再帰で順次処理します。

オブジェクトの階層を辿ってデータを取り出す

const getTreeValue = <T extends { [_ in any]: any }, K extends string>(v: T, path: K) =>
  path.split('/').reduce((a, b) => a[b], v) as TreePick<T, K>

こちらは実処理になります。文字列を分解して順番に内容を展開します。そして先ほど作ったTreePickで戻り値の型を決めます。

まとめ

TypeScriptはバージョンが進むにつれて、今まで出来なかった事が実現可能になっていきます。
夢が広がります。

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

【Node.js】npm proxyの使い方

プログラミング勉強日記

2021年3月1日

proxyとは

 proxyは、npm configコマンドで使用できる代理(プロキシ)サーバー設定のこと。
 開発中にインターネットでアクセスの制限をされた場合など、セキュリティの問題によってアクセス制限された環境でプロキシサーバーを経由した開発を行うことがある。

使い方

 npmでプロキシサーバーの設定をするためには、npm configコマンドでproxyを使用する。npm config set proxyと入力することで、npm config機能の一部であるproxyを利用できる。

$ npm config set proxy http://<proxy-host>:<proxy-port>
proxyを解除する方法
$ npm -g config delete proxy
proxy設定の確認
$ npm -g config list

参考文献

プロキシサーバーを使う際に役立つ!npm proxyの使い方【初心者向け】
Node.js / npm 設定 に プロキシ を 設定 または 解除 する 方法

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

MDwiki:レイアウトを自由に変更する

はじめに

MDwikiはmarkdownで書かれたテキストをお手軽にwebページ化することができる、お手軽なCMSである。

基本的にMdwikiは必要最小限のコンテンツを最小限の手間で公開にもっていくためのインスタントなシステムである。内輪向けのウェブサーバで、随時更新されていくマニュアルやレポートを共有するといった使い方に向いている。Markdownを加筆修正するだけなので、日々の更新作業にかかる手間と時間を大いに軽減できて便利だ。半面、複雑に凝ったサイト作成には向かない。

とはいえ、ある程度使い込んでくると、ページ全体の色調とかフォントとか、少しは自分好みに変更したいと思うのが人情だろう。そこで、この記事ではMdwikiページの「見栄え」を自由にカスタマイズするためのクイックハックを提示する。

mdwiki-debug.htmlの改造

MDwikiのパッケージにはいくつかのhtmlファイルが含まれている。普通はmdwiki.htmlを公開用に利用するだろうが、ここでは中身がminifyされていないmdwiki-debug.htmlを弄ってみる。

theme

MDwikiでは、レイアウトを「theme」を選択することで決定する。themeとはMDWiki用に調整されたCSSファイルである。公式サイトには13種類のthemeファイルが用意されている。このthemeを弄ることでMDwiki下の各ページの見栄えを変えることができる。

そこでレイアウト変更の手順は次のようになる。

  1. 適当なダウンロードファイルをダウンロードしてくる。
  2. ダウンロードしてきたtheme内のCSSの記述を適当に改変する。
  3. 出来たCSSファイルを適当な名前に変え、適当な場所にアップロードして、インターネット上で自由にアクセスできるようにする。
  4. mdwiki-debug.htmlに加筆して、上記のオリジナルthemeをthemeとして認識できるようにする。

mdwiki-debug.htmlを見ると、次のようにこのthemeを列挙している箇所がある。

    var themes = [
        { name: 'bootstrap', url: 'netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css' },
        { name: 'amelia', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/amelia/bootstrap.min.css' },
        { name: 'cerulean', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/cerulean/bootstrap.min.css' },
        { name: 'cosmo', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/cosmo/bootstrap.min.css' },
        { name: 'cyborg', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/cyborg/bootstrap.min.css' },
        { name: 'flatly', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/flatly/bootstrap.min.css' },
        { name: 'journal', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/journal/bootstrap.min.css' },
        { name: 'readable', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/readable/bootstrap.min.css' },
        { name: 'simplex', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/simplex/bootstrap.min.css' },
        { name: 'slate', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/slate/bootstrap.min.css' },
        { name: 'spacelab', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/spacelab/bootstrap.min.css' },
        { name: 'united', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.0/united/bootstrap.min.css' },
        { name: 'yeti', url: 'netdna.bootstrapcdn.com/bootswatch/3.0.2/yeti/bootstrap.min.css' }

このコードを頼りに、適当なCSSファイルをダウンロードしてくる。例えばwgetを利用するなら下のような感じになる。
ここではmytheme.cssという名前で保存することにしよう。

wget -O mytheme.css http://netdna.bootstrapcdn.com/bootswatch/3.0.2/yeti/bootstrap.min.css

cssの書き換え

このmytheme.cssをエディタで開いて編集する。
次のような記述をファイルの末尾に書き加え、ブラウザで当該ページを再読み込みしてみよう。該当箇所の色が変わっていることが確認できるだろう。

/* メニューバー全体 */
/* .navbar-inverse .navbar-brand{                                                                                                            
  color: yellow;                                                                                                                             
} */

/* メニューバー中のサイトタイトル */
navbar-header a.navbar-brand{
  color: magenta;
}

.navbar-inverse .navbar-nav > li > a { /* メニューバー中のプルダウンメニュー */
  color: orange;
}

/* 本文タイトル */
.page-header h1{
  color: green;
}

/* 本文 */
#md-content{
  background-color: #666;
}

JavaScriptの書き換え

mdwiki-debug.html中のthemesの定義を書き換えて、このmytheme.cssをthemeとして認識させる。

    {name: 'mytheme', url: 'example.com/mdwiki/mytheme.css'}

こんな感じになる。サイト名から省略なく書かなくてはならない。

navigation.mdの書き換え

どのthemeを使うかはnavigation.mdの中で設定することになっている。これを上記の変更を反映するように書き換える必要がある。
具体的にはgimmickパラメータを適切に設定する。

[gimmick:Theme (inverse: true)](mytheme)

おわりに

このように、オリジナルCSSファイルをMDwikiの各ページに適用させることができる。ブラウザの検証ツールを活用して作りこんでいけば、相当に印象を変えることができるだろう。

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

[js] 文字列への変換にnew String()を使うのはよくない

こんなコードを見かけた

console.log('price = ' + new String(price));

数値から文字列の変換にnew Stringを用いている。

実はこのnew Stringが返すのはstringではなくstringっぽい何かなのだ!

const s = '100';
const s2 = new String(100);
console.log(typeof s); // string
console.log(typeof s2); // object

実はnew Stringが返すのはstringのボックス型なのだ。似たような振る舞いをするが決してstringではない!

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

=== undefined よりも == null を使おう

以下のコードを見てくれ

const data = await fetchData( ... );
if (data.flag === undefined) {
  ...
}

=== undefined を使っているがそれは意図して使っているのか?

== null=== undefinedには以下のような違いがある

== null

null もしくは undefined と一致

=== undefined

undefinedと一致

つまりnullはOK!undefinedはダメ!という状況でしか使わないのだ!
いつ紛れ込むかわからないnullのことを考えると == nullを使うのが吉だ!

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

[js] 指定したPromiseが解決されるまでローディング表示

概要

a.gif

Promiseを渡すとそれが解決されるまでローディング表示をする関数を実装する。

ローディング関数の仕組み

  1. 最初にローディング表示する
  2. 渡されたPromiseが解決されたらローディング表示を解除する

Promiseの解決を検知する方法

渡されたPromiseに対してthenメソッドを呼び出すことでPromiseが解決されたことを検知できる。

promise.then(() => console.log('解決された'))

実装

function loading(promise) {
  let i = 0;
  const interval = setInterval(() => {
    showLoading(((++i) % 3) + 1);
  }, 500);
  promise.then(() => {
    clearInterval(interval);
    endLoading();
  });
}

デモ

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

なぜTypeScriptが必要なのか?

概要

TypeScriptがなぜ必要なのかをざっくりと把握することを目的とします。

対象者

TypeScript聞いたことあるけど、なにがいいんだろうと思っている方。

JavaScriptの簡単な概要と問題点について

TypeScriptはとJavaScriptのスーパーセット(上位互換)です。JavaScriptでできることはTypeScriptでもできます。
TypeScriptという言語はJavaScriptの問題点を補うために生まれました。
そのため、まずはじめにJavaScriptの概要問題点を簡単に抑えておきましょう。

JavaScriptの概要

JavaScript (ECMAScript としても知られています) は、ブラウザ用のスクリプト言語としてその生涯をスタートさせました。
JavaScripを利用することで、Webサイトにて複雑な機能を実行することができます。
例えば、Webサイトを訪問したとき、ポップアップ画面を表示したり、アニメーションが動いているのを見たことがあるのではないでしょうか?
あのようなブラウザが「動く」ために、指示を出しているプログラミング言語としてJavaScriptが利用されています。

また、インターネットの普及に応じてブラウザを利用するユーザーが増えてきたことから、ますますJavaScriptの人気が出てきました。
それに伴い、Webブラウザの開発者は、実行エンジンの最適化やJavaScriptでできることの拡張 (API の追加) によってJavaScriptの需要の増加に対応してきました。そして、Web開発者はさらにJavaScriptを使用するようになりました。

さらには、node.jsを使ってバックエンドをJavaScriptで実装するなど、ブラウザ以外でも利用できるほど普及してきました。
最近では、JavaScriptだけを使ってフロントエンドとバックエンドを開発するフルスタックエンジニアがたくさんいます!

JavaScriptの問題点

上記の歴史で発展してきたJavaScriptですが、この言語には実は粗末さがあります。
以下にいくつかの例を挙げてみましょう。

  • "" == 0の条件判定がTrueとなってしまう。
if ("" == 0) {
// 条件式の結果はTrueとなりif文が実行される
}
  • 以下のように文字列をいれた変数に数値を再代入できてしまう。
let moji = 'Hello World!';
moji = 10;

ほとんどのプログラミング言語は、コンパイル中、つまりコードが実行される前に上記のような事象に対してエラーを返却してくれます。
ですが、JavaScriptにおいてはこのような予期しない動作を許してしまう問題点があります。

TypeScriptの特徴

TypeScriptの最大の特徴は「静的型付け言語」であることです。
この特徴により、TypeScriptは型定義型推論を行うことができます。

今回は型推論について紹介していこうと思います。
型推論とは文字の通り、TypeScriptが変数や関数の戻り値などの型を推論してくれる便利な機能です。

型推論ができると何がいいのでしょうか?
たとえば以下のコードを書いたとします。

let moji = 'Hello World!';
moji = 10;// error : Type 'number' is not assignable to type 'string'.

エラーとして、「型「number」は、型「string」には代入できません。」という内容が返却されています。
mojiという変数は最初にstring型とTypeScriptでは型推論されたため、その後にnumber型の10を代入ができないということを伝えているわけですね。
これは型を推論したからこそ検知できたわけです。
このようにJavaScriptでは型の矛盾を許していましたが、TypeScriptで矛盾を検知できるため、エラーを未然に防ぐことができます。

まとめ

以上がTypeScriptの特徴の1つである型推論についてでした!
型推論だけでも大いにメリットはありますね。
この記事でなぜTypeScriptが必要なのか?の理解が進みましたら幸いです。
次回は型定義について紹介していこうと思います。

参考文献

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

【JavaScript】 海外Teck系YouTuberを真似てみた!(part 4) Blog App | React JS

1,はじめに

この記事は、海外Teck系YouTuberの動画を参考に、同じプロジェクトを作成してみたものになります!
簡単にですが、動画を通して学べた技術や知識をまとめました!

今回の完成品は、下記に載せています:bangbang:

2,学んだこと

Reactに必要な、RouterやHookについて学ぶことができました!
JSONサーバーのビルド方法や、Fetchを使ってサーバーと接続する方法も知れました!

3,参考動画

4,完成:tada:

2021-03-01_00h23_36.gif

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