- 投稿日:2019-12-05T23:40:32+09:00
Chrome のコンソールに猫の足跡を並べよう
Chrome のコンソールに猫の足跡を並べたくなったので、並べてみました。
⚠注意点⚠
これは所謂「無限ループ」です。
実行したが最後、 Chrome のタブを閉じない限り実行され続けます。
閉じても問題のないタブのコンソールから実行してください。スクリプト全体
{ const nyanFrequency = 11; const nyanSize = '36'; const nyanTime = 500; const nyan = '?'; const footstep = '?'; let nyandam; let nyanText = nyan; const nyanFunc = () => { nyandam = Math.round(Math.random() * 100); if(nyandam % nyanFrequency === 0){ nyanText = nyanText + nyan; } nyanText = nyanText + footstep; console.log(`%c${nyanText}`,`font-size: ${nyanSize}px`); }; setInterval(nyanFunc, nyanTime); };猫の足跡が増えるタイミングを変えたい
猫の足跡が増えるタイミングを変えたい場合は
var nanTime = 500;
を変更してください。
ミリ秒単位で変更可能です。猫が登場する頻度を変えたい
猫が登場する頻度を変えたい場合は
const nyanFrequency = 11;
を変更してください。
数字は1
以上の任意の値にして下さい。
数字を増やせば猫が登場する頻度は落ちますし、数字を減らせば猫が登場する頻度が増えます。猫の大きさを変えたい
猫の大きさを変えたい場合は
const nyanSize = '36';
を変更してください。
数字は1
以上の任意の値にして下さい。
ただし最小フォントサイズは Chrome の設定に依存します。猫以外も表示したい
こちらの記号をお好みのものに変更してください。
const nyan = '?'; const footstep = '?';例えばこのように変更すると……
const nyan = '⛄️'; const footstep = '❄';冬っぽくなります。
- 投稿日:2019-12-05T23:38:36+09:00
【Rails】Rails⇔JavaScript間で時間データを渡す方法
【はじめに】
先日、こちらの記事を書きました。
【Rails】Rails側で定義した変数をJavaScriptに簡単に渡せるgem 「gon」を使ってみた - Qiitaただ、時間関係の変数についてはフォーマットがRubyとJavaScriptで異なってしまい、うまく動きません。
そこでRails⇔JavaScript間で時間データを渡す変換方法を調べたのでまとめてみました
【この記事が役に立つ方】
- Rails⇔JavaScript間で時間データをやりとりしたい方
【この記事のメリット】
- Rails⇔JavaScript間で時間データをやりとり出来るようになる。
【環境】
- macOS Catalina 10.15.1
- zsh: 5.7.1
- Ruby: 2.6.5
- Rails: 5.2.4
【ポイント】
1000を掛ける、割るで調整する。
【変換方法】
1.Ruby → JavaScript
t1 = Time.now => 2019-12-05 14:11:03 +0000 t1.to_f * 1000 => 1575555063351.479Ruby側で
to_f
メソッドを使い、1000を掛けることでミリ秒に変換。
↕
JavaScript側でnew Date()
の引数にそのミリ秒を渡す。let d1 = new Date(1575555063351.479) d1 // Thu Dec 05 2019 23:11:03 GMT+0900 (日本標準時)
2.JavaScript → Ruby
let d2 = new Date() d2 // Thu Dec 05 2019 23:20:04 GMT+0900 (日本標準時) d2.getTime() / 1000 // 1575555604.804JavaScript側で
getTime()
で拾った数値を1000で割る。
↕
Ruby側でTime.at()
の引数にその数値を渡す。Time.at(1575555604.804) => 2019-12-05 14:20:04 +0000【参考】公式リファレンスより
【Ruby】 Timeオブジェクト
秒
単位Time オブジェクトは時刻を起算時からの経過秒数で保持しています。起算時は協定世界時(UTC、もしくはその旧称から GMT とも表記されます) の 1970年1月1日午前0時です。なお、うるう秒を勘定するかどうかはシステムによります。
class Time (Ruby 2.6.0 リファレンスマニュアル)
【JavaScript】 Dateオブジェクト
ミリ秒
単位日付や時刻を扱うことが可能な、JavaScript の Date インスタンスを生成します。Date オブジェクトは、1970 年 1 月 1 日 (UTC) から始まるミリ秒単位の時刻値を基準としています。
【おわりに】
最後まで読んで頂きありがとうございました
特にJavaScriptと他言語は頻繁に関わってくると思いますが、言語間でエラーが出たらそもそも前提が違うという視点を持って対処していかないといけないですね
【参考にさせて頂いたサイト】(いつもありがとうございます)
- 投稿日:2019-12-05T23:06:46+09:00
htmlをReactコンポーネントにぶちこんでやるぜ〜〜!!
はじめに
いなたつアドカレの五日目の記事です。
今回はReactで生のhtmlを扱うための方法とその時の注意について少し、、、
ざっくりいうと
- dangerouslySetInnerHTMLを使おう
- XSS に気をつけよう
- markdownから変換して表示も可能だよ
dangerouslySetInnerHTMLを使う
<span dangerouslySetInnerHTML={{ __html: html}} />dangerouslySetInnerHTML属性に__html(あんだーばーあんだーばーへいちてぃーえむえる)に展開したいhtmlを設定する オブジェクト を渡すことで指定したhtmlをタグ内部に展開することができます。
dangerouslyやねん
XSSの危険がつきまとってくるんですよね。
XSSってのはクロスサイトスクリプティングといって、テキストボックスにhtmlを埋め込み、予期せぬ動作をさせようとすることです。
これの何がdangerouslyかというと
- テキストボックスにスクリプトを入力し、javascriptを実行させることで、クッキーなどを盗みなりすましができる
- 同様に、アプリケーションの機能を悪用されるかもしれない
クッキーでセッションIDなんかを保持しているとなりすましの温床になりかねないですね。。。。
dangerouslyって名前についてるのはこういうところからなので、まあ、あまり使わない方がいいですね。じゃあどーやって対策すんの
** サニタイズ **
markdownがうんぬんかんぬん
markdownを生のhtmlに変換してここにぶちこんでやるぜーしたらmarkdownエディタのプレビューっぽいことできるって話
次回reactとmarkedのお話を書きます。。。。
- 投稿日:2019-12-05T23:00:27+09:00
ドットインストールのfirebaseでチャットアプリ作るやつの、わからないところを調べてみた
とりあえずわからない単語調べて書いてみます。
querySelector()
DocumentのquerySelecto()メソッドは、指定されたセレクターまたは、グループに一致する、文書ないの最初のElementを返す。一致するものが見つからない場合はnullを返す
対象要素の子孫のうち、引数に指定したCSSmセレクタにマッチする最初の要素を返す
JavaScriptから、任意のHTML要素を検出、取得することができるメソッド
getElementById()とかgetElementByClassName()とかと似てるけど、id属性、class属性値など意識せずに、jQuery感覚で、HTML要素をセレクタ指定することができる
(idなしでもいける)getElementByIdの方がアクセスは素早い
element = document.querySelector(selections);引数
selectors
一つまたは複数のセレクターを含むDOMString。この文字列は妥当なCSSセレクターでなければならず、そうでない場合は、SYNTAX_ERRが投げられる。セレクターとその管理の方法の詳細について、セレクターをしようしたDOM要素の指定を参照してください。
【JavaScript入門】querySelector()によるHTML要素の取得方法まとめ!
MDN web docsremoveChild
DOMから子ノードを取り除く
firstChild
ノードツリーの中で、最初の子ノード、もしくは子ノードがなければnullを返す。ノードがDocumentの場合は、その直接の子のリストの内の最初のノードを返す
ノードとは
節、交点という意味
四角がノード
ハンドメイド感溢れる図ですみませんaddEventListener
特定のイベントが対象に配信されるたびに呼び出される関数を設定する。対象としてよくあるものは、Element,Document,Windowであるが、イベントに対応したあらゆるアブジェクトが対象になる。
構文
target.addEventListener(type,listener,options);
- type:対象とするイベントの種類を表す文字列
- submitとか
- listener:指定された型のイベントが発生する時に通知(Eventインターフェースに準拠しているオブジェクト)を受け取るオブジェクト。
- option:対象のイベントリスナーの特性を指定する、オプションのオブジェクト。次のオプションが使用できる
- capture:Boolean値で、listenerの呼び出しを一回のみとしたいかどうかを値で指定する。trueを指定すると、listenerは一度実行された時に自動的に削除される
- passive:Boolean値で、trueならば、listenerで指定された関数が、preventDefault()を呼び出さないことを示す。呼び出されたリスナーがpreventDefault()を呼び出すと、ユーザーエージェントは何もせず、コンソールの警告を出力する。
随時調べて追加します
- 投稿日:2019-12-05T22:48:57+09:00
07. テンプレートによる文生成
07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.
Go
package main import "fmt" func template(x,y,z string) string { return fmt.Sprintf("%s時の%sは%s",x,y,z) } func main() { fmt.Println(template("12","気温","22.4")) }python
# -*- coding: utf-8 -*- def template(x,y,z): return '{0}時の{1}は{2}'.format(x,y,z) print template(12,"気温",22.4)Javascript
function template(x,y,z) { return "x時のyはz".replace("x",x).replace("y",y).replace("z",z); } console.log(template(12,"気温",22.4));まとめ
これは、こんなでいいのかしら?。
- 投稿日:2019-12-05T22:46:10+09:00
JavaScriptの「カバー文法」とは何か
皆さんこんにちは。この記事は2019新卒 エンジニア Advent Calendar 2019の6日目の記事です。新卒でもバイト経験何年という人たちがひしめく中、筆者が実務経験く無しでWeb業界に新卒で飛び込んだのが今年の4月のことでした。そんな思い出の年がもう1月もしないうちに終わってしまうのはとても感慨深いですね。
普段から業務でJavaScript(というかTypeScript)を使っている筆者ですが、今回はどちらかというと趣味で興味を持った内容について書いてみました。新卒ということなのでお手やわらかにお願いしたいところです。
この記事では、仕様書におけるJavaScriptの構文の定義に登場するカバー文法について解説します。
JavaScriptの文法はどのように定義されているか
JavaScriptは、その仕様がしっかりと定められている言語のひとつです。今どきは大抵の言語がOSSとして開発されているとはいえ、どこかの会社や非営利団体が音頭を取って言語の開発方針を決めていたり、実質的にコンパイラの動作によって言語が定義されていたりといった運用をされるものが多くあります。その一方で、JavaScriptには、JavaScriptがどのような言語かを定める仕様書が存在しています。
仕様書はJavaScriptの言語機能や動作を定めるものであり、JavaScriptへの機能追加は仕様書の改訂という形で行われます。この仕様書の管理を司るのが、ECMA Internationalという機関の中に存在するTC39という委員会です。この委員会にはいろいろな企業のメンバーが参加しており、どこか一つの企業が舵取りをしているというわけではありません(力が強い会社というのは多分あると思いますが)。
JavaScriptを実装する言語処理系(ブラウザとか)はこの仕様書を基準にして実装されており、「実装が仕様」と言わんばかりの言語たちとは一線を画しています(実際には仕様が正式リリースされる前に実装が先行するような仕組みになっているのですが)。
なお、仕様書にはECMAScriptという名前が書いてありますが、まあJavaScriptのことだと思って問題ありません。
そして、当然ながら、JavaScriptの文法を定義するのもやはり仕様書です。文法というのは、どのような文字列がJavaScriptプログラムとして受け入れられるかを定義したものです。
JavaScriptの文法は構文規則の集まりによって表現されています。例えば
const foo = bar + 3;
というのはJavaScriptにおける正しい文ですが、これがなぜ正しい文なのかは次の表(あるいは木)で説明されます。この表を上から読みつつ多少言葉で説明すると以下のようになります。
const foo = bar + 3;
はStatementListItemである。なぜなら、DeclarationはStatementListItemの一種であると定義されているから。const foo = bar + 3;
はDeclarationである。なぜなら、LexicalDeclarationはDeclarationの一種であると定義されているから。
const foo = bar + 3;
はLexicalDeclarationである。なぜなら、LetOrConst, BindingList,;
が並んだものはLexicalDeclarationであると定義されているから。
const
はLetOrConstであると定義されている。foo = bar + 3
はBindingListである。LexicalBindingはBindingListの一種であると定義されているから。
foo = bar + 3
はLexicalBindingである。なぜなら、BindingIdentifierとInitializerが並んだものはLexicalBindingであると定義されているから。
foo
はBindingIdentifierである。なぜなら、IdentifierはBindingIdentifierであると定義されているから。= bar + 3
はInitializerである。なぜなら、=
とAssignmentExpressionが並んだものはInitializerであると定義されているから。
bar + 3
はAssignmentExpressionである。(以下略)「定義されている」という言葉を多用しましたが、もちろんこれらの定義は仕様書に書いてあります。上の例に登場した定義の一部を仕様書 13.3.1 Let and Const Declarationsから画像で引用します。
一番上には「LetOrConst, BindingList,
;
が並んだものはLexicalDeclarationである」という定義が書かれています。その次には、「LetOrConstとはlet
という文字列またはconst
という文字列である」という定義が書かれています。以下も同様です。なお、StatementListItemとかDeclarationとか、LetOrConstといった名前がたくさん登場しましたが、これらを総称して非終端記号と呼びます。どのような文字列がどの非終端記号に合致するかというのを定義するのが文法です1。JavaScriptプログラム全体はScriptまたはModuleという非終端記号で表されます。正しいJavaScriptプログラムとは、JavaScriptの文法に照らしてScriptまたはModuleに当てはまるような文字列のことを言います(実際には後述のような追加のエラーチェックをくぐり抜ける必要がありますが)。
実際に与えられた入力文字列がどの非終端記号に当てはまるかを特定するのがパースという作業です。上記のような定義を見ると分かる通り、非終端記号は生の文字列(終端器号)や別の非終端記号の組み合わせによって定義されています。
全てのJavaScriptプログラムは、仕様書に書かれた膨大な文法・非終端記号の定義によって全て説明し尽くすことができるのです。
JavaScriptの文法における「曖昧性」
文法にはいろいろな分類が知られています。例えばLL(1)やLR(1)などです。この(1)というのは文字列をパースする際に先読みが1文字(あるいは1トークン)必要であるという意味です。(0)なら先読みが不要、(2)なら2文字の先読みが必要ということになります。
JavaScriptの文法が正確にどのようなクラスに属するのかについては、筆者には確実なことが言えないので言及を避けようと思います。しかし、JavaScriptの文法定義を眺めると、1トークンの先読みでパースできるように定義されているように見えます。そして、仕様書では、1トークンの先読みでパースできないような規則を「曖昧である」と呼んでいるように見えます(cf. 5.1.4 The Syntactic Grammar)。
例えば、
const foo = bar + 3;
については先読みなしでパースできます。パーサーの気持ちになって考えてみましょう。パーサーは前から順番にトークンを読んでいきます。
- 最初に
const
を読んだ時点でこれはLetOrConstであることが確定します(const
は予約語であるため、そういう名前の変数であるなど他の可能性はありません)。- 次に
foo
を読みます。このような普通の変数名っぽいものはIdentifierになります。const
の次である(次はLexicalBindingが来るはず)ことも加味するとこれはBindingListの中のBindingIdentifierであることも確定します。- 次に読んだ
=
も、LexicalBindingの中のInitializerの最初の=
であることは明らかです。- 次の
bar
はIdentifierになります。このIdentifierがどのような役割かはまだ分かりません。+
を読んだ時点で今AdditiveExpressionの真ん中にいることが明らかになります。これにより、先ほどのbar
がAdditiveExpressionの左のAdditiveExpressionであることが明らかになります。- 次の
3
はNumericLiteralになります。- 最後に
;
を読んだ時点でAdditiveExpressionの右に位置するMultplicativeExpressionは3
のみであることが明らかになります。実は、この一連の処理においては先読みが発生していません。4において「Identifierがどんな役割なのかまだ分からない」というシチュエーションもありましたが、「Identifierである」という結論自体はその場で下すことができています。先読みが必要になるというのは、「
bar
がIdentifierである」というような結論すらその場で下すことができない(全く異なる複数の可能性が存在してしまう)シチュエーションを指しています。JavaScriptの文法に存在する曖昧性
JavaScriptの文法は、前節で説明したような「曖昧性」が存在しないように作られているようです。1トークンの先読みまでは許容しますが、それでも曖昧性が解消できない構文は存在しないというのが原則です。
しかし、実はその原則が破られる場合が存在します。そのひとつがアロー関数です。
アロー関数は
(foo) => { console.log(foo); }
のような構文です。これは関数を作る式であり、この例の場合は「引数foo
を受け取ってconsole.log(foo);
を実行する関数」になります。アロー関数では引数一覧を
( )
で表しますが、これが問題です。次の2つの文を見比べてみましょう。const v1 = (foo) => { console.log(foo); }; const v2 = (foo);この2つの文はどちらも妥当な文です。そして、
=
の右が(foo)
まで同じです。しかし、この2つの文における
(foo)
の役割は全く異なります。1つ目の文ではアロー関数の引数部分であるのに対し、2つ目の文では変数foo
を括弧で囲んだものです。2つ目の文はconst v2 = foo;
と同じ意味ですね。ここで問題となるのは、この2つの文の区別は1トークンの先読みでは無理である、すなわち(前節で述べた意味で)曖昧であるということです。
実際、前から順番にトークンを読むことを考えると、
const v =
まで読んだという前提のもとでも、次の(
が「アロー関数の引数一覧の始まりの括弧」なのか、それとも「グルーピングの括弧」なのか分かりません。また、1文字先読みしても無理です(それどころか、任意の定数個の先読みでもこれは不可能です)。JavaScriptの新機能を議論するときには「文法の曖昧性」は大きな問題であり、曖昧性を持つ文法を導入するのは極力避けられます。それにもかかわらず、アロー関数はその利便性や構文の分かりやすさを天秤にかけて、曖昧性を受け入れるほうを取ったことになります。
ちなみに、曖昧性が実際に問題になった例として、TypeScriptに搭載されたパーサーの例を紹介します。これはまだオープンなissueであり、ものすごく曖昧な入力をパーサーに与えることでパースに非常に時間がかかってしまうというものです。回避することはできるかもしれませんが(Babelなどがどうやっているのか調べるとよいと思いますが残念ながら未検証です)、いずれにせよ何らかの工夫が求められるものであり、曖昧性が実際にパーサーの実装者に負担を与えることが分かります。
カバー文法による曖昧性の対処
やっとカバー文法が話に登場します。前節ではJavaScriptの文法が(先読み1でパースできないという意味での)曖昧性を抱えていることを示しましたが、実は実際の仕様書における文法定義では、カバー文法というものを使って曖昧性を半ば無理やり回避しています。この手法を紹介するのが本記事のメインのテーマです。
いきなりキーとなる非終端記号を紹介します。それはCoverParenthesizedExpressionAndArrowParameterListです(仕様書から画像で引用)。
見ると分かる通り、この終端器号は
( )
で囲まれた何かの構文を表すようです。詳細は省きますが、実はこの終端器号は「グルーピングの構文としての( )
」と「アロー関数の引数リストとしての( )
」の両方をカバーする定義になっています。これにより、
(
を見かけたらとりあえず「CoverParenthesizedExpressionAndArrowParameterListの始まりの括弧である」と判定すれば間違いがありませんので、前述の曖昧性が形式上は消えていることになります。実際、グルーピングの構文に対応するものとしてCoverParenthesizedExpressionAndArrowParameterListはPrimaryExpressionであるという定義が存在しています。また、アロー関数の構文に関しても、ArrowFunction非終端記号が CoverParenthesizedExpressionAndArrowParameterList
=>
ConciseBody という並びから作れることが定義されており、アロー関数の引数部分がCoverParenthesizedExpressionAndArrowParameterListで表現されていることがわかります。カバー文法と実際の構文のギャップ
上で説明したCoverParenthesizedExpressionAndArrowParameterListを愚直に受け入れると、例えば以下のようなものが妥当なプログラムとしてパースしうるということになります。
(1+2) => 3実際、これは以下のようにパースできます。
ArrowFunction ArrowParameters
CoverParenthesizedExpressionAndArrowParameterListConciseBody
AssignmentExpression
(中略)
NumericLiteralExpression
(中略)
AdditiveExpression
(以下略)(
1
+
2
)
=>
3
問題の本質は、カバー文法は本来異なる2つの構文を無理やり同一視するものであるという点にあります。このため、カバー文法を用いた場合、本来パースできるべきではない誤ったプログラムがパースできてしまうという問題が発生します。
この問題に対しては、仕様書はSupplemental Syntaxを用いて対処しています。例えばアロー関数に対しては次のSupplemental Syntaxが定義されています(14.2 Arrow Function Definitionsから画像で引用)。
Supplemental Syntaxの言っていることは、「CoverParenthesizedExpressionAndArrowParameterListに該当するものがアロー関数の一部として現れたらそれを念の為パースし直して
(
UniqueFormalParameters)
に当てはまるかどうかをチェックする」ということです。(
UniqueFormalParameters)
はアロー関数の引数の( )
部分の構文を正確に表しており、これに合わないもの(上の(1+2) => 3
のようなもの)はこのチェックで弾かれて晴れて文法エラーとなります。これで、なんとか曖昧性を回避しつつ構文が定義できました。ただ、「再パース」とかいう荒業を用いたものになっています。
愚直にこの通りにパーサーを実装した場合、同じ部分を何度もパースすることになるため最悪の場合入力の長さ$N$に対して$O(N^2)$くらい時間がかかりそうです。おや、先ほど紹介したTypeScriptのissueにquadratic (二次)と書いてあったような……?2
なお、グルーピングの
( )
構文の側にも同様にsupplemental syntaxが定義されています。こちらはEarly Errorという仕組みを用いて文法エラーが定義されています。これは、パースには成功したものの妥当ではないというようなプログラムを弾くための仕様書上の仕組みです。まとめ
この記事ではJavaScriptの構文に潜む(1トークン先読みでパースできないという意味での)“曖昧性”と、言語仕様におけるワークアラウンドとしての「カバー文法」について解説しました。
ここでは代表例としてアロー関数の場合を紹介しましたが、他にもカバー文法の例がいくつかあります。気になる方は仕様書を
cover
とかで全文検索してみましょう。この記事ではあくまで言語仕様がどうなっているかに絞って解説したので、実際のパーサーの実装がどうなっているかといったことには触れませんでした。そういった内容について補足してくださる方や記事を書いてくださる方は大歓迎です。
いやあ、実際の実装に目も向けず理想(言語仕様)ばかり追い求めているのがいかにも右も左もわからない新卒という感じの記事でしたね。この記事は2019新卒 エンジニア Advent Calendar 2019の6日目の記事でした。明日もお楽しみに。
- 投稿日:2019-12-05T22:39:40+09:00
オブジェクト操作のすゝめ (map,forEachとかが便利すぎる話)
はじめに
この記事は初心者向けのものではありません!
自分用のメモでもあるので、詳しい解説をしていないものもあります。
読んでいてわからないことがあったら、MDNなどを頼りましょう。JavaScriptの型標準メソッドが便利
Array
,Object
のメソッドのmap()
,forEach()
が、ショートコーディング好きの私にピッタリだったので、少しマニアックなコードを紹介します。ちなみにどんなメソッド?
それぞれ動きはほとんど同じですが、帰り値がちょっと違います。
メソッド forEach()
map()
filter()
some()
引数 callback: function callback: function callback: function callback: function callbackに渡される引数 エレメント自身、エレメントのインデックス エレメント自身、エレメントのインデックス エレメント自身、エレメントのインデックス エレメント自身、エレメントのインデックス 処理 全ての要素それぞれを引数にcallbackを呼ぶ 全ての要素それぞれを引数にcallbackを呼ぶ 全ての要素それぞれを引数にcallbackを呼ぶ 全ての要素それぞれを引数にcallbackを呼ぶ 返り値 undefined callbackの返り値を含む新しい配列 callbackの返り値がtruthyだったものだけを含む新しい配列 callbackの返り値のどれかがtruthyだった場合にtrue、そうでない場合にfalseのboolean ∴処理速度 速い 遅い - - - - - - - - - - - - 用途別に使い分けると高速化できますし、コードの読み手にも優しいですね。
具体例
idをキーに、idのあるエレメントだけを含むオブジェクトを生成
無理やりインデントするとこんな感じ。
let elems = new ( function() { [...document.querySelectorAll("*")] .filter (element => element.id) .forEach(element => this[element.id] = element) } )();仕組み
まず、
document.querySelectorAll()
で"*"
を指定して、ページ上の全てのエレメントを取得します。このままだと
arrayLikeObject
と呼ばれる、配列に似たただのオブジェクトなので、map()
もforEach()
も使えません。
なので、スプレッド構文の裏技を使って、ちゃんとした配列に変換します。let array; array = [...arrayLikeObject]; // 別の書き方 array = Array(...arrayLikeObject); array = Array.from(arrayLikeObject); array = new Array(...arrayLikeObject);そして、idを持っているエレメントだけ取り出すために、
filter()
を使います。
それをthis
に書き出したら終わりです。裏話
このコードでは、結果を
this
に書き出すためにnew
と即時関数を組み合わせています。うーん、美しい。
new
と即時関数の相性は抜群です。返り値をthis
というオブジェクトとして扱えるので、return
されるオブジェクトを格納するための変数を、いちいち宣言しなくてもよいのです。ここで、「アロー関数の方が短いのに、なんで使わないの?」と思った方もいると思いますが、ちゃんと理由があるんです。
なぜアロー関数ではないのかというと、アロー関数はthisArg
を無視して、グローバルオブジェクトのthis
をそのまま継承してしまうからです。ちなみにminバージョンはこちら。
let elems=new(function(){[...document.querySelectorAll("*")].filter(e=>e.id).forEach(e=>this[e.id]=e)})();うーん、短い、最高。
小ネタ
配列から範囲を指定して取り出し
引数はインデックスの下限値、上限値です。
Array.prototype.range = function(min, max) { return this.filter( (element, index) => (min || 0) <= index && (max || this.length) >= index ) }; const hogeArray = ["foo", "bar", "baz", "qux", "quux"] console.log( hogeArray.range(2) ); // ["baz", "qux", "quux"] console.log( hogeArray.range(1, 3) ); // ["bar", "baz", "qux"]インデックスを不等号で評価(
min
以上、max
以下)して、filter()
に渡しています。min版
Array.prototype.range=function(n,x){return this.filter((e,i)=>(n||0)<=i&&(x||this.length)>=i)};配列反転
Array.prototype.flip = function() { return this.map((element, index) => this[this.length-++index] ) }; console.log( ["foo", "bar", "baz"].flip() ); // ["baz", "bar", "foo"]thisArgが
["foo", "bar", "baz"]
の場合の動作を表にしてみます。
index 0 1 2 this.length - index
3-0 == 3
3-1 == 2
3-2 == 1
this.length - ++index
3-(++0) == 3-1 == 2
3-(++1) == 3-2 == 1
3-(++2) == 3-3 == 0
this[index]
this[0] == "foo"
this[1] == "bar"
this[2] == "baz"
this[this.length-++index]
this[2] == "baz"
this[1] == "bar"
this[0] == "foo"
min版
Array.prototype.flip=function(){return this.map((e,i)=>this[this.length-++i])};
- 投稿日:2019-12-05T22:10:05+09:00
Reactの式展開について
Reactでコンポーネントを作るに当たって、変数展開は欠かせないものですが、微妙に気になることがいくつかあります。
消えるもの、消えないもの
よく条件で表示するものを変えるときに、
{condition || <SomeComponent />}
や{condition && <SomeComponent />}
のように書くこともあるかと思いますが、条件を満たさない場合、左側がそのまま描画対象となります。そして、
true
やfalse
、null
、undefined
、空文字列ならもちろん何も描画されないのですが、truthyなものは基本的に描画対象となりますし、falsyなものでも0は「0」として描画されてしまいます。
? :
を使って条件判定の後で描画するものを明確にする、あるいは展開箇所で不等号や!
、!!
などを直接書いてtrue
かfalse
しか入らないようにする、といった注意は払ったほうがいいかもしれません。テキストノードが入らない
ふつうに以下のようなHTMLを書くと、
<button>
とテキスト
の間には改行があるので、スペースが入ります。<p> <button type="button">ボタン</button> テキスト </p>一方で、JSXの場合は改行の絡むスペースはすべて無視される(
</button> テキスト
のように、同一行に書いたときは無視されません)ので、<button>
とテキスト
がくっついてしまいます。もちろん、「CSSで隙間を開ける」という解決策もあるのですが、スペースとしてのテキストノードを書いたほうがいい場面もあります。
このような場合、式展開の形で書けばスペースが消えることはありません。
<p> <button type="button">ボタン</button> { ' ' } テキスト </p> <p> <button type="button">ボタン</button> { ' テキスト' } </p>スペースだけ独立して書いてもいいですし、スペースに続く文言まで文字列として含むのもありです。
- 投稿日:2019-12-05T21:47:26+09:00
非Javascriptエンジニアのための「Javascriptの変数」
アクマちゃんデモ!
非JavascriptエンジニアのためのJavascript入門 3日遅れのひとり Advent Calendar 2019の5日目の記事(3日遅れだから実質2日目)デモ!!
この記事はJavaScript 「再」入門をベースに書いているデモ。今日はJavascriptの変数についてデモ。Javascriptの変数はJava畑のワガハイ的には最初は???って感じだったデモ!
そんなに複雑ではないからしっかり学んでいくデモ!変数の宣言方法
Javascriptには3種類(+1種類)の変数の宣言方法があるデモ!
let
const
var
- (未宣言)
let
let
で変数を宣言すると、その変数には値を再代入することができるデモ。let nickName = "Akuma chan"; console.log(nickName); // Akuma chan nickName = "Tenshi kun"; console.log(nickName); // Tenshi kun
let
と後述するconst
は、ブロックスコープのローカル変数になるデモ。if (true) { let x = 5; } console.log(x); // ReferenceError: x が定義されていないconst
const
はlet
と同じブロックスコープの変数を宣言するのに使うデモが、let
とは違って値の再代入ができないデモ。let nickName = "Akuma chan"; console.log(nickName); // Akuma chan nickName = "Tenshi kun"; // Uncaught TypeError: Assignment to constant variable.
const
はconstant(定数)の略語デモね。var
[ES6]で
let
とconst
が追加されるまではJavascriptの変数はvar
で宣言していたデモね。
[ES6]以前のJavascriptにはブロックスコープが存在せず、var
には関数スコープが適応されるデモ。var
で宣言された変数はたとえブロック内にあっても、そのブロックを内包する関数内で自由に使えてしまうデモ。if (true) { var y = 5; } console.log(y); // y は 5変数の巻き上げ
var
で宣言された変数は、宣言するよりも前に参照してもエラーにならないデモ。
変数と関数定義は実行前に物理的にコード*の一番上に移動するらしいんデモ。これを変数の巻き上げというデモ。
*変数は関数の先頭に移動する...と思うデモ。console.log(x === undefined); // true var x = 3;コードを実行してみると、
x
はundefined
になっているデモ。
なぜかというと、巻き上げられるのは変数の「宣言」だけで初期化部分は巻き上げられないからデモ。イメージ的にはこんな感じデモね(間違っていたら教えて欲しいデモ)
// 巻き上げられた後のコードの状態 var x; console.log(x === undefined); // true x = 3;わかりやすいデモね。前回書いた通り、値を代入していない状態の変数は
undefined
になるデモ。
この巻き上げはlet
やconst
では起こらないデモ。console.log(x); // ReferenceError let x = 3;varは再宣言できる
let
やconst
は一度宣言した後、同じ変数名を宣言しようとするとエラーになるデモ。
var
は再宣言できてしまうデモ。var a = "var defined."; var a = "var redefined."; // エラーにならない let b = "let defined."; let b = "let refined?"; // エラー意図せずに同じ変数名を使ってしまうとバグの元デモね。
この挙動は前述の巻き上げのために起こっているんじゃないかと思うんだけど、合ってるデモかね?// 上記コードが巻き上げられると var a; a = "var defined."; a = "var redefined."; // エラーにならないこう考えると納得がいくデモ。誰か教えて欲しいデモ。
未宣言のグローバル変数
実は、変数は宣言することなく使用することができるデモ。
x = 1; console.log(x); // 1未宣言のグローバル変数は予期しない動作を引き起こしやすいため、使用しない方がいいと言われているデモ。この記事のベースとなっているJavaScript 「再」入門にはそもそも書かれてもいないデモ。
let/constとvarどちらを使うべきか
let
とconst
は2015年のES6から実装された機能デモ。言い換えると、それ以前のブラウザでは使えないということデモ。
let
const
は前述のvar
を改良したものデモ。基本的にはlet
const
を使うと覚えておいていいと思うデモ。
動作するブラウザが2015年以前のものとわかっている場合はvar
を使わざるを得ないデモね...。変数の命名
変数名(変数の識別子)の命名のルールは以下デモ。
- 文字、アンダースコア (_)、あるいはドル記号 ($) から始まる。
- 変数名は数字から開始できない。
- 2文字目以降は数字も含め、文字、アンダースコア (_)、ドル記号 ($)を使用できる。
- 予約語と被る名前は利用できない
使用できる文字の記述を引用するデモ。
JavaScript は大文字・小文字を区別するため、使用できる文字には "A" から "Z" (大文字) に加えて "a" から "z" (小文字) も含まれます。
å や ü といった ISO 8859-1 や Unicode 文字 (詳しくはこのブログ記事を参照) も識別子に使用できます。 Unicode エスケープシーケンスも識別子に使用できます。あとがき
なんとか2日目も書いたデモ。
Javascriptの変数は特殊な感じがするデモね。ブラウザでちょこっと動的なコンテンツをつくるような言語から始まったという歴史的な背景のせいデモかね?
ほかにもそういう仕様がたくさんありそうなので、これから掘り下げていこうと思うデモ!楽しみデモね!それじゃあまた明日デモ!
参考
- 投稿日:2019-12-05T21:03:24+09:00
reactive-react-reduxの紹介
はじめに
本記事では、ReactでReduxを使うためのライブラリを紹介します。このライブラリは、先日紹介したreact-trackedのRedux版とも言えます。
reactive-react-reduxとは
リポジトリはこちらです。
https://github.com/dai-shi/reactive-react-redux
Reduxが公式に提供しているReact向けライブラリは、react-reduxと呼ばれます。reactive-react-reduxは非公式ライブラリで、公式ライブラリの代わりに使えるものです。ただし、Hooks APIしか提供しません。
公式のReact ReduxのHooks APIとの差分は2点あります。
- useTrackedStateというhookを提供する
- stateを直接contextに入れる方式を採用する(storeではなく)
今回は前者について説明します。
そもそも課題はなにか?
実はこのライブラリを作ったのは、connectへの不満からでした。Reduxはもっとシンプルに使えるものであるはずと思い、mapStateToPropsを排除したAPIを提供したかったというのが動機です。
現在では、useSelectorというhookが公式ライブラリから提供されているので、その差分で説明したいと思います。
例として、storeのstateが次のような形をしているものを考えましょう。
const state = { user: { lastName: 'React', firstName: 'Hooks', age: 1, }, color: 'white', };このstateを使って、コンポーネントでfirstNameとlastNameを表示してみましょう。useSelectorを使った典型的な書き方は次のようになります。
const Component = () => { const firstName = useSelector(state => state.user.firstName); const lastName = useSelector(state => state.user.lastName); return ( <div> <div>First Name: {firstName}</div> <div>Last Name: {lastName}</div> </div> ); };connectに慣れている方は、useSelectorが複数あることに慣れないかもしれませんが、これがオススメされる書き方です。
仮にここで、
const { firstName, lastName } = useSelector(state => ({ firstName: state.user.firstName, lastName: state.user.lastName, }));のように書いてしまうと、動作はしますが、望まない形になります。望まない形とは、state.colorだけに変更があった場合でも、このuseSelectorはコンポーネントを再renderすることを指します。これが起こるのは、useSelectorに指定している関数が毎回新しいオブジェクトを生成するためです。
これを回避する(オススメはuseSelectorを分けることですが、それができない場合に)方法は、公式ライブラリでは2つあります。
- equalityFnを第二引数に指定する
- memoized selectorを作って使う
この辺りが、React Reduxが難しく感じる理由の一つだと感じています。
そもそも、selectorというのは大きいstateオブジェクトから必要なものを選択してくるというAPIとしてはとても分かりやすいものです。そこに、パフォーマンス向上(ここでは無駄なrenderを抑制すること)のために、selectorにreference equality(=参照透過性)の概念を持ち込むことが難しさの原因かと思います。
useTrackedStateを使うとどうなるか?
useTrackedStateを使うと上記のコードは、次のようにかけます。
const state = useTrackedState(); const { firstName, lastState } = state.user;もしくは、selectorを別で定義したとして、次のようにも書けます。
const selectUserNames = state => ({ firstName: state.user.firstName, lastName: state.user.lastName, }); const Component = () => { const { firstName, lastName } = selectUserNames(useTrackedState()); return ( <div> <div>First Name: {firstName}</div> <div>Last Name: {lastName}</div> </div> ); };このように特にreferential equalityを気にせずにselectorを書いたとしても、useTrackedStateにより、無駄なrenderを抑制することができます。
なぜそんなことができるのか?
ReduxメンテナーのMarkのブログ記事では、「Magic?」として紹介されています。reference equalityを意識してselectorを書いている人には、むしろこの挙動が予測不能なものに感じるのかもしれません。
useTrackedStateが動作する仕組みは、Proxyによるものです。
Proxyを使うと、オブジェクトへの操作を追跡(Track)できます。つまり、storeの大きなstateの中でどのオブジェクトプロパティにアクセスしたかを知ることができます。これを利用して、アクセスがあったプロパティに変更があった場合のみコンポーネントを再renderするようにuseTrackedStateが制御しています。
Proxyって遅いんじゃないの?
比較対象を何にするかですが、まず、人が正しくreferential equalityを考慮したselectorを書けるとは限らないということは伝えたいと思います。その場合は、機械がTrackする方が正確になります。もちろん無駄がなく、トータルで速いです。
仮に、完璧なselectorを人が書けたとして、Proxyにはオーバーヘッドがあるのは事実です。簡単な例でベンチマークをした結果を載せます。
ここで比較すべきは、reactive-react-reduxのuseTrackedStateと同じくuseSelectorのカラムです。このベンチマークではほとんど差がないことが分かるでしょう。他のベンチマーク評価もしましたが、極端な例では差が出るものの、実用に耐えうる範囲とみています。
Proxyを利用している他のプロジェクト
React系では、immerやMobXでProxyが使われています。また、Vue.jsでも使われているとのことです。
Redux Toolkitではimmerを採用していますし、今後Proxyを使ったライブラリは増えてくるかもしれません。
おわりに
reactive-react-reduxのuseTrackedStateについて紹介しました。興味を持ってくださった方は、ぜひ触ってみてください。ちなみに、react-trackedにも同じhookが用意されていますので、非Redux派の方はそちらをどうぞ。
今回は踏み込みませんでしたが、useTrackedStateにも限界がないわけではありません。例えば、次のようなuseSelectorの例については、useTrackedStateで同じ挙動を再現することはできません。
const isYoung = useSelector(state => state.user.age < 10);最後に、Redux系は、最近、Redux Toolkitをはじめ、ドキュメントの充実化など様々な改善が行われています。これから学び始める人には、良い環境になってきていると思います。
- 投稿日:2019-12-05T20:55:38+09:00
Kintoneをデータストアにしたときにハマること
Kintone初心者アドベントカレンダー四日目です。
ごめんなさい。昨日だったのをつい忘れてしまいました。
あぁ、やってしまった〜。
って感じです。
今日に四日目をかいてしまっています。
申し訳ございません。
よくハマりがちな罠についてかいていきたいと思います。なぜこのテーマなのか。
実はヒーローズリーグ大垣Pythonハッカソンで実はこの問題にハマりました。
それはAPIトークンとURLの場所を間違え実装してしまう。
これが一番の罠です。
Kintoneは実際言うとアプリを作るフレームワークに近いということを最初に認識していないといけません。
実際Pythonで今回は実装したのですが一番簡単なのはNode.jsです。
Python縛りがあったため自分は前職のC#の職場が実は本業はPythonなので学んでいたおかげでスイスイと組めました。
ですがここで間違えたのがAPIキーの場所です。
@RyBBさんも指摘していますが。
皆さん急ぐと間違えてしまいます。kintone_output.py#ここまでは順調です。 #ここからが問題です。 #何処を見るのでしょうか。 #APIトークンのCurlが書かれている後ろのURLです。 URL = #APIトークンもよく間違えます。 #注意が必要です。 #自分もNode.jsで教えていただいたときに間違えました。 #APIトークンの部分をコピーするのですよ。 API_TOKEN = #此処から先は通常コードで問題ないです。 #上の2つを間違えると痛い目見ます。 def get_kintone(url, api_token): "kintoneのレコードを全件取得する関数" headers = {"X-Cybozu-API-Token": api_token} resp = requests.get(url, headers=headers) return resp if __name__ == "__main__": RESP = get_kintone(URL, API_TOKEN) print(RESP.text)nodejs:kintone.jsconst kintone = require('@kintone/kintone-js-sdk'); //ここですよ。 //気をつけてください。 //自分もハマりました。 const domainName = 'SUB_DOMAIN.cybozu.com'; // Your kintone URL //ここですよ。 //気をつけてください。 const APIToken = 'YOUR_API_TOKEN'; // Your API Token const appId = ○○; // AppID const record = { text: { value: '追加したいテキスト' } }; // Connection const kintoneAuth = new kintone.Auth(); kintoneAuth.setApiToken({apiToken: APIToken}); const kintoneConnection = new kintone.Connection({domain: domainName, auth: kintoneAuth}); const kintoneRecord = new kintone.Record({connection: kintoneConnection}); // Add Record kintoneRecord.addRecord({app: appId, record: record}).then((rsp) => { console.log(rsp); }).catch((err) => { console.log(err.get()); });この二点を気をつければハッカソンでKintoneは無敵の武器になります。
下手にデータベースをいじるより楽になります。
みんなクラウドに走りがちですが自分の場合だったらハッカソンは断然Kintoneです。
ハッカソンの地図系マッシュアップにも無敵です。うーん。
来年もハッカソンではKinetone使います。
@RyBBさん。
- 投稿日:2019-12-05T20:23:46+09:00
【Node.js】zxcvbnを使ってパスワード強度をチェックする
「パスワード強度チェックするようなライブラリって何かあるのかな?」
と興味本位で調べてみたらzxcvbn
というものが見つかったので、ご紹介。zxcvbnとは
Dropbox社製のパスワード強度チェッカーです。
Node.js以外にも色々な言語に対応したライブラリが作られています。dropbox/zxcvbn: Low-Budget Password Strength Estimation
準備
$ npm i zxcvbn
基本的な使い方
とりあえず
hogehoge
という文字列に対してパスワード強度をチェックしてみましょう。const zxcvbn = require('zxcvbn'); const result = zxcvbn('hogehoge'); console.log(result);基本的な使い方はとても簡単ですね。
第2引数に入力を渡して更に細かい設定をすることも可能なようですが、今回は遊んでみたかっただけなので割愛させてください。出力内容を見てみましょう。
{ password: 'hogehoge', guesses: 20003, guesses_log10: 4.301095134950942, sequence: [ { pattern: 'repeat', i: 0, j: 7, token: 'hogehoge', base_token: 'hoge', base_guesses: 10001, base_matches: [Array], repeat_count: 2, guesses: 20002, guesses_log10: 4.301073422940843 } ], calc_time: 3, # zxcvbnが計算するのにかかった時間(ミリ秒) あんまり気にしなくていい crack_times_seconds: { # パスワードが特定されるまでにかかる時間(秒) # 4種類の攻撃パターンごとの想定時間 # 基本的には`offline_fast_hashing_1e10_per_second`の値だけ見ておけばいいかも online_throttling_100_per_hour: 720108, online_no_throttling_10_per_second: 2000.3, offline_slow_hashing_1e4_per_second: 2.0003, offline_fast_hashing_1e10_per_second: 0.0000020003 }, crack_times_display: { # パスワードが特定されるまでにかかる時間(わかりやすい形式) online_throttling_100_per_hour: '8 days', online_no_throttling_10_per_second: '33 minutes', offline_slow_hashing_1e4_per_second: '2 seconds', offline_fast_hashing_1e10_per_second: 'less than a second' }, score: 1, # 0 ~ 4でパスワード強度を評価する feedback: { warning: 'Repeats like "abcabcabc" are only slightly harder to guess than "abc"', suggestions: [ 'Add another word or two. Uncommon words are better.', 'Avoid repeated words and characters' ] } }
crack_times_seconds
の4種類のパターンについては、公式ドキュメントには次のように記述してありました。
- online_throttling_100_per_hour
online attack on a service that ratelimits password auth attempts.
- online_no_throttling_10_per_second
online attack on a service that doesn't ratelimit,
or where an attacker has outsmarted ratelimiting.
- offline_slow_hashing_1e4_per_second
offline attack. assumes multiple attackers,
proper user-unique salting, and a slow hash function
w/ moderate work factor, such as bcrypt, scrypt, PBKDF2.
- offline_fast_hashing_1e10_per_second
offline attack with user-unique salting but a fast hash
function like SHA-1, SHA-256 or MD5. A wide range of
reasonable numbers anywhere from one billion - one trillion
guesses per second, depending on number of cores and machines.
ballparking at 10B/sec.うーん、セキュリティって難しい!
参考
- 投稿日:2019-12-05T19:05:30+09:00
FileAPIを使って、CSVファイルからテーブルを作成する方法
はじめに
初めまして、初投稿です。読みづらい箇所もあると思いますが、よろしくお願いします。
エクセルなどで作成した表データをそのままテーブルにできないかと色々模索した際に作成したコードです。
「エクセルをそのままテーブルにしたい!」という私みたいなモノグサさんのお役に立てれば幸いです。環境
- html
- Javascript
- Google Chrome
- CSVファイル(Excel、Googleスプレッドシートなどで作成)
※条件:セル内改行なし ・ コンマ , は使用しない作業手順
以下の手順でコードを作成していきます。
- 1. 【HTML】CSVファイルの読み込み
- 2. 【HTML】テーブルを書き出す場所を作成する
- 3. 【Javascript】form要素(CSVファイル)の取得する
- 4. 【Javascript】CSVの各データを配列として読み込む-1
- 5. 【Javascript】CSVの各データを配列として読み込む-2
- 6. 【Javascript】テーブルに書き出す処理
- 7. 【Javascript】作成したテーブルの tr にidを付与する1 【HTML】FileAPIを使ってCSVファイルを読み込む
まず最初に、FileAPIを使ってCSVファイルを読み込みます。
accept="text/csv" とすることで、CSVファイルだけが選択できるようにします。sakusaku_table.html<input name="userFile" type="file" accept="text/csv">2 【HTML】テーブルを書き出す場所を作成する
テーブルを書き出したい場所に、次のコードを入れます。
div id="table" に、テーブルが出力されます。sakusaku_table.html<div class="container"> <div id="table" class="userTableBox"> </div>3 【Javascript】form要素(CSVファイルの中身)の取得する
form要素を取得し、FileReaderクラスでFile オブジェクト(CSV)の中身を読み込みます。
sakusaku_table.jsvar form = document.forms.userform; form.userFile.addEventListener('change', function(e) { var result = e.target.files[0]; var reader = new FileReader(); reader.readAsText( result );4 【Javascript】CSVの各データを配列として読み込む-1
3 で読み込んだCSVを配列化して取得します。
さらに、テーブルにした時の、1列の要素数を取得しておきます。sakusaku_table.js//1行ごとに配列化します。 reader.addEventListener('load', function() { var arr = reader.result.split('\n'); //全ての配列を個別に取得します。 for(i = 0; i < arr.length; i++){ var arrayElements = new Array(arr[i]); } //一番上にくる配列(<th>タグに入る)を取得 var first = arr[0]; var userData = reader.result.split(/,|;|\n/); //1列の要素数を取得 var rowCnt = parseInt(userData.length) / parseInt(arr.length);5 【Javascript】CSVの各データを配列として読み込む-2
配列データを中身の要素数(エクセルの列数)で均等に分割します。
sakusaku_table.jsArray.prototype.divide = function(n){ var userArray = this; var indx = 0; var results = []; var length = userArray.length; while (indx + n < length){ var result = userArray.slice(indx,indx+n); results.push(result); indx = indx + n } var rest = userArray.slice(indx,length+1); results.push(rest); return results; } userArray = reader.result.split(/,|;|\n/); dividedArrayData = userArray.divide(rowCnt);6 【Javascript】テーブルに書き出す処理
ここでついに、取り込んだCSVファイルのデータをテーブルに書き出します。この時点では、ヘッダー行のみ背景色と文字色を変更しておきます。さらに今後、CSSで見た目を修正する時のために各セルにIdとclassを追加する準備をします。
sakusaku_table.jsfunction makeTable(data, tableId){ var rows=[]; var table = document.createElement("table"); //完成したテーブルに <table id="userCsvTable">というidを付与します。 table.id = "userCsvTable" //(縦方向)の要素数を取得・縦方向の繰り返し処理回数を指定 for(i = 0; i < data.length; i++){ rows.push(table.insertRow(-1)); if (i === arr.length){ break } //(横方向)の要素数を取得・横方向の繰り返し処理回数を指定 これは要素数分、列を作成する処理 for(j = 0; j < data[0].length; j++){ cell=rows[i].insertCell(-1); // 追加した行にセルを追加してテキストを書き込む cell.appendChild(document.createTextNode(data[i][j])); if(i==0){ // ヘッダー行(エクセルの、1行目):<th>の部分・背景色と文字色の指定 cell.style.backgroundColor = "gray"; cell.style.color = "white"; //ヘッダー行:各セルのidにインデックス番号を入れる //(ここでは0ゼロしか入らない・次のステップで正しい連番に置き換えます。) cell.id = i //各セルのclassを追加。ここではヘッダー行は index としています。 cell.classList.add("index_"+i,"cellContents","header-cell"); } else{ //ヘッダー行以外の内容行(エクセルの、2行目以降) cell.id = i //各セルのclassを追加。2行目以降(ヘッダー行以外)は contents としています。 cell.classList.add("contents_"+i,"cellContents","body-cell") } } } // 指定したdiv要素にテーブルを追加する document.getElementById(tableId).appendChild(table); } // 表のデータ var data = dividedArrayData // 表を作成する makeTable(data,"table");7 【Javascript】作成したテーブルの<tr>にidを付与する
CSSで見た目を修正するために各セルに正式なidを付与して完成です。
sakusaku_table.js//作成したテーブルの tr にidを付与する var trId = "userElementID_" var tmp = document.getElementsByTagName("tr") ; for(var i=0;i<=arr.length-1;i++){ //id追加 tmp[i].setAttribute("id",trId+i); } //作成したテーブルの各セルにidを付与する ※⑥で先にセルにidを付与しないと動作しない var idName = "cell_" var cellId = document.getElementsByClassName("cellContents") ; for(var i=0;i<=tmp.length-1;i++){ //idを追加 cellId[i].setAttribute("id",idName+i); } }); });最後に
ここまでお付き合いいただきありがとうございます。
もっとシンプルな書き方とかあると思いますのでアドバイスなどいただけると嬉しいです。動作サンプルはこちらからご確認いただけます。
- 投稿日:2019-12-05T18:56:42+09:00
【Javascript】Array.splice()の動作確認
公式リファレンスより
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
splice() メソッドは、 (in place で) 既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更します。
有り体にいうと、配列から指定した範囲の要素を削除してそこに新たな要素を入れることができる。
構文は次の通り
var arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
start
→削除したい範囲の起点となる添字をおく
deleteConut
→start
からdeleteCount
で指定した範囲が削除の対象となる
item1,2,3,4,
→新たに追加される要素試してみる
const numbers1 = [1,2,3,4,5]; const numbers2 = [1,2,3,4,5]; const numbers3 = [1,2,3,4,5]; numbers1.splice(0, 3, 100); numbers2.splice(0, 3, 100, 200, 300,400, 500, 600); numbers3.splice(1, 3); console.log(numbers1); console.log(numbers2); console.log(numbers3); > Array [100, 4, 5] > Array [100, 200, 300, 400, 500, 600, 4, 5] > Array [1, 5]任意の位置に要素を追加することもできる
//三番目に100を挿入する。[1,2,100,3,4,5]としたい。 const numbers1 = [1,2,3,4,5]; numbers1.splice(2, 0, 100); console.log(numbers1); > Array [1, 2, 100, 3, 4, 5]
- 投稿日:2019-12-05T18:36:01+09:00
Array<object>→CSV→ダウンロードをJavaScriptで
こちらの記事で作った管理画面につけたCSVダウンロードボタンの実装方法についての覚え書きです。
JavaScriptでCSV変換・読み込みを行うライブラリは複数ありますが、カスタマイズ性を考えフルスクラッチで書いてみます。{ id: 1, name: "Mary", words: ["foo", "bar", "baz"] }このように単純なkey-valueを持ったオブジェクトの配列を、以下のようにヘッダー行を持ったCSVに変換します。
id,name,words 1,"Mary","["foo","bar","baz"]" . . .arrayToCsv.js/** * 単純なオブジェクトの配列をCSV文字列に変換 * @param {Array<object>} array * @param {Array<string>} keyArray * @returns {string} */ export default (array, keyArray = []) => { const keys = keyArray; array.forEach((data) => { Object.keys(data).forEach((key) => { if (keys.indexOf(key) === -1) { keys.push(key); } }); }); const lineArray = [keys.join(',')].concat( array.map((data) => keys.map((key) => { let valueString = ''; if (typeof data[key] === 'number') { valueString = data[key]; } else if (typeof data[key] === 'string') { valueString = `"${data[key]}"`; } else { valueString = `"${JSON.stringify(data[key]).replace(/"/g, '""')}"`; } return valueString; }).join(',')), ); return lineArray.join('\r\n'); };CSVの構造への変化のために、最初に全てのkeyを自動でリストアップしてから各行の値を入れていきます。
引数にデフォルトのkeyの配列を渡すことでkeyの順番を指定できるようにしました。この関数は文字列を返すのみなので、文字列からファイルを作成してダウンロードするための関数も用意します。
downloadStringFile.js/** * 文字列からファイルをダウンロード * @param {string} string * @param {string} fileName * @param {string} type * @param {boolean} insertTimestampToFilename */ export default (string, fileName, type, insertTimestampToFilename) => { const downLoadLink = document.createElement('a'); let name = fileName; if (insertTimestampToFilename) { const nameArray = name.split('.'); const timestamp = new Date().toLocaleString().replace(/[/ :]/g, '_'); nameArray[0] = `${nameArray[0]}_${timestamp}`; name = nameArray.join('.'); } downLoadLink.download = name; downLoadLink.href = URL.createObjectURL(new Blob([string], { type })); downLoadLink.dataset.downloadurl = [type, downLoadLink.download, downLoadLink.href].join(':'); downLoadLink.click(); };ブラウザ上でユーザーにダウンロードさせるためには、aタグの
download
属性を利用します。
insertTimestampToFilename
にtrueを渡すと、ファイル名にタイムスタンプが追加されます。実際に使うときはこんな感じです。
const csvContent = arrayToCsv(docs.map((doc) => (doc.data())), ['id']); downloadStringFile(csvContent, 'members.csv', 'text/csv', true);ここでは、Firestoreの
DocumentSnapshot
の配列を単純なオブジェクトの配列に変換したものをarrayToCsv
に渡し(id
を行の先頭にする)、タイムスタンプをつけたファイル名でダウンロードさせています。
- 投稿日:2019-12-05T18:31:28+09:00
コーディングのTIPS
コーディングのTIPSを随時更新していきます。
・ホバー時の色は、カラーコードではなくopacity(透明度)をいじると良い
・基本的には要素同士の関係よりクラスで指定した方が良いが、
liとか、絶対にタグが変わらないものとかは関係でもOK・タブレットで表示した時の見え方は、meta viewportでPC版に合わせる。そうするとタブレットの微妙な画面幅のことを考えなくて良くなる
・コーディングに入る前に、繰り返し使えそうなパーツは何かを考える
・余白はパーツのスタイルに入れず、利用箇所ごとで設定するとパーツの再利用性が上がる
・BEMの命名は、セクションごとではなくパーツごと
(ヘッダーにBlockが4個くらいあったりする)・Block名はキャメルケースで少し長めにするとかぶる心配が減ってやりやすい
・flexboxで、カラム数を「2or4」みたいにしたい時は、要素を2つずつdivで囲む
・色は最初に定義しておくとやりやすい
・SMACSSの各セクションの開始位置に、エディターでしおりをつけておくと捗る
・
li:not(:last-child):not(:first-child)
でfirst-child
とlast-child
以外という指定ができる
- 投稿日:2019-12-05T18:21:22+09:00
よくある動き、お気に入りの実装方法
コーディングをする上でよく遭遇する動きの実装方法を、随時更新していこうと思います。
ハンバーガーメニュー
CSSで実装。表示の動きとか場所は、以下の記事よりもう少し工夫した方が良いかも。
CSSだけで簡単!ハンバーガーメニューの作り方(スマホ対応)タブ切り替え
CSSで実装。
CSSだけでタブ切り替えを作る方法画像スライダー
Swiperを使って実装。
【実例12パターン】画像スライダーはSwiper使っておけば間違いない!実用的な使い方を紹介画像ポップアップ
これって方法が見つかってません。lightboxが有名だけど、何となくもっさりしていた印象があります。
- 投稿日:2019-12-05T17:57:08+09:00
JavaScriptで配列をディープコピーしたい
はじめに
フロントエンドエンジニア1年目の新人プログラマーです。
以下、自分の勉強と備忘録に記録しておきます。参考になれば幸いです。
困ったこと
JavaScriptの配列をコピーするだけならいろいろな方法がありますが、
私が困ったのは以下のケースです。const array1 = [{a:1, b:2}, {a:10,b:12}]array1は維持したまま、各要素のbだけを変更したい
これを単純に値渡しすると、うまくいきません。
const array2 = array1.concat() array2.forEach(x => x.b = null) console.log(array2) // (2) [{…}, {…}] 0: {a: 1, b: null} 1: {a: 10, b: null} console.log(array1) // (2) [{…}, {…}] 0: {a: 1, b: null} 1: {a: 10, b: null}どうやら配列[] 自体は値コピーできていても、各要素は同じ参照になっているのでうまくいかないそうです。
解決策
なので各要素も値コピーするために、次の実装が良さそうです。
const array2 = array1.map(x => { const y = {}; Object.assign(y,x); return y;}); array2.forEach(x => x.b = null) console.log(array2) // (2) [{…}, {…}] 0: {a: 1, b: null} 1: {a: 10, b: null} console.log(array1) // (2) [{…}, {…}] 0: {a: 1, b: 2} 1: {a: 10, b: 12}まとめ
言語によっては仕様が違うかもしれませんが、javaScriptにおいては上記の実装で解決できました。
- 投稿日:2019-12-05T17:34:49+09:00
Riot.js v4対応 Router
Riot.js Advent Calendar 2019 の5日目が空いていたので埋めます。
Riot Router
Riot.js
がv4
になったことでRiot Router
も新しくなっていたので再入門します。
https://github.com/riot/routeインストールは
npm i -S @riotjs/route
簡単なサンプル
まずは簡単なコードから試していきます。
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Riot Router</title> </head> <body> <div id="app"></div> <script src="./scripts/bundle.js"></script> </body> </html>index.jsimport { component, register } from 'riot' import { Router, Route } from '@riotjs/route' import App from './app.riot' import Hello from './hello.riot' import Goodbye from './goodbye.riot' // グローバルコンポーネントとして登録 register('my-router', Router); register('my-route', Route); register('my-hello', Hello); register('my-goodbye', Goodbye); // グローバルに登録せずに直接コンポーネントを生成&マウント component(App)(document.getElementById('app'));app.riot<my-app> <my-router> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/hello">Hello</a></li> <li><a href="/goodbye">Goodbye</a></li> </ul> </nav> <my-route path="/"> Welcome to home </my-route> <my-route path="/hello"> <my-hello></my-hello> </my-route> <my-route path="/goodbye"> <my-goodbye></my-goodbye> </my-route> </my-router> </my-app>hello.riot<my-hello> <p>Hello World!!</p> </my-hello>goodbye.riot<my-goodbye> <p>Goodbye World!!</p> </my-goodbye>私はWebpackを使っていますが、ここから下はお好みで。
package.json{ "name": "riotv4-router-sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack --mode production --devtool source-map", "start": "webpack-dev-server --inline --watch --hot --colors --content-base app/ --open-page index.html -d --port 4500" }, "keywords": [], "author": "KAJIKEN <kentaro@kajiken.jp> (http://kajiken.jp)", "license": "MIT", "dependencies": { "@riotjs/route": "^4.1.0" }, "devDependencies": { "@babel/core": "^7.7.2", "@babel/preset-env": "^7.7.1", "@riotjs/compiler": "^4.5.2", "@riotjs/hot-reload": "^4.0.0", "@riotjs/webpack-loader": "^4.0.1", "babel-loader": "^8.0.6", "riot": "^4.6.6", "webpack": "^4.41.2", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.9.0" } }webpack.config.jsconst path = require('path') module.exports = { mode: 'development', //mode: 'production', entry: './src/scripts/index.js', output: { path: path.resolve(__dirname, 'app/scripts'), filename: 'bundle.js', publicPath: '/scripts/', }, devtool: 'inline', //devtool: 'source-map', module: { rules: [ { test: /\.riot$/, exclude: /node_modules/, use: [{ loader: '@riotjs/webpack-loader', options: { hot: true, // set it to true if you are using hmr // add here all the other @riotjs/compiler options riot.js.org/compiler // template: 'pug' for example } }] }, { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }.babelrc{ "presets": [ [ "@babel/preset-env", { "modules": false, "targets": [ ">0.25%", "not ie 11", "not op_mini all" ] } ] ] }ディレクトリ構成. │ .babelrc │ package.json │ webpack.config.js │ ├─app │ │ index.html │ │ │ └─scripts │ bundle.js │ bundle.js.map │ ├─node_modules │ └─src └─scripts app.riot goodbye.riot hello.riot index.js注意点
このままではURLを直接入力した場合はエラーになります。
Web Server側の設定でトップに飛ばすなどすれば回避は可能です。
今回の例ですと、
webpack-dev-server
を使っているので、historyApiFallback
オプションを利用します。package.json(抜粋)"scripts": { "build": "webpack --mode production --devtool source-map", "start": "webpack-dev-server --inline --watch --hot --colors --content-base app/ --open-page index.html --historyApiFallback true -d --port 4500" },
historyApiFallback:true
とすることで404 not found
の時にindex.html
に飛ばされます。Web Server側で対処できないような場合はハッシュを使いましょう。
app.riot<my-app> <my-router> <nav> <ul> <li><a href="#/">Home</a></li> <li><a href="#/hello">Hello</a></li> <li><a href="#/goodbye">Goodbye</a></li> </ul> </nav> <my-route path="(/|/#/)"> Welcome to home </my-route> <my-route path="/#/hello"> <my-hello></my-hello> </my-route> <my-route path="/#/goodbye"> <my-goodbye></my-goodbye> </my-route> </my-router> </my-app>
- 投稿日:2019-12-05T17:30:22+09:00
年末まで毎日webサイトを作り続ける大学生 〜48日目 マウスの軌跡を描く & マウスの動きに画像が付いてくる〜
はじめに
こんにちは!@70days_jsです。
14日目に、マウスの動きに合わせて黒い丸を表示したかったけどできませんでした。
今日はそのリベンジです。48日目。(2019/12/5)
よろしくお願いします。サイトURL
https://sin2cos21.github.io/day48.html
やったこと
主に2つのことをやりました。
- マウスに合わせて画像が付いてくる
- マウスの軌跡を描く
では1から説明していきます。
1. マウスに合わせて画像が付いてくる
html全体↓
<body> <div id="mouseMove"> <div id="positionDisplay"></div> <img src="day48men.png" id="men" class="position" /> <img src="day48doctor.png" id="doctor" class="position" /> </div> </body>css全体↓
body { margin: 0; overflow: hidden; } #mouseMove { height: 100vh; width: 100vw; } .position { position: absolute; height: 50px; width: 50px; transform: translateY(-50%) translateX(-50%); } .dot { position: absolute; background-color: black; width: 3px; height: 3px; } #men { top: -100%; left: -100%; } #doctor { top: -100%; left: -100%; }JavaScript 全体↓
let mouseX = 0; let mouseY = 0; let mouseMove = document.getElementById("mouseMove"); let positionDisplay = document.getElementById("positionDisplay"); let men = document.getElementById("men"); let doctor = document.getElementById("doctor"); let x; let y; mouseMove.addEventListener("mousemove", function(e) { x = e.pageX; y = e.pageY; let coordinate = " (X座標:" + x + " Y座標:" + y + ")"; positionDisplay.innerHTML = coordinate; doctor.style.top = y + "px"; doctor.style.left = x + "px"; men.style.top = y + 25 + "px"; men.style.left = x + 30 + "px"; let div = document.createElement("div"); div.setAttribute("class", "dot"); div.style.top = y + "px"; div.style.left = x + "px"; mouseMove.appendChild(div); });#mouseMove {
height: 100vh;
width: 100vw;
}id="mouseMove"のdivを画面いっぱいに広げて、その中で検知するようにしています。
bodyではなく、divを作って設定しておくことで、mouseMoveの大きさを変えるだけで、いつでも表示したい範囲を調整できるようになります。.position {
position: absolute;
height: 50px;
width: 50px;
transform: translateY(-50%) translateX(-50%);
}.positionクラスで画像の位置をabsoluteにしています。
transformは画像の表示場所をマウスの中心に置くために付けています。位置の検知はJavaScriptで、mousemoveイベントを使い、pageXとpageYのプロパティから取得しています。
mouseMove.addEventListener("mousemove", function(e) {
x = e.pageX;
y = e.pageY;マウスに画像が付いてくるのは、スタイルのtopとleftをpageXとpageYの値に変えてやるだけです。
doctor.style.top = y + "px";
doctor.style.left = x + "px";2. マウスの軌跡を描く
これもmousemoveイベントをトリガーにして行っています。
mouseMove.addEventListener("mousemove", function(e) {
//処理
}イベントが起こるごとに、divを作っています。
let div = document.createElement("div");
作ったdivに用意してあるクラスをつけます。
div.setAttribute("class", "dot");
.dotクラス↓
.dot {
position: absolute;
background-color: black;
width: 3px;
height: 3px;
}positionがabsoluteなので、あとはtopとleftの値をpageXとpageXの値に設定して、appendchildを使い子要素に加えたら完成です。
div.style.top = y + "px";
div.style.left = x + "px";
mouseMove.appendChild(div);感想
14日目には分からなかったことができて嬉しいです。
いまいち成長しているかどうか分からなかったんですが、過去にできなかったことができるようになっていたので少し自信がつきました。
とりあえずこの調子で年末まで頑張ります。最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。
参考
アイコンをお借りしました。ありがとうございます!
- 投稿日:2019-12-05T17:29:46+09:00
年末まで毎日webサイトを作り続ける大学生 〜48日目 マウスの軌跡を描く & マウスの動きに画像が付いてくる〜
はじめに
こんにちは!@70days_jsです。
14日目に、マウスの動きに合わせて黒い丸を表示したかったけどできませんでした。
今日はそのリベンジです。48日目。(2019/12/5)
よろしくお願いします。サイトURL
https://sin2cos21.github.io/day48.html
やったこと
主に2つのことをやりました。
- マウスに合わせて画像が付いてくる
- マウスの軌跡を描く
では1から説明していきます。
1. マウスに合わせて画像が付いてくる
html全体↓
<body> <div id="mouseMove"> <div id="positionDisplay"></div> <img src="day48men.png" id="men" class="position" /> <img src="day48doctor.png" id="doctor" class="position" /> </div> </body>css全体↓
body { margin: 0; overflow: hidden; } #mouseMove { height: 100vh; width: 100vw; } .position { position: absolute; height: 50px; width: 50px; transform: translateY(-50%) translateX(-50%); } .dot { position: absolute; background-color: black; width: 3px; height: 3px; } #men { top: -100%; left: -100%; } #doctor { top: -100%; left: -100%; }JavaScript 全体↓
let mouseX = 0; let mouseY = 0; let mouseMove = document.getElementById("mouseMove"); let positionDisplay = document.getElementById("positionDisplay"); let men = document.getElementById("men"); let doctor = document.getElementById("doctor"); let x; let y; mouseMove.addEventListener("mousemove", function(e) { x = e.pageX; y = e.pageY; let coordinate = " (X座標:" + x + " Y座標:" + y + ")"; positionDisplay.innerHTML = coordinate; doctor.style.top = y + "px"; doctor.style.left = x + "px"; men.style.top = y + 25 + "px"; men.style.left = x + 30 + "px"; let div = document.createElement("div"); div.setAttribute("class", "dot"); div.style.top = y + "px"; div.style.left = x + "px"; mouseMove.appendChild(div); });#mouseMove {
height: 100vh;
width: 100vw;
}id="mouseMove"のdivを画面いっぱいに広げて、その中で検知するようにしています。
bodyではなく、divを作って設定しておくことで、mouseMoveの大きさを変えるだけで、いつでも表示したい範囲を調整できるようになります。.position {
position: absolute;
height: 50px;
width: 50px;
transform: translateY(-50%) translateX(-50%);
}.positionクラスで画像の位置をabsoluteにしています。
transformは画像の表示場所をマウスの中心に置くために付けています。位置の検知はJavaScriptで、mousemoveイベントを使い、pageXとpageYのプロパティから取得しています。
mouseMove.addEventListener("mousemove", function(e) {
x = e.pageX;
y = e.pageY;マウスに画像が付いてくるのは、スタイルのtopとleftをpageXとpageYの値に変えてやるだけです。
doctor.style.top = y + "px";
doctor.style.left = x + "px";2. マウスの軌跡を描く
これもmousemoveイベントをトリガーにして行っています。
mouseMove.addEventListener("mousemove", function(e) {
//処理
}イベントが起こるごとに、divを作っています。
let div = document.createElement("div");
作ったdivに用意してあるクラスをつけます。
div.setAttribute("class", "dot");
.dotクラス↓
.dot {
position: absolute;
background-color: black;
width: 3px;
height: 3px;
}positionがabsoluteなので、あとはtopとleftの値をpageXとpageXの値に設定して、appendchildを使い子要素に加えたら完成です。
div.style.top = y + "px";
div.style.left = x + "px";
mouseMove.appendChild(div);感想
14日目には分からなかったことができて嬉しいです。
いまいち成長しているかどうか分からなかったんですが、過去にできなかったことができるようになっていたので少し自信がつきました。
とりあえずこの調子で年末まで頑張ります。最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。
参考
アイコンをお借りしました。ありがとうございます!
- 投稿日:2019-12-05T17:01:05+09:00
「Angular は全員 80 点」 ≠ 「誰が書いても同じようなコード」
はじめに
前の記事『フロントエンドフレームワーク選定前に知っておくべき Angular の 6 つの問題点と、それでも Angular を選ぶ理由』で、「Angular は大規模向け」というあやしい噂をひっくり返したかったのですが、 @okunokentaro さんの 『Angularでの開発を快適に進めるために知っておきたいこと』というアンサー記事の登場で、「Angular は全員 80 点」というこれまたあやしい言説が広まる結果になってしまいました。
アンサー記事を書いていただけたことについては本当に本当に感謝しかありません。記事には納得できる部分もたくさんあります。
ただ、「Angular は全員 80 点」という言説には明らかに語弊があり、しかも非常にキャッチーなのが危険です。
語弊
「Angular は全員 80 点」という言説からはまるで「誰が書いても同じようなコードになる」かのような印象を受けます。誰かの「Java は全員 50 点」という言説にはそういった意図が含まれるかもしれませんが、少なくとも @okunokentaro さんの意図は違うと思います。もちろん Java であれ Angular であれ、誰が書いても同じようなコードにはなりません。
そして「Angular は全員 80 点」というフレーズはあまりにキャッチーであり、波及しやすく、文脈無視 & 思考停止して受け入れてしまいそうになります。おそらく @okunokentaro さんも文脈無視 & 思考停止は望んでいないと思います。
言葉が一人歩きしてキャッチフレーズのように広まってしまう前に、まずは文脈を確認しましょう。
ちゃんと読んで
まず、ここでは引用しませんが、「100点か50点か、それより全員80点を」という節には技術選定の話しかありません。技術選定については Angular なら誰でも 80 点になるというのは私もその通りだと思います。
次の節「足かせとしてのAngular」にも 80 点という言葉が出てきます。切り取って引用するとまた語弊が生まれやすいと思うので、節をまるごと引用します。
足かせとしてのAngular
もう一つ、足かせとしてのAngularの側面として、あまり凝ったことをしにくいようにできています。
例えば、Angularではhtmlとcssとtsを組み合わせた、シンプルなコンポーネントを毎回定義していきます。「Higher Order Componentはないのか」「html + cssよりtsx + CSS in JSのほうがよいのではないか」その意見は分かります。
ですが、Angularは他人に説明するとき「この表示についての実装は、このtsを見てください。それがブラウザ上でどうなるかは、このhtmlを見てください。そのときのデザインはcssに宣言があります」といったように、それぞれのファイルごとの責務に応じた説明が可能です。
この構造は、デベロッパー・デザイナーでの分業や、社内複数案件のコンテキストスイッチの切り替えの観点で特に役に立ちます。全社的にAngularを採用し、プロダクトを増やしていった場合に「同じ会社なのに別案件のことはすべてキャッチアップし直し」といったことが起こらないのです。即戦力、即80点です。
ReactのCSS in JSやtsxを非難する気は全くなく、自分もあれは便利だと思うので、趣味プロで思う存分書いた経験はあります。ですが、あらゆるデベロッパーのReact案件コードを読ませてもらって思ったものとして「この案件のReactではこうなっている。あっちの案件のReactでは別のああなっている」といった事象がとても多かった記憶があります。同じ社内のReact案件でも流行り廃りがあるとすら聞きました。
Angularはこれを防いでいます。Angular案件は、少なくともここ3年大きな変化がありません。あるとすればNgRxがデファクトになった、くらいでしょうか。なのでもちろんあまりにも古いAngularと現在のAngularに乖離がないのかといえばありますが、Angular動向そのものをキャッチアップすれば、それは変化について行けているということです。すべての些末なライブラリをすべてキャッチアップする必要はありません。
このようにAngularを選択する意義には、組織やチームのことを考えた結果といったこともあるのです。そして、チームが大きくなる案件ほどプロジェクト自体が大規模になりやすい傾向はありますので、そこからAngularは大規模に向いているという論説が導かれるのであれば、それは筆者は納得できます。
(191204追記)公開後に言及が多いので触れておきますが、20点や、もっというと0点かもしれない人たちが、何もせずにAngularを使えばいきなり80点になるという誤解がありそうなので釈明します。
Angularを使った場合「Angularの作法通りこのように書くとよい」というコードレビューが周知しやすく、それによって知識差があったとしてもリードデベロッパーがチーム全体の平均点を80点まで引き上げやすいという意図で書きました。残念ながらまわりに、Angularについて詳しい人が誰もいない場合にいきなり0点から80点になるかというと、そういうわけではなく、点数を積み重ねる学習は必要になってきます。これを学習コストの高さと捉えられることもあれば、ウェブ開発を進める上での必要最低限が揃っていると捉えることもできると思います。
ここは言葉足らずだったなと感じていますので、あえて追記させていただきました。
追記より前の論旨は、「1 度 Angular を学んで 80 点のコードが書けるようになれば、他の Angular 案件でも即 80 点のコードが書ける。 React は案件によって組み合わせるライブラリが異なることが多いので、案件が変われば学び直す必要がある。」といったことだと思います(これは私の勘違いの可能性があります、ご自身で読んでください)。この論旨であれば納得です。
ところが、これが前の節の技術選定における「Angular は全員 80 点」(こっちは Angular を学んだことがなくても誰でも 80 点)と混ざって理解され、大きな誤解が生じているように思います。
追記では「知識差があったとしてもリードデベロッパーがチーム全体の平均点を80点まで引き上げやすいという意図で書きました」と明確な意図が示されています(私が追記より前の内容から汲み取った論旨とは違ったようです)。さらに、「残念ながらまわりに、Angularについて詳しい人が誰もいない場合にいきなり0点から80点になるかというと、そういうわけではなく、点数を積み重ねる学習は必要になってきます」とまで丁寧に言及されています。
Angular Way
Angularを使った場合「Angularの作法通りこのように書くとよい」というコードレビューが周知しやすく、それによって知識差があったとしてもリードデベロッパーがチーム全体の平均点を80点まで引き上げやすいという意図で書きました。
Angular の作法はしばしば Angular Way と呼ばれます。おそらく Rails Way から来ているのだと思います。
Ruby on Rails の「設定より規約」アプローチは、誰でも同じように書くことを促し、学習コストも減らしているように思います。ちなみに Next.js と Nuxt.js からも「設定より規約」の匂いがします(すみません、私はどちらもまだちゃんと使ったことがありません、間違っていたらごめんなさい)。
Angular Way も、明確な規約として機能するのであれば、学習コストを減らしてくれるでしょうし、コードも似てくると思います。残念ながら私にはその Angular Way が見つけられませんでした。公式のスタイルガイド(とそのチェック用 TSLint ルール集 codelyzer)は Angular の作法ではありますが、規約として機能するような The Angular Way ではありません。
まとめ
「リードデベロッパーがチーム全体の平均点を80点まで引き上げやすい」という言説は本当なのかもしれません。ただ、どうすればそのようなリードデベロッパーになれるのでしょうか。周りにそのようなひとがいれば違うのでしょう。私は Angular で 80 点だと自負できるコードは書けません。
前の記事で引用した NRI の事例 では、 Angular のためにここまでするかという規模の社内標準ルールを定めた話が出てきました。
Angular では、 RxJS について言及せずとも、たとえばフォームひとつ作るにしても、双方向データバインディングを使ってもいいし、リアクティブフォームを使っても、どちらも使わなくてもよく、いったい何が正解なのでしょうか。ユースケースによるのでしょうか。
私は Ruby 的な “Diversity is Good” ではなく PEP 20 (The Zen of Python) の “There should be one-- and preferably only one --obvious way to do it.” という考え方が好きです。 Angular がこっちだとは思いません。
- 投稿日:2019-12-05T16:56:50+09:00
kintone × Asana
kintone Advent Calender 2019 Part2 12月22日
去年に続き、今年も参加させて頂きました。
さて、早速ですが今回は、kintone と Asana を連携しようと思います。Asana ってなに?
https://asana.com/ja
チームや個人でプロジェクトのタスク管理をできるツールらしいです。
(とりあえず連携ネタを書こうと以前ちょこっとだけ触ったツールを選んだので、良く知らない。)無料版触ってみた感想
kintone は標準の一覧がリッチではないですが、Asana は豊富に用意されていたので、
プロジェクト全体のタスク確認をするのはとても便利そうです。
ただ、自分が持っているタスク一覧は見辛い印象でした。わかりやすくメリットデメリットを上げたので、↓の用途で連携できないか試してみます。
Asana -> プロジェクトの全体把握
kintone -> 個人のタスク管理連携概要
kintone から Asana のタスクを取得し、ステータスが完了になったら Asana 側のタスクも完了する
必要環境
- kintone 開発環境
開発者ライセンス
https://developer.cybozu.io/hc/ja/articles/200720464#step4- Asana
無料トライアル
https://asana.com/ja/?utm_source=app.asana.com&utm_campaign=app.asana.com#trialAsana の設定
パーソナルアクセストークンの発行
https://app.asana.com/0/developer-console
↑にアクセスし、トークンを発行する。
一度しか表示されないので大事にメモしましょう。
注意文言がありますが、個人で使うだけなので問題なし。
kintone の設定
アプリ作成
アプリストアの To Do アプリを使います。
Asana 側の情報を保持するために、タスクID(taskID) という数値フィールドとプロジェクト名(projectName)という文字列一行フィールドを追加します。
カスタマイズファイル作成
以下のファイルを kintone アプリに適用します。
・kintone UI Component
・sample.jssample.js(() => { const ASANA_TOKEN = 'xxxx'; // 先ほどメモしたパーソナルアクセストークン const APP_ID = kintone.app.getId(); const ASANA_API_BASE_URL = 'https://app.asana.com/api/1.0'; const ASANA_ID = 'xxxx'; // Asana のログインID const HEADER = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${ASANA_TOKEN}` }; const GET_TASKS_BUTTON = new kintoneUIComponent.Button({ text: 'タスクを取得する', type: 'submit' }); const SPINNER = new kintoneUIComponent.Spinner({}); /** * Asana API 実行用 * @param {string} urlPath パス * @param {string} method メソッド * @param {object} header ヘッダー * @param {object} body ボディ * @returns {Promise} kintone.proxy() */ const runAsanaApi = function(urlPath, method, header, body) { return kintone.proxy(ASANA_API_BASE_URL + urlPath, method, header, body); }; /** * Asana のタスクを取得する関数 * @returns {Promise} タスク */ const fetchAsanaTasks = async function() { try { // ワークスペースすべて取得 const worksaceResp = await runAsanaApi('/workspaces', 'GET', HEADER, {}); if (worksaceResp[1] !== 200) throw worksaceResp; const tasksPromises = JSON.parse(worksaceResp[0]).data.map(val => { return runAsanaApi(`/tasks?workspace=${val.gid}&assignee=${ASANA_ID}`, 'GET', HEADER, {}); }); // タスクすべて取得 const tasksResp = await kintone.Promise.all(tasksPromises); if (tasksResp[0][1] !== 200) throw tasksResp; const tasksDetailPromises = JSON.parse(tasksResp[0][0]).data.map(val => { return runAsanaApi(`/tasks/${val.gid}`, 'GET', HEADER, {}); }); // タスク詳細取得 const detailResp = await kintone.Promise.all(tasksDetailPromises); if (detailResp[0][1] !== 200) throw detailResp; return detailResp.map(task => { return JSON.parse(task[0]).data; }); } catch (err) { console.log(err); return new Error('Asana のタスク取得に失敗しました。'); } }; /** * タスクから kintone にインサートする関数 * @param {Object} tasks Asana のタスク */ const insertKintoneRecord = function(tasks) { const promises = tasks.map(async task => { const query = `taskID = "${task.gid}"`; const body = { app: APP_ID, query: query }; const getRecordsResp = await kintone.api(kintone.api.url('/k/v1/records'), 'GET', body); if (!getRecordsResp.records.length) { const postBody = { app: APP_ID, record: { To_Do: { value: task.name, }, taskID: { value: task.gid }, Assignees: { value: [ {code: kintone.getLoginUser().code} ] }, Duedate: { value: task.due_on }, projectName: { value: task.projects[0].name } } }; return kintone.api(kintone.api.url('/k/v1/record'), 'POST', postBody); } return kintone.Promise.resolve('新規タスクなし'); }); return kintone.Promise.all(promises); }; /** * レコード情報から Asana タスクを更新する * @param {Object} opt_record kintone イベントオブジェクトのレコード */ const updateAsanaTask = function(opt_record) { const record = opt_record; const body = { data: { completed: true } }; return runAsanaApi(`/tasks/${record.taskID.value}`, 'PUT', HEADER, body); }; document.getElementsByTagName('BODY')[0].appendChild(SPINNER.render()); GET_TASKS_BUTTON.on('click', async () => { SPINNER.show(); try { // タスク取得して kintone に登録 const tasks = await fetchAsanaTasks(); await insertKintoneRecord(tasks); SPINNER.hide(); location.reload(); } catch (err) { SPINNER.hide(); window.alert(err); } }); kintone.events.on('app.record.index.show', event => { kintone.app.getHeaderMenuSpaceElement().appendChild(GET_TASKS_BUTTON.render()); return event; }); kintone.events.on('app.record.detail.process.proceed', async event => { if (event.nextStatus.value !== '完了') return event; SPINNER.show(); try { const resp = await updateAsanaTask(event.record); if (resp[1] !== 200) throw resp; SPINNER.hide(); return event; } catch (err) { event.error = 'Asana タスク更新失敗しました。'; SPINNER.hide(); return event; } }); })();動作確認
まとめ
いろいろと改良点がありますが、とりあえずは Asana との連携が実装できました。
無料版だと「未完了のタスクを取得する」といったリクエストが書けず、全タスクを取得してインサートする、という処理になってしまったのが残念ポイントです。
タスクの詳細情報も1回のリクエストで取得できなかったので↓のように3回リクエストする必要がありました。
ワークスペース取得 -> タスク概要取得 -> タスク詳細取得Premium access
https://developers.asana.com/docs/#search-tasks-in-a-workspace
この API でクエリとかが書けそう?async await のエラーハンドリングわからん。
注意事項
cybozu developer network のコーディングガイドラインに記載されている通り、
本番環境ではちゃんとトークンは隠しましょう。
プラグイン化するなり、あとは OAuth に変更するなりが必要ですね。この記事はあくまで動作確認 or 個人用ということで。
Asana の開発用環境とか取得できたら嬉しいなー。
- 投稿日:2019-12-05T16:08:41+09:00
お気持ちをJSで読める記号にするアプリ作った
前置き
クソアプリ2 Advent Calendar 2019の
((-~[]+[]+-~[]>>-~[])+(-~[]<<-~[]))
日目やらしてもらいます
記号プログラミングが好きなりゅうって言います!素直な気持ちを伝えるのは恥ずかしい…
それならちょっとだけ読みにくい形にしちゃえ!!
というわけで任意の文章をJSな記号に変える素敵なクソアプリを作りましたよかったら遊んでみてね => https://s17001.github.io/okimotiToKigou/dist/
アプリケーション概要
任意の文字を入力すると、jsな記号プログラミングに変換するWEBアプリケーションです
元の文章に戻すには
・ ブラウザのコンソールに直で貼ったり
・ evalしたりする(evalするときは変数使わないモードにしてね!)
などすれば元の文章を取り出せますこのアプリでメッセージを作って、気になるあの子に記号でメッセージを送ってみよう!!
生成される文字数が多いのでだいたいツイートできませんが仕様です技術的なお話
使ったもの
- Vue.js
リポジトリ: https://github.com/s17001/okimotiToKigou
記号のお話
基本としてJavaScriptにおける暗黙的な型変換をいっぱい利用しております
数字まわり
~[] // -1 からの配列をビット反転して数字にする -~[] // 1 頭に - をつけて負の符号を打ち消す -~[] + -~[] // 2 これらの足し算文字まわり
![]+[] // "false" falseをString型へ変換させる (![]+[])[-~[]] // "a" falseの1番目をとってくる例えば僕の名前である ryu を作るなら以下の通りです
(!![]+[])[-~[]]+(-~[]/[]+[])[((-~[]+[]+-~[]>>-~[])+(-~[]<<-~[]))]+([][""]+[])[-[]] // ryu記号から作り出せる文字、数字は全部で以下の通りです
true, false, undefined, infinity, NaN, Object, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0上記の文字から取り出せない文字はUnicodeからの呼び出しを用いることにより記号で表現できるようになります
絵文字とUnicodeのお話
emojiを記号で表現する際に、サロゲートペアな絵文字たちがバラバラになってしまう問題がありました
'?'.charCodeAt().toString(16) // "d83e" "\ud83e" // "�"対象の文字のlength分対応する記号のコードを生成して対応しました
// charCode単位でイテレートする for (let i = 0, l = '?'.length; i < l; ++i) { const parsedArr = [...(input.charCodeAt(i).toString(16) + [])] const flamedArr = parsedArr.map(いい感じに記号にする関数) /* 以下いい感じの処理たち */ }ツイートするために
ツイートさせる画面のクエリパラメータに+を含めないために温かみのある素直なコードを書きました
<div> <a class="tweet" :href="'https://twitter.com/intent/tweet?text='+twitter+'&hashtags=kuso_app_2019'" target="_blank">ついーとするためのボタン</a> </div>computed:{ twitter(){ return encodeURI(this.tweetText).split('+').join("%2B") } }とても参考になる記事 + @
javascript
php
僭越ながら僕のスライド
https://speakerdeck.com/s17001/ji-hao-puroguramingufalseji-da-cheng
まとめ
このアプリを作るまでに変数がいまどの型になってるかがある程度わかるようになりました!!!
みんなも記号プログラミングをして人間コンパイラになりましょう!さあ!!!!!!!!!思いついたアプリケーションを思いのままに作っていくのは最高に楽しい、さらにいい感じに記事にまとめる機会があるので、クソアプリなアドベントカレンダーはとてもいい文化だなぁと思いました。
ここまで読んでくれてありがとうございました!!!
残りの2019年もよき日を過ごしましょう。それでは、良いお年を。
- 投稿日:2019-12-05T15:23:06+09:00
JavaScriptの基本文法
こちらは、 CAMエンジニア Advent Calendar 2019 7日目の記事です。
https://qiita.com/advent-calendar/2019/cam-inc
昨日は tomomi_bro さんの
CSSアニメーションで雪だるまとツリーを作ろう⛄?
でした。本日はjavascriptの記事です!
かなり初心者向けの記事になるので、もう知ってるよと言う方は明日に期待してください笑
javascript歴約1年の僕がこの1年で学んでみて疑問に思ったことや詰まったところを重点に書いてきます!
ちなみにフロントエンドエンジニアをやらせていただいています。
プログラミングをはじめたいけど何からやればいいの?系の記事です。環境は問いませんが、おすすめは
https://codepen.io/pen/
さんです。Web上でフロントエンド の環境のプレイグランドを提供してくれます。
なんとipadやスマホでも動くので今時の開発にもってこいです。JavaScriptについて
はじめに Java と Javascript はインドとインドネシアくらい違います。
長いのでJavaScript = js
と呼びます。
Javaは万能で有名ですが js はブラウザが認識できる数少ない言語です。ブラウザって言うのはこの記事を見るために開いたやつ(safariとか chromeとか firefoxとか)
実はこれらのブラウザが認識できる言語は js くらいしかないのです。← まじかよ (PHPは一旦おいときます。)なのでブラウザと密接にお仕事をするフロントエンドエンジニアのほとんどは実質このjsに縛られるということですね。
EcmaScriptについて
Javascriptは別名 EcmaScript と呼ばれていて(Ecmaはあくまで仕様だとかの話があるそうですが)
hogeを調べたいときは大体
js hogehoge
es6 hogehoge
ででます。
(hoge
とはプログラムやってる人が使いがちななになに
です、僕も最初わかりませんでした。なんでhogeなんでしょうね。)es6ってなんぞやって話ですが、EcmaScriptの策定バージョンで、
ドラクエ2、ドラクエ3、ドラクエ4、ドラクエ5的 なノリです。じゃあなんでes6なの?って話ですが、現在普及しているブラウザの安定した言語仕様がes6だからです。
単純に ドラクエ6 が今流行ってるからです。(ドラクエは5派です。ビアンカ派です。)
ちなみに最新はes2019です、es2017から毎年バージョンアップされるようになってこの調子で行くと来年はes2020になります。
ウイイレ、パワプロ君みたいよね。なのでes6はes2016とも呼ばれています。
これからjavascriptを始める人はなるべくこのes6の仕様にのっとって書いた方がいいらしいです。僕もes6からしか分かりません。基本文法
本とかに書いているのは基本文法です。非常に大事なことが書いてあるのですが、実際書いてみないといつ使うのかわからない状態で習熟度的に身につきません。
なのでプログラミングをはじめたいと思っている人はとりあえず本はしまって何もない状態から書いてみましょう。
js を書くにおいて知って置くべき点は以下だけです。変数の定義 (値を箱に入れることができます。)
const box = '箱の値'
値の入れ直しがある場合
let box = '変わる場合があるよ'
入れることのできる値は形が決まっています。
1. 文字 'もじはチョンチョンで囲む'
2. 数字 831 ←数字はそのままかける
3. 真偽値 true false ←この二つだけ
3. 配列 [1番目, 2番目, 3番目] ← 配列は大カッコで囲む
4. オブジェクト {name: 'apple', color: 'red'} ← オブジェクトは中カッコで囲む
5. undefined ← js特有の何も定義されていないことを表す値
6. null ← こちらは何もないことを定義している値if文 条件分岐
// コメント文と言って'//'これを先頭につけると実行されないのでコメント、メモを残すことができる。 const box = 'apple' // もし box の中身が 文字の'apple'だったら'これはりんごだよ'と言う文字を返す if (box === 'apple') { return 'これはりんごだよ' }function 関数
お好みの関数を定義することができる。
// 関数内の()のなかみは引数と呼ぶ function say(name) { // 引数をconsole.log()と言う関数の引数に渡して実行する。 console.log(name) } // 使うとき say('私の名前はhogeです。') // 以下みたいにもかける(thisの話は一旦おいといて) const say = (name) => { console.log(name) }これだけでjsを書くことができます。
つまづく点
これは僕が入社して、実際の現場で使われているコードのなかで「なんだこれ!」となったシリーズです。
!!(ビックリマーク二つ JS)
これは最初戸惑いました、主に条件式の中に書かれていることが多いのですが、
jsでは先頭にビックリマークをつけるとBoolean型の反転に変換してくれる機能があるのです。← ナンジャそりゃ
これを利用してビックリマークを二つつけると反転の反転、すなわちBooleanに擬似キャスト(変換)してくれるのです。
変人が思う変人は正常な人か! つまりはそう言うことです。// boxは文字列でBooleanじゃない const box = '文字があるよ' console.log(!!box) // trueと出る(Booleanになってる!)if (!!inputName) { return '名前が入力されました。' }アローファンクション二重
最初はわけが分かりませんでしたが、要はアローファンクションのなかでコールバック関数にアローファンクションを入れているだけです。
const say = name => name => // 処理 // 以下と同じ const say = function (name) { return function (name) { // 処理 } }&二つ ショートサーキット評価
こちらは && と || の論理演算を使ったもので、論理演算の評価の性質上以下のようになります。
const box = true // boxがtrueだったら右を実行 box && 'boxがtrueだよ' // boxがtrueだったら左を実行 box || 'boxがfalseだよ'変数の先頭に__アンダースコア
こちらは他のプログラミング言語を触ったことがある人はわかる、privateな変数です。
残念ながらES6ではprivate変数は実装されていないため、明示的にこのような書き方をしています。
動作的には普通に変数を定義するのと変わりません。
ちなみにtc39ではprivatefieldsなるものが実装予定なので今後対応されるかもしれないです。
https://github.com/tc39/proposal-class-fieldsconst __private = 'これはプライベート'まとめ
javascriptは学ぶことが多いです。他の言語と比べても結構特殊な部類に入ると思います。
しかし他の言語よりもWeb業界ではメジャーな言語でもあるので学ぶ価値は十分にあると思います。
特にフロントエンド を目指している人(Webサイトを作ってみたい人)には非常におすすめです。
僕はプログラミング歴約1年ですが、まだまだ学びきれていないと感じるので気長に頑張りたいと思います。
次回はnonoakijさんの記事です!お楽しみに〜
- 投稿日:2019-12-05T15:21:44+09:00
堅牢な node.js プロジェクトのアーキテクチャとは?
こちらの記事は、Sam Quinn 氏により2019年 4月に公開された『 Bulletproof node.js project architecture 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。GitHub repositoryでの実装例: 2019/04/21 アップデート
始めに
Express.jsは、node.js のREST APIを作成するための優れたフレームワークですが、node.jsプロジェクトの設計方法についての手がかりを与えてくれるものではありません。
ばからしく聞こえるかもしれませんが、この問題は確かに存在するのです。
node.jsプロジェクト構造の正しい設計により、コードの重複を回避でき、安定性を向上させます。また、正しく設計されていれば、サービスをスケールさせるときに役に立つかもしれません。
この記事は、貧弱な構造のnode.jsプロジェクト、望ましくないパターン、そしてコードリファクタリングと構造の改善に無数の時間を費やし対応してきた、長年の経験に基づく広範囲な探求です。
本記事に合わせnode.jsプロジェクトのアーキテクチャを見直すために助けが必要な場合は、santiago@softwareontheroad.comにご連絡ください。
目次
- フォルダ構造
- 3層アーキテクチャ
- サービスレイヤー
- Pub/Subレイヤー
- Dependency Injection (DI) --※日本語で「依存の注入」
- ユニットテスト
- Cron ジョブと定期的なタスク
- 構成情報及びシークレット
- ローダー 例(GitHub repojitory)
フォルダ構造
以下はこれから話を進めていくnode.jsプロジェクトの構造です。
構築するすべてのnode.js REST APIサービスで、これをを使用します。では、それぞれのコンポーネントが何をするのか詳しく見ていきましょう。
│ app.js # App entry point └───api # Express route controllers for all the endpoints of the app └───config # Environment variables and configuration related stuff └───jobs # Jobs definitions for agenda.js └───loaders # Split the startup process into modules └───models # Database models └───services # All the business logic is here └───subscribers # Event handlers for async task └───types # Type declaration files (d.ts) for Typescript単なるJavascript ファイルの並び替えをする方法ではありません..
3層アーキテクチャ
下記のアイデアは、「関心の分離」の原則に基づき、ビジネスロジックをnode.js APIルーティングから分離させるものです。
これはあなたがいつか、CLIツールでビジネスロジックを使用したい、定期的なタスク処理では十分でない、と思うようになったときのためです。
そしてnode.jsサーバーからそれ自体へのAPI呼び出しは、良いアイディアではありません...
コントローラーにビジネスロジックを入れてはダメです!!
express.jsコントローラーを使用してアプリケーションのビジネスロジックを保存したくなるかもしれませんが、これはすぐにスパゲッティコードになります。ユニットテストを書く必要があるときには、リクエストまたはレスポンスexpress.jsオブジェクトの複雑なモックを扱うことになります。
いつ応答を送信するべきかを区別するのは複雑です。 バックグランドで処理が続行され、その後 応答がクライアントに送信されたとしましょう。
以下は望ましくない例です。
route.post('/', async (req, res, next) => { // This should be a middleware or should be handled by a library like Joi. const userDTO = req.body; const isUserValid = validators.user(userDTO) if(!isUserValid) { return res.status(400).end(); } // Lot of business logic here... const userRecord = await UserModel.create(userDTO); delete userRecord.password; delete userRecord.salt; const companyRecord = await CompanyModel.create(userRecord); const companyDashboard = await CompanyDashboard.create(userRecord, companyRecord); ...whatever... // And here is the 'optimization' that mess up everything. // The response is sent to client... res.json({ user: userRecord, company: companyRecord }); // But code execution continues :( const salaryRecord = await SalaryModel.create(userRecord, companyRecord); eventTracker.track('user_signup',userRecord,companyRecord,salaryRecord); intercom.createUser(userRecord); gaAnalytics.event('user_signup',userRecord); await EmailService.startSignupSequence(userRecord) });ビジネスロジックをサービスレイヤーで扱っている
このレイヤーは、ビジネスロジックが存在すべき場所です。
それは、node.jsに適用されるSOLID原則に従って、明確な目的(情報)を持つクラスのコレクションです。
このレイヤーには「SQLクエリ」のいかなるフォームも存在するべきではありません。データアクセス層を使用してください。
- express.jsルーターからソースコードを遠ざける
- リクエストまたはレスポンスオブジェクトをサービスレイヤーに渡さない
- ステータスコードやヘッダーなど、HTTPトランスポートレイヤーに関連するものをサービスレイヤーから返さない
例
route.post('/', validators.userSignup, // this middleware take care of validation async (req, res, next) => { // The actual responsability of the route layer. const userDTO = req.body; // Call to service layer. // Abstraction on how to access the data layer and the business logic. const { user, company } = await UserService.Signup(userDTO); // Return a response to client. return res.json({ user, company }); });サービスが裏でどのように機能するかを以下に示します。
import UserModel from '../models/user'; import CompanyModel from '../models/company'; export default class UserService { async Signup(user) { const userRecord = await UserModel.create(user); const companyRecord = await CompanyModel.create(userRecord); // needs userRecord to have the database id const salaryRecord = await SalaryModel.create(userRecord, companyRecord); // depends on user and company to be created ...whatever await EmailService.startSignupSequence(userRecord) ...do more stuff return { user: userRecord, company: companyRecord }; } }Pub/Sub レイヤーも利用する
pub / subパターンは,、ここで提案されている従来の3層アーキテクチャを超えていますが、非常に便利です。
すぐにユーザーを作成できるシンプルなnode.js APIエンドポイントは、分析サービスであったり、あるいは電子メールシーケンスの開始などのサードパーティサービスを呼び出そうとするかもしれません。
遅かれ早かれ、そのシンプルな「作成」の操作はいくつかのことを実行し、1,000行にも及ぶコードがすべて1つの関数中で実行されることになるでしょう。
それは単一責任の原則に反しています。
したがって最初から責任を分離しておくほうが良く、それによってコードの保守性を維持できます。
import UserModel from '../models/user'; import CompanyModel from '../models/company'; import SalaryModel from '../models/salary'; export default class UserService() { async Signup(user) { const userRecord = await UserModel.create(user); const companyRecord = await CompanyModel.create(user); const salaryRecord = await SalaryModel.create(user, salary); eventTracker.track( 'user_signup', userRecord, companyRecord, salaryRecord ); intercom.createUser( userRecord ); gaAnalytics.event( 'user_signup', userRecord ); await EmailService.startSignupSequence(userRecord) ...more stuff return { user: userRecord, company: companyRecord }; } }依存サービスへの呼び出し命令は、最良の方法ではありません。
ここでより良いアプローチは、イベントを発行することです。(例.「ユーザーはこのメールでサインアップしました」)
これで完了です。リスナーの仕事は、リスナーの責任としています。
import UserModel from '../models/user'; import CompanyModel from '../models/company'; import SalaryModel from '../models/salary'; export default class UserService() { async Signup(user) { const userRecord = await this.userModel.create(user); const companyRecord = await this.companyModel.create(user); this.eventEmitter.emit('user_signup', { user: userRecord, company: companyRecord }) return userRecord } }イベントハンドラー/リスナーを複数のファイルに分割できています。
eventEmitter.on('user_signup', ({ user, company }) => { eventTracker.track( 'user_signup', user, company, ); intercom.createUser( user ); gaAnalytics.event( 'user_signup', user ); })eventEmitter.on('user_signup', async ({ user, company }) => { const salaryRecord = await SalaryModel.create(user, company); })eventEmitter.on('user_signup', async ({ user, company }) => { await EmailService.startSignupSequence(user) })awaitステートメントをtry-catchブロックにラップする、もしくは単に失敗処理として” unhandledPromise “プロセスとして処理することもできます。
依存性の注入 (D.I.)
依存性の注入(D.I.)、または制御の反転(IoC)は、クラスまたは関数の依存関係をコンストラクターに「注入」または渡すことで、コードの編成に役立つ一般的なパターンです。
このようにすることで、例えばサービスの単体テストを作成するときや、サービスが別のコンテキストで使用されるとき、「互換性のある依存関係」を注入する柔軟性が得られます。
D.I. なしのコード
import UserModel from '../models/user'; import CompanyModel from '../models/company'; import SalaryModel from '../models/salary'; class UserService { constructor(){} Sigup(){ // Caling UserMode, CompanyModel, etc ... } }手動でD.I. を実装したコード
export default class UserService { constructor(userModel, companyModel, salaryModel){ this.userModel = userModel; this.companyModel = companyModel; this.salaryModel = salaryModel; } getMyUser(userId){ // models available throug 'this' const user = this.userModel.findById(userId); return user; } }これでカスタマイズされた依存関係を注入できます。
import UserService from '../services/user'; import UserModel from '../models/user'; import CompanyModel from '../models/company'; const salaryModelMock = { calculateNetSalary(){ return 42; } } const userServiceInstance = new UserService(userModel, companyModel, salaryModelMock); const user = await userServiceInstance.getMyUser('12346');サービスが持つことのできる依存関係の量は無限で、新しく追加する際にいちいちインスタンス化をリファクタリングするのは、退屈でエラーが発生しやすいタスクです。
そういうわけでDI フレームワークが作成されました。
これにより、クラスで依存関係を宣言し、そのクラスのインスタンスが必要な場合には、 'Service Locator'を呼び出すだけでよくなります。
“ typedi “を用いてnode.jsにDIをもたらすnpmライブラリの例を見てみましょう。
“ typedi “の使用方法の詳細については公式ドキュメントをご覧ください。
注意: typescript での例
import { Service } from 'typedi'; @Service() export default class UserService { constructor( private userModel, private companyModel, private salaryModel ){} getMyUser(userId){ const user = this.userModel.findById(userId); return user; } }ここでtypediはUserServiceが必要とする依存関係を解決します。
services/user.jsimport { Container } from 'typedi'; import UserService from '../services/user'; const userServiceInstance = Container.get(UserService); const user = await userServiceInstance.getMyUser('12346');サービスロケーター呼び出しの乱用はアンチパターンです
Node.jsのExpress.jsでDIを使用する
express.jsでDIを使用する
これがnode.jsプロジェクトアーキテクチャのパズルの最後のピースです。ルーティングレイヤー
route.post('/', async (req, res, next) => { const userDTO = req.body; const userServiceInstance = Container.get(UserService) // Service locator const { user, company } = userServiceInstance.Signup(userDTO); return res.json({ user, company }); });Awesome! 素晴らしいプロジェクトになりました!
とても整理されていて、「今すぐ何かをコーディングしたい!」という気持ちになりますね。
単体テストの例
DI とこれらの設計パターンを使用することにより、単体テストは非常にシンプルになります。
リクエスト / レスポンス オブジェクトのモックや “ require … “ などの呼び出しを行う必要はありません。
例:サインアップユーザーメソッドの単体テスト
tests/unit/services/user.jsimport UserService from '../../../src/services/user'; describe('User service unit tests', () => { describe('Signup', () => { test('Should create user record and emit user_signup event', async () => { const eventEmitterService = { emit: jest.fn(), }; const userModel = { create: (user) => { return { ...user, _id: 'mock-user-id' } }, }; const companyModel = { create: (user) => { return { owner: user._id, companyTaxId: '12345', } }, }; const userInput= { fullname: 'User Unit Test', email: 'test@example.com', }; const userService = new UserService(userModel, companyModel, eventEmitterService); const userRecord = await userService.SignUp(teamId.toHexString(), userInput); expect(userRecord).toBeDefined(); expect(userRecord._id).toBeDefined(); expect(eventEmitterService.emit).toBeCalled(); }); }) })Cronジョブと定期的なタスク
ここまででビジネスロジックがサービスレイヤーにカプセル化されたので、Cronジョブから使用するのが簡単になりました。
node.js の
setTimeout
や、その他の原始的なコード実行を遅らせる方法に頼るのではなく、ジョブやデータベース内での処理を永続化するフレームワークを使用するべきです。こうすることで、失敗したジョブの制御や、成功した人のフィードバックを得ることができます。
node.js.
別の記事で、これらのグッドプラクティスについて既に書いていますので、こちらのガイドを確認してください。構成情報及びシークレット
node.jsにおいて研鑽された概念である「Twelve-Factor App」に従えば、 APIキーとデータベース文字列の対応情報を保存するもっとも良い方法は、dotenvを使用することです。
決してコミットしてはいけない
.env
ファイルを配置すると(ただし、リポジトリにデフォルト値で存在する必要があります)、 npm パッケージのdotenv
は
.env
ファイルをロードし、変数を node.js のprocess.env
オブジェクトに挿入します。これでも十分かもしれませんが、もうワンステップ加えたいと思います。
npmパッケージの dotenv が 参照するディレクトリ(今回の例では /config)配下に" index.js "ファイルを配置し、
.env
ファイルを読み込むことで 、変数を格納するオブジェクトを使用できます。これで構造とコードの自動補完を保持できます。config/index.jsconst dotenv = require('dotenv'); // config() will read your .env file, parse the contents, assign it to process.env. dotenv.config(); export default { port: process.env.PORT, databaseURL: process.env.DATABASE_URI, paypal: { publicKey: process.env.PAYPAL_PUBLIC_KEY, secretKey: process.env.PAYPAL_SECRET_KEY, }, paypal: { publicKey: process.env.PAYPAL_PUBLIC_KEY, secretKey: process.env.PAYPAL_SECRET_KEY, }, mailchimp: { apiKey: process.env.MAILCHIMP_API_KEY, sender: process.env.MAILCHIMP_SENDER, } }こうすることで
process.env.MY_RANDOM_VAR
によってコード記述の氾濫を回避でき、自動補完によって環境変数の命名方法を知る必要がなくなります。ローダー
このパターンはW3Techマイクロフレームワークから取得しましたが、そのパッケージには依存していません。
このアイデアでは、node.jsサービスの起動プロセスをテスト可能なモジュールに分割することが可能です。
古典的なexpress.jsアプリの立ち上げ手順を見てみましょう
const mongoose = require('mongoose'); const express = require('express'); const bodyParser = require('body-parser'); const session = require('express-session'); const cors = require('cors'); const errorhandler = require('errorhandler'); const app = express(); app.get('/status', (req, res) => { res.status(200).end(); }); app.head('/status', (req, res) => { res.status(200).end(); }); app.use(cors()); app.use(require('morgan')('dev')); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json(setupForStripeWebhooks)); app.use(require('method-override')()); app.use(express.static(__dirname + '/public')); app.use(session({ secret: process.env.SECRET, cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true }); require('./config/passport'); require('./models/user'); require('./models/company'); app.use(require('./routes')); app.use((req, res, next) => { var err = new Error('Not Found'); err.status = 404; next(err); }); app.use((err, req, res) => { res.status(err.status || 500); res.json({'errors': { message: err.message, error: {} }}); }); ... more stuff ... maybe start up Redis ... maybe add more middlewares async function startServer() { app.listen(process.env.PORT, err => { if (err) { console.log(err); return; } console.log(`Your server is ready !`); }); } // Run the async function to start our server startServer();ご覧のとおり、アプリケーションのこの部分は非常に煩雑化しています。
これに関して効果的な対処法は以下です。
const loaders = require('./loaders'); const express = require('express'); async function startServer() { const app = express(); await loaders.init({ expressApp: app }); app.listen(process.env.PORT, err => { if (err) { console.log(err); return; } console.log(`Your server is ready !`); }); } startServer();ここでローダーは、簡潔な目的を持つ単なる小さなファイルです
loaders/index.jsimport expressLoader from './express'; import mongooseLoader from './mongoose'; export default async ({ expressApp }) => { const mongoConnection = await mongooseLoader(); console.log('MongoDB Intialized'); await expressLoader({ app: expressApp }); console.log('Express Intialized'); // ... more loaders can be here // ... Initialize agenda // ... or Redis, or whatever you want }express ローダー
loaders/express.jsimport * as express from 'express'; import * as bodyParser from 'body-parser'; import * as cors from 'cors'; export default async ({ app }: { app: express.Application }) => { app.get('/status', (req, res) => { res.status(200).end(); }); app.head('/status', (req, res) => { res.status(200).end(); }); app.enable('trust proxy'); app.use(cors()); app.use(require('morgan')('dev')); app.use(bodyParser.urlencoded({ extended: false })); // ...More middlewares // Return the express app return app; })mongo ローダー
loaders/mongoose.jsimport * as mongoose from 'mongoose' export default async (): Promise<any> => { const connection = await mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true }); return connection.connection.db; }最後に..
ここまでで、私達は実績のあるnode.jsプロジェクトストラクチャについて深く理解できました。要約すると下記のような内容でしたね。
- 3層アーキテクチャを使用する
- ビジネスロジックをexpress.jsコントローラーに入れない
- PubSubパターンを使用してバックグラウンドタスクのイベントを発行する
- 負担を減らすためDI を実装する
- パスワード、シークレット、APIキーなどを漏らさないために構成マネージャーを使用する
- node.jsサーバー構成を、個別にロードできる小さな- モジュールに分割する
リポジトリの例はこちらからご覧ください。
ちょっと待って!まだ続きがあります。
この記事を楽しんでいただけたら、他の有益な情報も見逃すことがないように、私のメーリングリストを購読することをお勧めします。
何かを売りつけるようなことはしません。約束します!
今後の投稿もお見逃しなく!きっと気に入ってくれると思います :)
この記事のような、すごい記事がたくさんあるので、是非私のブログに来てください。
翻訳協力
Original Author: Sam Quinn
Thank you for letting us share your knowledge!この記事は以下の方々のご協力により公開する事が出来ました。
改めて感謝致します。
選定担当: @aoharu
翻訳担当: @upaldus
監査担当: @aoharu
公開担当: @posaune0423私たちと一緒に記事を作りませんか?
私たちは、海外の良質な記事を複数の優秀なエンジニアの方の協力を経て、日本語に翻訳し記事を公開しています。
活動に共感していただける方、良質な記事を多くの方に広めることに興味のある方は、ぜひご連絡ください。
Mailでタイトルを「参加希望」としたうえでメッセージをいただく、もしくはTwitterでメッセージをいただければ、選考のちお手伝いして頂ける部分についてご紹介させていただく事が可能です。
※ 頂いたメッセージには必ずご返信させて頂きます。ご意見・ご感想をお待ちしております
今回の記事は、いかがだったでしょうか?
・こうしたら良かった、もっとこうして欲しい、こうした方が良いのではないか
・こういったところが良かった
などなど、率直なご意見を募集しております。
いただいたお声は、今後の記事の質向上に役立たせていただきますので、お気軽にコメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
みなさまのメッセージをお待ちしております。
- 投稿日:2019-12-05T15:21:15+09:00
これWebなの!?PlayCanvasで作られたサンプル8個紹介!
1つめ!PlayCanvasを始めると誰もが通る道!"My First Project"
PlayCanvasに登録をすると強制的に飛ばされるこのプロジェクト、PlayCanvasで物理演算とキーボード操作が出来ることを教えてくれる
次からはPlayCanvasで作られた面白いプロジェクトを紹介します。
https://playcanvas.com/editor/project/3716672つめ!めちゃくちゃキレイ!MozillaとPlayCanvasが共同で開発した "After the Flood"
GIGAZINEでも紹介されていた。 After the FloodこちらはPlayCanvasの組み合わせです。
http://aftertheflood.playcanvas.com/
「WebGL 2」を自分のブラウザで実際に動かして確認できる「After The Flood」- GIGAZINE
3つめ!Twitterカードとして埋め込めるゲームPlayCanvas運営事務局作成のサンディーフォト
PlayCanvasで作成したゲームはTwitterのPlayCardという形でTwitter上で遊ぶことができます。
【PlayCanvas事例紹介】
— PlayCanvas運営事務局 (@playcanvasJP) September 18, 2019
Twitterで遊べる広告! -Sandyフォト-
Twitter Player Cardで遊べるリッチメディアコンテンツです。ぐりぐり回してPlayCanvas日本公式キャラクター、"Sandyちゃん"のベストショットを撮ってシェアしましょう!#playcanvas #sandyphoto #webgl https://t.co/Qg9Dz7MTo34つめ Ship Viewer
After the Floodのような形です。After the Flood同様、プロジェクトが公開されているので、自分で作る際にはカメラの動きなどが参考になります!
https://playcanv.as/p/sBlZkpKX/
5つめ!Webサイトから6dof / 3dofのVRのが使える WebVR Lab
PlayCanvasの公式のチュートリアルにある。WebVR Lab、iPhone, Oculus Quest, Androidなどで開くと、それぞれVRを体験することができます。
※iOS13(には未対応みたいです...(APIが変わったため)https://developer.playcanvas.com/ja/tutorials/webvr-lab/
6つめ!PhotonとPlayCanvasを組み合わせて作られたマルチプレイのゲーム
https://playcanvas.utautattaro.com/photon/
https://qiita.com/utautattaro/items/d754b9c0fcb50dc1100a
7つめ!AR Image Tracking Starter Kit
マーカーレスのWebARのプロジェクト!PlayCanvasのアカウントと8thwallのアカウントさえあればかんたんにWebARが作れる。
https://playcanvas.com/project/631721/overview/ar-image-tracking-starter-kit
使い方
- 8th Wallで始めるインストール不要なWebAR開発【PlayCanvas】8つめ!PlayCanvasで作られたウェブサイト!
PlayCanvasで作られたウェブサイトです。
https://pcpo.sabo.jp/3dwebsite/
PlayCanvas開発で参考になりそうな記事の一覧です。
入門
- PlayCanvas入門- モデルの作成~ゲームに入れ込むまで
- JavaScriptでスロットを実装する。【PlayCanvas】
PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む
その他の記事はこちらになります。
その他関連
- PlayCanvasタグの付いた記事一覧
PlayCanvasのユーザー会のSlackを作りました!
少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!
- 投稿日:2019-12-05T14:42:51+09:00
javascriptのDateで勘違いしたこと
はじめに
Date型から、月と日を取得しようとしていた。
間違っているところ
現在の日付.2019年12月5日なのに、
月が11,日が4になってしまっている。//現時点のDateを取得 const today = new Date(); console.log(today);//→Thu Dec 05 2019 14:31:31 GMT+0900 (日本標準時) //月を取得 console.log(today.getMonth());//→11 //何日か取得 console.log(today.getDay());//→4修正後
//月を取得 console.log(today.getMonth() + 1);//→12 //何日か取得 console.log(today.getDate());//→5おわり
getMonthって返却値が0~11なのですね。
参考リンクgetDateに関しては、1~31のようです。
参考リンクgetDay使ってたのは、完全に勘違いでした。
getDayは曜日を返却してくれるようです。(0~6)
参考リンク
- 投稿日:2019-12-05T14:25:59+09:00
addEventListenerの使い方【備忘録】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>addevent</title> </head> <body> <input id="button" type="button" value="クリックしてください"> <script> window.onload = function() { const e = document.getElementById('button'); e.addEventListener('click', function(){ console.log('イベント発生'); }, false); } </script> </body> </html>
- 投稿日:2019-12-05T14:25:18+09:00
Nuxt2.10.2と@nuxt/typescript-build環境でtypescript3.7以降を入れているとbuild失敗する
概要
ハマったのでメモです。
自社サイトのNoSchoolにTypeScriptを導入したのですが、
npm run build
時に失敗してしまって、結果TypeScriptのバージョンを巻き戻したら治りました。環境
- Nuxt v2.10.2
- @nuxt/typescript-build v0.3.2
- TypeScript 3.7.2
補足
TypeScript3.7を入れていたのは、最近Optional Chainingが出て話題だったのでNuxtで使えないかなと思って入れたのがきっかけだったのですが、その後、VSCodeで
vetur.useWorkspaceDependencies
をTrueにして利用することにしたので入れていました。なので、あくまで開発環境でVeturを動かすことを目的として、devDependenciesのほうにTypeScriptを入れていました。
package.json"devDependencies": { ..., "typescript": "^3.7.2", ... }起こったこと
npm run dev
は通っていたのですが、npm run build
時に下記ログで失敗しました。ERROR in /opt/noschool/client/node_modules/@nuxt/types/config/build.d.ts(18,10): 18:10 Import declaration conflicts with local declaration of 'FileLoaderOptions'. 16 | import { Options as OptimizeCssAssetsWebpackPluginOptions } from 'optimize-css-assets-webpack-plugin' 17 | import { TerserPluginOptions } from 'terser-webpack-plugin' > 18 | import { Options as FileLoaderOptions } from 'file-loader' | ^ 19 | import { Options as PugOptions } from 'pug' 20 | import * as Less from 'less' 21 | import { Options as SassOptions } from 'node-sass'原因の探し方
まずは
Import declaration conflicts
でググってみると、下記issueが見つかりました。https://github.com/angular/protractor/issues/5348
結局ここにTypeScriptのバージョンを下げたら治ったといったことが書いてあるので、自分の手元でもTypeScriptを3.6.4に戻してみたらbuildが通りました。
package.json"devDependencies": { ..., "typescript": "^3.6.4", ... }結果として3.6.4に戻した格好になります。
そもそも
@nuxt/typescript-build
がTypeScript3.6を対象にしているので、合わせるのが無難でしたね。
https://github.com/nuxt/typescript/blob/master/packages/typescript-build/package.json下記の記事で
NuxtではTypeScript3.7は使えない
と書いてありますが、使えないとは具体的にどういう意味だったのかはわからなかったので、今回buildが失敗して実感しました。
https://qiita.com/simochee/items/a2eca2ea8761409889beわからないこと
とはいえ3.7以降だとなぜダメで、3.6に戻すと治ったのかはよくわからず。僕自身としては3.6から3.7になってOptional Chaining以外に何が変わったのかよく知らないので、調べてみてわかったら追記します。
とりあえず上記のエラーメッセージでググったときに記事がほとんどなかったので、QiitaのSEOにあやかって同じミスでハマってしまった方がすぐに解決できたら嬉しいです。