- 投稿日:2020-06-29T23:58:50+09:00
コンポーネント設計で悩んでいる人へ。簡単にコンポーネント設計できるESLint Plugin作りましたよ??
この記事について
この記事は、筆者が作ったESLint プラグインの紹介記事なります。
「 なぜ作ったのか? 」「 コンポーネント設計とは何か? 」という議題に触れ、コンポーネント設計の難しさやこのプラグインの有意性について語っています。
筆者は専門家では無いので間違っている所や認識のズレなどがあるかもしれませんが、もしあった場合は編集リクエストやコメントにて指摘してくれると嬉しいです。後、現在この記事で紹介するプラグインは「 コンポーネントは JSX で記述されている 」ことを想定しています。
作ったモノ
Specific Component Designを導入するためのELint プラグインです。
プラグインの詳細は、github のドキュメントに日本語で書いていますので、是非見てみて下さい。
以下の画像は、github へのリンクカードです。インストール方法
npmに公開しているので、以下のコマンドでインストールする事が出来ます。
npmでインストール$> npm install eslint-plugin-scdyarn でも大丈夫です。
yarnでインストール$> yarn add eslint-plugin-scd
eslint-plugin-scd の使い方
使い方は、他のESLint プラグインと同じです。この記事では、
.eslintrc.js
で設定する方法を紹介します。.eslintrc.jsで設定module.exports = { extends: ["plugin:scd/recommended"], // デフォルトのルールを適用 plugins: ["scd"], // eslint-plugin-scdを適用 rules: { // オプションを渡してカスタマイズ出来ます。 // 詳しくはgihubリポジトリに書いています。( 日本語ドキュメントあります ) "scd/nue-element-size": [ "error", { max: 10, }, ], }, };現在のところ
jsx
とtsx
の方では動作している事を確認しています。
typescript
では、以下の設定で動くことを確認しました。.eslintrc.js(typescriptに対応)module.exports = { env: { browser: true, es2020: true, }, extends: ["eslint:recommended", "plugin:scd/recommended"], parser: "@typescript-eslint/parser", parserOptions: { ecmaFeatures: { jsx: true, }, sourceType: "module", }, plugins: ["scd", "@typescript-eslint"], };Specific Component Design について
Specific Component Design( 以下 SCD と表記します )は、具体的なコンポーネント基準によってコンポーネントを分類して設計するための設計思想です。
SCDの目的は、コンポーネントに明確な基準を設け、より設計しやすく・より管理しやすくすることです。
またSCDは、コンポーネントを「フロントエンドとしてのコンポーネント
」と捉え、フロントエンドフレームワークでのコンポーネント設計で役立つことを想定しています。なので、CSS などの設計についてはSCDに含まれていませんので、ご注意ください。具体的なコンポーネント基準とは?
SCD はコンポーネント単位が小さい順に、
- Nue
- Otem
- Pafe
- Page
- Module
の 5 つの分類があり、それぞれの分類には明確な分類基準があります。
それら明確な基準は以下のようになります。Nue
Nue は最小単位のコンポーネントを分類します。
規約
- コンポーネントファイルを
nues
またはnue
フォルダー下に入れる- 要素数( html タグやコンポーネントの数 )は、0~5 個以内する
- State は持たせない
- Hooks の使用禁止
Otem
Nueよりも大きなコンポーネントを分類します。
規約
- コンポーネントファイルを
otems
またはotem
フォルダー下に入れる- 要素数( html タグやコンポーネントの数 )は、5~10 個以内する
- State は持たせない
- Hooks の使用禁止
Pafe
NueやOtemよりも大きなコンポーネント、または State を保持ているコンポーネントを分類します。
規約
- コンポーネントファイルを
pafes
またはpafe
フォルダー下に入れる- 要素数( html タグやコンポーネントの数 )が10 個以上、または State を保持している
- Hooks を使用してもよい
Page
Page
は、ページを構成するコンポーネントを分類します。規約
- コンポーネントファイルを
pages
またはpage
フォルダー下に入れる<main></main>
を最低一つ持たせる- Hooks を使用してもよい
Module
Module
は、上記のどれにも分類できない例外的なコンポーネントを分類します。
Module
には明確な規約はありません。自由に設計できます。※ Module に分類するのは、どうしても他のモノに分類出来ない時です。なるべくNue・Otem・Pafe・Pageに分類できるようにして下さい。
使ってみた感じ
以下の gif 画像は、プラグインを導入してNueコンポーネントを定義した時の挙動です。( VSCode の拡張を使ってます )
Nueコンポーネントは、要素数が 5 個以下かつHooks を使ってはダメなので、要素数が 6 個以上またはHooks を使った時にエラーになります。
また、分かりにくいですが
nue
フォルダー下にコンポーネントファイルがあることも確認できます。(src > nue > sample.jsx
ってところ )コンポーネント設計の難しいさ
ここからはコンポーネント設計の難しさについて解説したいと思いますが、皆さんはコンポーネント設計は簡単だと思いますか?
私は難しいと思っています。そして、その理由は大きく分けて再利用性
と拡張性
の二つがあると思ってます。
これら二つはコンポーネントが求めるもの・作る意義であり、より良いコンポーネントを作るためには両者は必要になってきます。しかし、再利用性
と拡張性
は相反しており双方を維持することはできません。なので、私たちがコンポーネント設計する場合は、必ず再利用性
か拡張性
のどちらかを捨て、どちらかを高めるという設計を状況に応じてする必要があります。この「 状況に応じて
再利用性
か拡張性
のどちらかを捨てる 」という事が、コンポーネント設計における最大の難しさだと思います。さて、その難しいさを作っている
再利用性
と拡張性
ですが具体的にはどんなモノなのでしょうか?
それを理解するために実際に具体例を出して見てみましょう。コンポーネント設計における再利用性
再利用性
というのは、名前の通り再利用しやすいかどうかを主眼に置いており、コンポーネントを扱いやすくするためにはとても大事な要素です。ですが、「 再利用しやすい 」とはどういう事でしょうか?そこをちゃんと考えてみましょう。以下に具体例を示します。
// 記事をカードで表示 const EntryCard = ({ entry }) => ( <li className="entry-card"> <div className="thumbnail"> <img src={entry.thumbnail} /> </div> <h3>{entry.title}</h3> <div className="author"> <p>{entry.author.name}</p> </div> </li> ); // 記事一覧ページ const ExplorePage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} /> ))} </ul> </main> ); // 投稿した記事一覧ページ const MyEntriesPage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} /> ))} </ul> </main> );上記のソースコードは、記事情報からカードを表示するコンポーネント
<EntryCard />
があり、そのコンポーネントを<ExplorePage />
と<MyEntriesPage />
の二つのコンポーネントが使っています。
注目すべき点は、ExplorePage
とMyEntriesPage
の双方ともに<EntryCard />
を同じ方法で使っていて、同じ表示をすることが出来ています。これは普通に hmlt タグで書いた時よりもシンプルに書けているのが分かりますね。この「 同じ方法で同じ表示が出来ている 」という所が非常に重要です。これはつまり、同じ方法でも違う挙動( 表示 )をしてたり、同じ挙動( 表示 )でも違う使い方をしている場合は、「 再利用しやすい 」とは言えません。
例えば、以下のようなコンポーネントがそれです。
// ランダムで記事をカードで表示 const RandomEntryCard = ({ entry }) => { const isShow = Math.floor(Math.random() * 100) < 50; if (!isShow) return null; // falseならnullを返す return ( <li className="entry-card"> <div className="thumbnail"> <img src={entry.thumbnail} /> </div> <h3>{entry.title}</h3> <div className="author"> <p>{entry.author.name}</p> </div> </li> ); };
<RandomEntryCard />
はランダムで渡された entry 情報を表示します。このコンポーネントはさっきの<EntryCard />
と同じ方法で使うことができますが、明らかに使いにくい事が分かります。これが「 同じ方法でも違う挙動( 表示 )をしている 」という事です。
では次に、「 同じ挙動( 表示 )でも違う使い方をしている 」はどうでしょうか?
以下のコンポーネントがそれです。// 記事をカードで表示 const EntryOrBlogCard = ({ entry, blog }) => { entry = entry || blog; return ( <li className="entry-card"> <div className="thumbnail"> <img src={entry.thumbnail} /> </div> <h3>{entry.title}</h3> <div className="author"> <p>{entry.author.name}</p> </div> </li> ); };
<EntryOrBlogCard />
は、<EntryOrBlogCard entry={entry} />
または<EntryOrBlogCard blog={entry} />
で同じ挙動を示します。
しかし、コンポーネントの内部を知らない人が<EntryOrBlogCard />
を使っている所を見た時、Props が違う事から連想して、何か違う挙動をするように見えてしまいます。そうなると、このコンポーネントを使うのにわざわざ内部構造を理解しなければならなくなり、非常に利用しにくい状態になってしまいます。これが「 同じ挙動( 表示 )でも違う使い方をしている 」という事です。
コンポーネント設計における拡張性
それでは、次に
拡張性
について見ていきましょう。
この記事での拡張性
とは「 後付けがしやすい 」という事ではなく、「 挙動( 表示 )を変化させやすい 」という事です。1言葉だけじゃ分かりにくいと思うので、例を見てみましょう。
// 記事をカードで表示 const EntryCard = ({ entry }) => ( <li className="entry-card"> <div className="thumbnail"> <img src={entry.thumbnail} /> </div> <h3>{entry.title}</h3> <div className="author"> <p>{entry.author.name}</p> </div> </li> ); // 記事一覧ページ const ExplorePage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} /> ))} </ul> </main> ); // 投稿した記事一覧ページ const MyEntriesPage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} /> ))} </ul> </main> );上記のコードは、
再利用性
の時と同じソースコードです。現状では<EntryCard />
は表示のみをしており、その他の事はしていません。
それを Author 情報の表示非表示を出来るように修正したいと思います。
理由は、このコードの<MyEntriesPage />
は自分が投稿した記事しか表示されない為、Author 情報を表示する必要が無いためです。( Author 情報が全て自分( ページを見ている人 )になるため )
<EntryCard />
を以下のように修正します。EntryCardを修正const EntryCard = ({ entry, showAuthor }) => ( <li className="entry-card"> <div className="thumbnail"> <img src={entry.thumbnail} /> </div> <h3>{entry.title}</h3> {showAuthor ? ( <div className="author"> <p>{entry.author.name}</p> </div> ) : null} </li> );showAuthor という Props によって、author 情報の表示を切り替えられるようにしました。
この修正によって、<EntryCard />
を使っているコンポーネントは以下のように修正します。ExplorePageとMyEntriesPageを修正// 記事一覧ページ const ExplorePage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} showAuthor={true} /> // Author情報を表示 ))} </ul> </main> ); // 投稿した記事一覧ページ const MyEntriesPage = ({ entries }) => ( <main> <ul> {entries.map((entry) => ( <EntryCard entry={entry} showAuthor={false} /> // Author情報を非表示 ))} </ul> </main> );上記の修正により、author 情報は必ず表示していた所を
showAuthor
の値によって表示非表示を出来るように<EntryCard />
を修正しました。これにより
<EntryCard />
の出来ることが増え、より対応できる場面が増えたと分かります。再利用性と拡張性は相反する
さて、
再利用性
と拡張性
を説明してきましたが、ここで一つ問題が発生しました。
それはExplorePageとMyEntriesPageを修正
のソースコードの以下の二つ部分です。ExplorePageコンポーネントより抜粋( /* ... */ <EntryCard entry={entry} showAuthor={true} /> // Author情報を表示 /* ... */ )MyEntriesPageコンポーネントより抜粋( /* ... */ <EntryCard entry={entry} showAuthor={false} /> // Author情報を非表示 /* ... */ )上記の二つのコードは
<EntryCard />
の拡張性
を上げるためにshowAuthor
を Props に追加したため、<EntryCard />
にshowAuthor
の値を渡しています。これは特段問題が無いように見えますが、再利用性
という観点から見ると問題が発生しています。
以下のソースコードを見てください。showAuthorにtrueを渡す例( /* ... */ <EntryCard entry={entry} showAuthor={true} /> /* ... */ ) ( /* ... */ <EntryCard entry={entry} showAuthor /> /* ... */ )showAuthorにfalseを渡す例( /* ... */ <EntryCard entry={entry} showAuthor={false} /> /* ... */ ) ( /* ... */ <EntryCard entry={entry} /> // showAuthorはundefinedとなるが、undefinedはfalseとして扱われるため同じ挙動を示す /* ... */ )上記の書き方でも、
<EntryCard />
は同じ挙動をします。
再利用性
とは「 同じ方法で同じ挙動( 表示 )を扱える 」という事でした。
しかしどうでしょう?上記のコードは、 同じ方法でしょうか?
showAuthor
に boolean を渡すのに、true
・false
それぞれ二通りずつあります。これは明らかに、再利用性
が損なわれていることが分かります。つまり、
拡張性
を上げた結果、再利用性
が失われています!これが
再利用性
・拡張性
が相反すると言った所以です。
また、これらはboolean型だけの話ではなく、Propsの数が多くなったり、別のデータ型になったとしても同じです。再利用性と拡張性にどう向き合うか?
再利用性
と拡張性
はコンポーネント作るための意義であり、双方とても大事な要素です。
しかし双方が相反しているため両者を高く保つことは困難で、状況に応じてどちらかを高く保つ必要があるかという事にコンポーネント設計者は頭を悩ませています。私はこの問題について再利用性と拡張性を維持することを諦め明確な基準を設けることで、
再利用性
と拡張性
を最低限保持することで対応しています。そして、この「 明確な基準を設ける 」というのがこの記事で紹介している ESLint プラグインやSCDを作った理由です。
長々と話してきましたが、結局のところ
再利用性
と拡張性
を高く保つ事は難しく、考えた末の結果は最低限の再利用性
と拡張性
は保持するという所に落ち着きました。これが良いか悪いかで言えば、それはたぶん人それぞれでしょう。あなたは eslint-plugin-scd を使うべきか?
もしあなたがコンポーネント設計を複数人で行う場合は、この記事で紹介している ESLint プラグインのようなコンポーネント設計を強制するESLint プラグインを使うべきであると思います。
なぜなら、人によってコンポーネント設計の基準は違っており、それが色々な人為的ミスにつながるからです。
もしそうでないなら、この記事で紹介している ESLint プラグインを使っても使わなくても大丈夫でしょう。つまり、このプラグインを使うべき人は以下のような人たちだと思います。
- コンポーネント設計に自信が無い人
- コンポーネント設計に労力をかけたくない人
- 複数人でコンポーネント設計を行う必要がある人
あとがき
ここまでコンポーネント設計について語ってきましたが、正直説明するのが難しく、私の考えている事の一割も説明できてないように思えて、自分の説明力の無さに落胆していますが、あくまでESLint プラグインの紹介記事なので、プラグインの事だけでも知ってもらえたら幸いです。
コンポーネント設計の闇は、State とかキャッシュが入ってきた途端に一気に深くなりますよね ?
私はフロントエンドで一番難しいのがコンポーネント設計だと思ってますが、皆さんはどうでしょうか?そこまで難しくないでしょうか?
ここら辺の認識は、使うフレームワークなどによって結構変わってくるかもしれませんね。Anguler だと悩まないかも ?またフロントエンドはデザインのところばかり注目されて、こういった設計があるって事を外部の人から理解してもらえない事が多々あるので、この記事で少しでもコンポーネント設計の難しさについて理解してもらえたらいいなと思っています。
最後に、今回作ったESLint プラグインですが、もしかしたら既存のプラグインで同じような事は出来るかもしれません。
ただ今回は調べるのが面倒くさかったのと、前々から ESLint プラグイン作ってみたかったので作ってみました。
色々と学べたのでいい機会だったと思います ?もしSCDのルールが気に入らない人は
.eslintrc.js
などの設定ファイルで、ルールをカスタマイズして使うこともできますので、是非使ってみて下さい。それでは ?
拡張性という言葉はあまり良くないと思ってますが、他にいい言葉が思い浮かばなかったので拡張性としています。 ↩
- 投稿日:2020-06-29T23:50:47+09:00
【 javascript】分割
javascriptには構造化の概念がサポートされているがその逆の分割のための構文はなかった。
オブジェクトの分割
まずは簡単な分割の例を見てみる。
let person = {//データ構造を作成 name: 'Tanaka' } let {name} = person;//データ構造を分割 console.log(name);//Tanakaまず、オブジェクトリテラルを使ってデータ構造を作成している。これによりプロパティを簡単な方法で指定できる。
sonogo
データ構造の分割をしている。nameプロパティを変数に取り出すための分割分が使用されている。let person = {//データ構造を作成 name: 'Tanaka', age:25 } let {name,age} = person;//データ構造を分割 console.log(name);//Tanaka 25上記では、オブジェクトwから抽出したいフィールドの名前を指定するとフィールドと同じマナエの変数にフィールド値を代入される。
ES5で同じことをするとこうなる。let name=person.name; let age=person.age;別名を使用したい場合にはこうする。
let {name:firstName,age:yearOld}=person;これによりfirstNameとyearOldにperson.name、person.ageの値が割り当てられる。
データ構造の構築時とまさに逆であるところがミソ。データ構造の異なるレベルのデータの分割
の場合は入れ子に(ネスト)する。
let geoLocation = { "location": { "lat": 51.0, "lng": -0.1 }, "accuracy": 1200.4 } let { location: { lat, lng },accuracy } = geoLocation; console.log(lat)//51 console.log(lng)//-0.1 console.log(accuracy)//1299.4 console.log(location)//undefinedlocation変数は作成されないことに注意。
このプロパティは調べただけ。配列の分割
配列もオブジェクトと同様に逆の構文。
let coords=[51.0,-0.1]; let [lat,lng,]=coords; console.log(lat);//51.0 console.log(lng);//-0.1オブジェクトはそれらの値をキー(名前)で追跡するため、それらの値は同じキー/名前で抽出しなければならない。
配列は値のインデックスで追跡する。配列を分割するときは変数に割り当てる変数が何を分割するのかを指定する。入れ子で分割
配列でも入れ子の分割ができる。
let location = ['str', [1234, 5678]]; let [moji, [suji, suji2]] = location; console.log(moji) //str console.log(suji) //1234 console.log(suji2) //5678使う場面
使う場面として値の入れ替えがある。
if (stop < start) { //値を入れ替える let tmp = start; start = stop; stop = tmp; }上の例だと入れ替えるのに一時的な変数を用意しているが、分割を使えばもっと単純になる。
if (stop < start) { //値を入れ替える [start, stop] = [stop, start]; }オブジェクトと配列の分割の組み合わせ
let res = { num: [51.0, -0.1] }; let { num: [ar, ar2] } = res; console.log(ar, ar2);上記ではオブジェクトと配列の分割を組み合わせている。これも入れ子の一種。
ここでもnumという変数は作成していない。作成したのはarとar2の2つである。
分割可能なデータ
オブジェクトの分割は配列を含め、任意のオブジェクトで使用できるが数字は変数名として使えないので、分割代入では別名にする。
const { 0: a, 1: b, length } = ['foo', 'bar'] console.log(a)//foo console.log(b)//bar console.log(length)//2配列の分割には以下のような用途もできる。
const [one, two, three, ] = 'abc'; console.log(one)//a console.log(two)//b console.log(three)//citerableプロトコルを実装しているオブジェクトはすべて配列の分割で分割できる。
まとめ
- 分割はデータ構造から値を出すためのシュガーシンタックス
- 分割はオブジェクトリテラルと配列リテラルを高文化する問の逆の構文でできる。
- オブジェクトの分割ではプロパティ名を使って値を指定する。
- 配列の分割は、インデックスを使って指定。
- 分割は入れ子も取り出せる
- 投稿日:2020-06-29T23:37:04+09:00
ESLintのカスタムルールでin+配列を禁止しよう
なぜin+配列を禁止するのか?
Pythonで
Python>>> 0 in [1,2] False >>> 1 in [1,2] Trueとなり、JavaScriptで
JavaScript> 0 in [1,2] true > 1 in [1,2] trueとなるので、間違いやすい。
このルールの正しいコードの例は
JavaScript> [1,2].includes(0) false > [1,2].includes(1) trueとなる。
ESLintにin+配列を禁止するルールがない。
ESLintのカスタムルールを作ってin+配列を禁止しよう。
禁止する方法
.eslintrc.js の rules に "no-in-array": "error" を追加。
rules フォルダーに no-in-array.js を作成。
動作確認
test.jsconst k = [1, 2]; console.log(1 in [3, 4]); // エラー for (const i in [5, 6]) // エラーなし for (const j in [7, 8]) // エラーなし console.log(i in k, j); // エラー> eslint --rulesdir rules test.js 4:13 error in + array is a troublemaker no-in-array 8:21 error in + array is a troublemaker(variable init) no-in-array--fix にも対応した。
> eslint --rulesdir rules test.js --fixtest.jsconst k = [1, 2]; console.log([3, 4].includes(1)); // エラーなし for (const i in [5, 6]) // エラーなし for (const j in [7, 8]) // エラーなし console.log(k.includes(i), j); // エラーなし
for (const i in [5, 6])
が文字列型で"0", "1"を返し[1, 2].includes("1")
がfalseになることも間違いやすい。参考にしたサイト
ESLint のカスタムルールを作ろう! (その1) - Qiita
書いて覚える ESLint ルールの作り方:キマリは通さない - Qiita
Working with Rules - ESLint - Pluggable JavaScript linterおわりに
ESLintに追加されたら嬉しい。
- 投稿日:2020-06-29T23:30:44+09:00
スクロール位置に応じてバーが追従するグローバルメニュー
はじめに
コーディングを行う際、ヘッダーのメニューにある下線をスクロール位置ごとに動かしたかったのですが、躓いてしまったため残したいと思います。
私はコーディング歴も浅く、手探りで実装したので、もしより良い方法があればご教授いただけると幸いです。完成形
コード
下線が追従する基本的な形はこちらを参考にしました。
sample.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sample</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css"> <link rel="stylesheet" href="css/style.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="js/script.js"></script> </head> <body> <header class="header"> <nav id="navigation"> <ul> <li class="current"> <a href="#">content</a> </li> <li class="menu-2"> <a href="#">second-content</a> </li> <li class="menu-3"> <a href="#">3rd-content</a> </li> <li class="menu-4"> <a href="#">4</a> </li> </ul> <span id="slide-line"></span> </nav> </header> <div class="contents"> <div class="content" id="content1">ひとつめ</div> <div class="content" id="content2">ふたつめ</div> <div class="content" id="content3">みっつめ</div> <div class="content" id="content4">よっつめ</div> </div> </body>style.cssheader{ width: 100%; height: 30px; position: fixed; } ul li { display: inline-block; } a{ display: inline-block; padding: 30px 20px; font-size:14px; text-decoration:none; } #navigation{ position: relative; } #slide-line{ position: absolute; bottom: 0; height: 2px; background-color:#75C2FF; -webkit-transition: all .3s ease; transition: all .3s ease; } .content{ height: 700px; padding: 80px; } .content:nth-child(odd){ background-color: #f5f5f5; }script.js$(window).load(function(){ $('nav span').css({ width: $('nav .current').width(), left: $('nav .current').position().left }); $(window).scroll(function () { let content2 = $('#content2').offset().top; let content3 = $('#content3').offset().top; let content4 = $('#content4').offset().top; if ($(this).scrollTop() < content2) { if ( ! $('header').hasClass("current1") ) { $('header').addClass("current1"); $('header').removeClass("current2 current3 current4"); $('nav span').animate({ width: $('nav .current').width(), left: $('nav .current').position().left }); } }else if ($(this).scrollTop() > content2 && $(this).scrollTop() < content3) { if ( ! $('header').hasClass("current2") ) { $('header').addClass("current2"); $('header').removeClass("current1 current3 current4"); $('nav span').animate({ width: $('.menu-2').width(), left: $('.menu-2').position().left }); } }else if ($(this).scrollTop() > content3 && $(this).scrollTop() < content4) { if ( ! $('header').hasClass("current3") ) { $('header').addClass("current3"); $('header').removeClass("current1 current2 current4"); $('nav span').animate({ width: $('.menu-3').width(), left: $('.menu-3').position().left }); } }else if ($(this).scrollTop() > content4) { if ( ! $('header').hasClass("current4") ) { $('header').addClass("current4"); $('header').removeClass("current1 current2 current3"); $('nav span').animate({ width: $('.menu-4').width(), left: $('.menu-4').position().left }); } } }); });仕組み
まずメニューバーが動く仕組みについてですが、animateメソッドを使って横幅と位置を指定することで動きをつけています。
こちらの詳細は参考サイトにございますのでご覧ください。
let content2 = $('#content2').offset().top;
let content3 = $('#content3').offset().top;
let content4 = $('#content4').offset().top;
次にスクロールによる、スクロールをした時の区切りとなる要素の位置を上記で定義します。
以下のif文に関しての説明です。
現在のスクロール位置が特定の区切りの中(例えばcontent2の中)にいるか、かつheaderが特定のクラス(前の例えだとcontent2というクラス)を持っていないとメニューバーが動くという仕組みです。悩んだこと
今回はheaderのクラスのつけ外しを行うことでうまく動かしているのですが、これをする前はうまくいっていませんでした。
console.logなどで確かめてみるとわかるのですが、scrollアクションは少しのスクロールで何回も発火するため、その度にanimateメソッドが読み込まれ非常に処理が重くなってしまいました。
そこで私は、animateメソッドが読み込まれるのをスクロール位置が切り替わった時のみにしたいと考え、クラスの有無を条件式としてあげることで重くなることを防ぐことができました。おわりに
最後までご覧いただきありがとうございます。
この方法は初心者の私が考えたやり方のため、無理のある記述の可能性があります。
ぜひ有識の方がいらしたらご意見くださると幸いです。
- 投稿日:2020-06-29T23:20:39+09:00
【Javascript】JS 変数 定数 違い 一言でまとめました
【ゴール】
javascriptの変数、定数の違い
【メリット】
* javascript理解度向上
【コード】
変数(var , let)
*中身を何回も更新できる
var
*再宣言可能
*再宣言の際に(var)は不要hoge.jsvar name = "太郎"; console.log(name); // 太郎が出力 name = "次郎"; console.log(name); //次郎が出力 var name = "三郎"; //再宣言可能let
*同変数名は一度しか宣言できない
hoge.jslet name = "太郎"; console.log(name); //太郎が出力 let name ="次郎"; //エラー出る...定数
*中身は一回だけ決めれる
const
hoge.jsconst name = "太郎"; console.log(name); //太郎と出力【合わせて読みたい】
■JavaScriptで書く「let,var,const」の違い・使い分け
https://techacademy.jp/magazine/14872■ 【JavaScript】 js 繰り返し文 for / while
https://qiita.com/tanaka-yu3/items/942d9d4838ebe14be1c2■ 【jQuery】初心者でもよく理解できたやつ
https://qiita.com/tanaka-yu3/items/a03734b248c3f2ee8461
- 投稿日:2020-06-29T23:11:23+09:00
kintone から LINE に投稿する
概要
先日に LINE チャンネルの友達登録や投稿情報を kintone に保管 で、LINEのチャンネル登録をして、友達登録や投稿情報を kintone に保管したり、AWS Lambda から返信できることまでは確認しました。
今回は、kintone から LINE チャンネルを友達登録した方に対して個別でメッセージを送る方法を試してみました。
kintone アプリの項目追加
先ずは、前回作成した LINE の友達登録や投稿情報を保管する kintone アプリに、LINE に送信するメッセージ用のフィールドを追加します。
フィールド名 タイプ フィードコート・要素ID lineユーザID 文字列(1行) lineUserId 日時 日時 lineDateTime lineタイプ 文字列(1行) lineType lineモード 文字列(1行) lineMode lineメッセージ 文字列(複数行) lineMessage (追加)lineに送るメッセージ 文字列(複数行) sendMessage (追加) スペース sendSubmit スペースは「LINEにメッセージ送信」ボタンを表示するために追加しています。
kintone の JavaScript プログラム作成
kintone の 詳細画面を表示した時に「LINEにメッセージ送信」ボタンを表示し、クリックすると同時に LINE に、レコードのLINEユーザID宛てに「lineに送るメッセージ」フィールドの値を送る JavaScript プログラムを作成します。
LINE のユーザ宛てにメッセージを送るためには、Messaging API の https://api.line.me/v2/bot/message/push で以下のヘッダーで、JSON データを POST します。
・ヘッダー
let headers = { 'Authorization': ' Bearer ' + '{' + token + '}', 'Content-Type' : 'application/json' };token の値は、 LINE チャンネルの友達登録や投稿情報を kintone に保管 で説明したチャンネルアクセストークンです。
・POST データ
let json = { "to":userId, "messages":[ { "type":"text", "text":message } ] };message にLINEユーザに連絡するテキスト文書をセットしておきます。
・CORS によるエラーに注意
kinton の JavaScript で外部APIを利用する場合は、XMLHttpRequest() は使えません。ついつい私も最初 XMLHttpRequest() で実装しようとして後になって気づくのですが、 kintone.proxy() を利用します。うっかり XMLHttpRequest() で実装しようものなら、エラーでデバッグ画面で以下を確認することになります。また、うっかりやってしました、、、(汗)
Access to XMLHttpRequest at 'https://api.line.me/v2/bot/message/push' from origin 'https://yukataoka.cybozu.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.・今回実装したコード例
今回は以下のように実装して、kintone から LINE にメッセージが送れるようになりました。
kintone.proxy() は Promise でなくて良かったのですが、同期処理になっています。(汗)sendLineMessage.js(function() { "use strict"; const Protocol = "https://"; const LineHost = "api.line.me"; const LinePath = "/v2/bot/message/push"; const LineToken = "LINEのチャンネルアクセストークン"; // レコード詳細表示時イベント var eventsDetailShow = [ 'app.record.detail.show', 'mobile.app.record.detail.show']; kintone.events.on(eventsDetailShow, function(event) { let sendButton = makeSendButton(LineToken, event.record.lineUserId.value, event.record.sendMessage.value); if(event.type !== 'mobile.app.record.detail.show') { kintone.app.record.getSpaceElement('sendSubmit').appendChild(sendButton); }else{ kintone.mobile.app.record.getSpaceElement('sendSubmit').appendChild(sendButton); } }); // LINEにメッセージ送信ボタン作成 function makeSendButton(token, userId, message) { let submitButton = document.createElement('div'); let sendButton = document.createElement('button'); sendButton.innerText = ' LINEにメッセージ送信 '; sendButton.className = "gaia-ui-actionmenu-save"; sendButton.onclick = function() { let json = { "to":userId, "messages":[ { "type":"text", "text":message } ] }; let url = Protocol + LineHost + LinePath; let res = funcPostLine(url, token, json); }; submitButton.appendChild(sendButton); return submitButton; } // line にメッセージを送信 function funcPostLine(url, token, json) { let headers = { 'Authorization': ' Bearer ' + '{' + token + '}', 'Content-Type' : 'application/json' }; kintone.proxy(url, 'POST', headers, json).then(function(args) { if (args[1] === 200) { alert("LINEにメッセージを送信しました!"); return true; }else{ alert("LINEのメッセージ送信にエラーがありました!\n" + args[0]); return false; } }, function(error) { alert("LINEのメッセージ送信にエラーがありました!\n" + error); return false; }); } })();実行結果
kintone の詳細画面で「LINEにメッセージ送信」ボタンを押すと、即座に「LINEにメッセージを送信しました!」の alert() が表示され、LINEへのメッセージ送信を確認できました。
まとめ
先日の LINE チャンネルの友達登録や投稿情報を kintone に保管 から、今回の kintoe から LINE に投稿するまでで、以下を確認できました。
・LINE チャンネルを開設する
・LINE チャンネルの友達登録や投稿情報を kintone に保管
・LINE チャンネルの投稿に返信を送信
・kintone から LINE にメッセージを送信特に kintone との連携の例が少なく相当な苦戦を予想していましたが、実際はわりと簡単に実装できました。実装のためのコストは以外とかからないと推測します。
ポイントとしては、チャンネルの友達登録時に LINE ユーザID を取得し、kintone で管理するアプリ(例えば顧客管理アプリ)のどう紐づけるかが鍵になりそうです。チャンネルの友達登録時や、その後でも kintone のアプリの情報と紐ずくしくみをどのように実装するかが、kintone と LINE を連携して活用するための鍵になりそうです。
参考情報
line 送信設定
https://developers.line.biz/ja/reference/messaging-api/#send-push-message
外部APIの実行
https://developer.cybozu.io/hc/ja/articles/202166320
- 投稿日:2020-06-29T22:59:10+09:00
Kinx ライブラリ - JIT ライブラリ
Kinx ライブラリ - JIT ライブラリ
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。JIT コンパイルのためのライブラリを作ってみました。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
JIT やりたいよね。今回は Kinx - native でも使っている SLJIT を使いやすくしようということで、ライブラリ化しました。SLJIT 自体、ドキュメントが少なくてソースから解読して使っているので、SLJIT そのものの使い方を備忘録的に書こうかとも思ったのだけど、今回は保留。どこかでやるかもしれない。
しかし、SLJIT をそのまま使うよりも勿論使いやすくなっているので、こっちのほうが良いと思う。ホスト言語もスクリプト なので、手軽に楽しめるでしょう。
どんな感じ?
先にどんな感じのプログラムになるか、サンプルを出しておきます。色々細かい話が続いてここまでたどり着いてくれなさそうなので。。。
using Jit; var c = new Jit.Compiler(); var entry1 = c.enter(); var jump0 = c.ge(Jit.S0, Jit.IMM(3)); c.ret(Jit.S0); var l1 = c.label(); c.sub(Jit.R0, Jit.S0, Jit.IMM(2)); c.call(entry1); c.mov(Jit.S1, Jit.R0); c.sub(Jit.R0, Jit.S0, Jit.IMM(1)); c.call(entry1); c.add(Jit.R0, Jit.R0, Jit.S1); c.ret(Jit.R0); jump0.setLabel(l1); var code = c.generate(); for (var i = 1; i <= 42; ++i) { var tmr = new SystemTimer(); var r = code.run(i); System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r); }
Jit.Compiler
オブジェクトを作って、enter
で関数エントリを作り、色々レジスタをいじくりまわしてret
するコードを書きます。で、実行するときはgenerate()
してrun()
、となります。generate()
してdump()
とすると、アセンブル・リストを見ることもできます。色々とばしたい方は サンプル へ Go! → サンプルでは Ruby、Python、PyPy とのベンチマークもしてますよ。
SLJIT
そもそも SLJIT とは何か。
一言でいえば 抽象化アセンブラ で、一つの書き方で複数の環境をサポートできてしまうという、CPU ごとに異なるので作り直さなければならないというアセンブラの問題を解決してくれるライブラリです。現状サポートしているというプラットフォームは以下の通り。
- SLJIT のサポート・プラットフォーム
- Intel-x86 32
- AMD-x86 64
- ARM 32 (ARM-v5, ARM-v7 and Thumb2 instruction sets)
- ARM 64
- PowerPC 32
- PowerPC 64
- MIPS 32 (III, R1)
- MIPS 64 (III, R1)
- SPARC 32
ただし、ここで紹介する Kinx 版 JIT ライブラリは 64bit しかサポートしていないこと、および x64 Windows と x64 Linux しか確認して(できて)いませんので、そこは悪しからずご了承ください。
公式? 説明文書
私が知る限り、参考になる文書は以下程度しか見つかりませんでした。
- https://zherczeg.github.io/sljit/
- http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-current/src/sys/external/bsd/sljit/dist/doc/tutorial/sljit_tutorial.html
参考にはなります。
GitHub のリポジトリは以下です。
Jit
さて、Kinx ライブラリとしての JIT ライブラリ。C のまま使うより便利にはしてます。もちろん C のライブラリを使えばもっとより細かく制御できると思いますが、それなりのことはできます。
using Jit
Jit ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。
using Jit;Jit オブジェクト
Jit オブジェクトはパラメータ用のメソッドとコンパイラ・クラスが定義されている。
Jit パラメータ用メソッド
Jit のパラメータとして、即値、レジスタ、メモリアクセスの 3 種類がある。以下の形で利用する。
即値、メモリアクセス
即値、メモリアクセスは以下のメソッドで利用する。
Jit.VAR()
はローカル変数領域を使うための特別なメソッド。スタック領域にローカル変数領域が自動的に確保され、その領域を使う。
メソッド 備考 Jit.IMM(v)
64bit 整数、浮動小数点数どちらも同じ書き方。代入先のレジスタと合わせる。 Jit.VAR(n)
ローカル変数領域。1 変数 8 バイト固定。 Jit.MEM0(address)
address として即値を代入、ただし現在実アドレスをスクリプトから指定できないので、スクリプトからは使えない。 Jit.MEM1(r1, offset)
r1 に指定したレジスタをアドレスとみて、offset 位置(バイト単位)のメモリアドレスを示す。 Jit.MEM2(r1, r2, shift)
shift は 0 なら 1 バイト、1 なら 2 バイト、 2 なら 4 バイト、3 なら 8 バイトを示し、 r1 + r2 * (shiftで示すバイト分)
の位置のメモリアドレスを示す。レジスタ
以下のレジスタを使用可能。関数内で使えるレジスタの数は自動的に計算され、関数(
enter()
で区切った範囲)ごとに変わる。
レジスタ 用途 Jit.R0
~Jit.R5
汎用レジスタ。一時的に利用。別関数呼び出し後に破棄される可能性あり。 Jit.S0
~Jit.S5
汎用レジスタ。別関数呼び出し後に破棄されない保証。 Jit.FR0
~Jit.FR5
浮動小数点レジスタ。一時的に利用。別関数呼び出し後に破棄される可能性あり。 Jit.FS0
~Jit.FS5
浮動小数点レジスタ。別関数呼び出し後に破棄されない保証。 尚、Floating Point 用のレジスタは
FR
/FS
合わせて最大で 6 個までなので、FR4
まで使用した場合、FS0
しか使えません。FR5
まで使うとFS*
は全て使えません。以下のような感じですのでご注意を。
FR*
レジスタFS*
レジスタ(使えない) FS0
,FS1
,FS2
,FS3
,FS4
,FS5
FR0
FS0
,FS1
,FS2
,FS3
,FS4
FR0
,FR1
FS0
,FS1
,FS2
,FS3
FR0
,FR1
,FR2
FS0
,FS1
,FS2
FR0
,FR1
,FR2
,FR3
FS0
,FS1
FR0
,FR1
,FR2
,FR3
,FR4
FS0
FR0
,FR1
,FR2
,FR3
,FR4
,FR5
(使えない) Jit コンパイラ
Jit 命令を作っていくためには Jit コンパイラ・オブジェクトを作成する。
var c = new Jit.Compiler();Jit コンパイラには以下のメソッドがある。
Jit コンパイラ・メソッド 復帰値 概要 Jit.Compiler#label()
label 現在の場所にラベルを付加する。 Jit.Compiler#makeConst(reg, init)
ConstTarget コード生成後に即値を設定するための仮定義コードを出力する。 Jit.Compiler#localp(dst, offset)
ローカル変数の実アドレスを取得するコードを出力する。 dst
に示したレジスタに格納される。offset はローカル変数番号。Jit.Compiler#enter(argType)
label 関数の入り口を作成。引数タイプを指定できる(省略可)。 Jit.Compiler#fastEnter(reg)
label 関数の入り口を作成。ただし、余計なエピローグ、プロローグは出力せず、復帰アドレスを reg
に保存する。Jit.Compiler#ret(val)
Return コードを出力する。 val
を返す。val
は浮動小数点数はFR0
レジスタ、それ以外はR0
レジスタで返却される。Jit.Compiler#f2i(dst, op1)
double を int64_t にキャストするコードを出力する。 dst
は汎用レジスタ。op1
は浮動小数点レジスタ。Jit.Compiler#i2f(dst, op1)
int64_t を double にキャストするコードを出力する。 dst
は浮動小数点レジスタ。op1
は汎用レジスタ。Jit.Compiler#mov(dst, op1)
dst
にop1
を代入するコードを出力する。浮動小数点とそれ以外の型は自動的に認識する。Jit.Compiler#neg(dst, op1)
op1
の符号反転した結果をdst
に格納するコードを出力する。Jit.Compiler#clz(dst, op1)
op1
の先頭から 0 であるビットの数を数え、dst
に格納するコードを出力する。Jit.Compiler#add(dst, op1, op2)
op1
とop2
を加算した結果をdst
に格納するコードを出力する。Jit.Compiler#sub(dst, op1, op2)
op1
とop2
を減算した結果をdst
に格納するコードを出力する。Jit.Compiler#mul(dst, op1, op2)
op1
とop2
を乗算した結果をdst
に格納するコードを出力する。Jit.Compiler#div(dst, op1, op2)
浮動小数点数のみ、 op1
とop2
を除算した結果をdst
に格納するコードを出力する。Jit.Compiler#div()
汎用レジスタで符号なしとして除算した値を R0
レジスタに格納するコードを出力する。Jit.Compiler#sdiv()
汎用レジスタで符号付きとして除算した値を R0
レジスタに格納するコードを出力する。Jit.Compiler#divmod()
汎用レジスタで符号なしとして除算した値を R0
レジスタに格納し、余りをR1
レジスタに格納するコードを出力する。Jit.Compiler#sdivmod()
汎用レジスタで符号付きとして除算した値を R0
レジスタに格納し、余りをR1
レジスタに格納するコードを出力する。Jit.Compiler#not(dst, op1)
op1
のビット反転した結果をdst
に格納するコードを出力する。Jit.Compiler#and(dst, op1, op2)
op1
とop2
でビット AND した値をdst
に格納するコードを出力する。Jit.Compiler#or(dst, op1, op2)
op1
とop2
でビット OR した値をdst
に格納するコードを出力する。Jit.Compiler#xor(dst, op1, op2)
op1
とop2
でビット XOR した値をdst
に格納するコードを出力する。Jit.Compiler#shl(dst, op1, op2)
op1
をop2
ビット分、左シフトした値をdst
に格納するコードを出力する。Jit.Compiler#lshr(dst, op1, op2)
op1
をop2
ビット分、論理右シフトした値をdst
に格納するコードを出力する。Jit.Compiler#ashr(dst, op1, op2)
op1
をop2
ビット分、算術右シフトした値をdst
に格納するコードを出力する。Jit.Compiler#call(label)
JumpTarget enter()
定義した関数呼び出しを行うコードを出力する。後で呼び出し先を設定する JumpTarget を返す。label
を指定した場合は後から設定する必要はない。Jit.Compiler#fastCall(label)
JumpTarget fastEnter()
で定義した関数呼び出しを行うコードを出力する。後で呼び出し先を設定する JumpTarget を返す。Jit.Compiler#jmp(label)
JumpTarget jmp
コマンドを出力する。label
を指定した場合は後から設定する必要はない。Jit.Compiler#ijmp(dst)
JumpTarget jmp
コマンドを出力する。dst
はアドレスを示すレジスタ、または即値。Jit.Compiler#eq(op1, op2)
JumpTarget op1 == op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#neq(op1, op2)
JumpTarget op1 != op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#lt(op1, op2)
JumpTarget 符号なしとして op1 < op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#le(op1, op2)
JumpTarget 符号なしとして op1 <= op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#gt(op1, op2)
JumpTarget 符号なしとして op1 > op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#ge(op1, op2)
JumpTarget 符号なしとして op1 >= op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#slt(op1, op2)
JumpTarget 符号付きとして op1 < op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#sle(op1, op2)
JumpTarget 符号付きとして op1 <= op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#sgt(op1, op2)
JumpTarget 符号付きとして op1 > op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#sge(op1, op2)
JumpTarget 符号付きとして op1 >= op2
を確認するコードを出力。条件が真となった場合のジャンプ先を指定する JumpTarget を返す。Jit.Compiler#generate()
JitCode コード生成を行う。 Jit.Compiler#enter(argType)
関数の入り口を
enter
メソッドで定義するが、argType
を指定しなかった場合はJit.ArgType.SW_SW_SW
を指定されたものとみなす。引数は 3 つまで(仕様)で、それぞれの型を指定する。
SW
... Signed Word (64bit)UW
... Unsigned Word (64bit)FP
... Floating Point (64bit)実際問題としては受け取るレジスタのビット列が同じになるため
SW
とUW
は変わらないが、もしかしたら将来何か違いを出すかもしれない。尚、最後の引数からSW
は省略可能。なので、以下は全部同じ意味となる。
Jit.ArgType.SW_SW_SW
Jit.ArgType.SW_SW
Jit.ArgType.SW
引数で渡されるレジスタは決まっており、以下のようになっている。
- 呼び出し側
型 第 1 引数 第 2 引数 第 3 引数 Integer Jit.R0
Jit.R1
Jit.R2
Double Jit.FR0
Jit.FR1
Jit.FR2
- 受け取り側
型 第 1 引数 第 2 引数 第 3 引数 Integer Jit.S0
Jit.S1
Jit.S2
Double Jit.FS0
Jit.FS1
Jit.FS2
呼び出し側でセットするレジスタと、受け取り側で受け取るレジスタが異なることに注意。
ConstTarget
ラベルアドレスを
setLabel()
で設定する。
ラベルのアドレスを即値としてレジスタやメモリに格納したいときに使う。あまり使う機会は無いか。ジャンプテーブルの代わりになるかとも思うが、テーブルの作り方がイマイチうまい仕組みを用意できていないので。ちなみに、即値を
setValue()
で設定することもできるが、普通にJit.IMM(100)
とか、浮動小数点数でもJit.IMM(0.1)
とかできるようにしたので、こちらを使う意味はあまりない。仮にジャンプテーブルに使うとした際の例は後述。
JumpTarget
ジャンプ先、もしくは関数コールのためのアドレスを
setLabel()
で設定する。例えば、比較した結果で分岐させる場合、以下のようになる。
var c = new Jit.Compiler(); // 関数のエントリポイント。 c.enter(); // S0レジスタ値 >= 3 var jump0 = c.ge(Jit.S0, Jit.IMM(3)); ... // 条件が偽のときのコード var jump1 = c.jmp(); var label0 = c.label(); ... // 条件が真のときのコード var label1 = c.label(); ... jump0.setLabel(label0); jump1.setLabel(label1);JitCode
generate()
メソッドでコード生成に成功すると、JitCode オブジェクト返る。JitCode オブジェクトのメソッドは以下の通り。尚、引数は 3 つまでしか指定できないことに注意(仕様)。抽象化アセンブラなので、様々なアーキテクチャに対応するために必要な仕様です。必要ならローカル変数領域を確保して、その先頭アドレスを渡すなどの工夫が必要。サンプルは後述。
メソッド 概要 JitCode#run(a1, a2, a3)
復帰値を Integer として受け取る。 JitCode#frun(a1, a2, a3)
復帰値を Double として受け取る。 JitCode#dump()
ジェネレートされたアセンブル・リストを出力する。 サンプル
フィボナッチ数列(再帰版)
では、恒例のフィボナッチ数列を算出する再帰版のコードを書いてみましょう。サンプルとして最初に提示したものそのままです。
var c = new Jit.Compiler(); var entry1 = c.enter(); var jump0 = c.ge(Jit.S0, Jit.IMM(3)); c.ret(Jit.S0); var l1 = c.label(); c.sub(Jit.R0, Jit.S0, Jit.IMM(2)); c.call(entry1); c.mov(Jit.S1, Jit.R0); c.sub(Jit.R0, Jit.S0, Jit.IMM(1)); c.call(entry1); c.add(Jit.R0, Jit.R0, Jit.S1); c.ret(Jit.R0); jump0.setLabel(l1); var code = c.generate(); for (var i = 1; i <= 42; ++i) { var tmr = new SystemTimer(); var r = code.run(i); System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r); }結果は以下の通り。
[ 0.000] fib( 1) = 1 [ 0.000] fib( 2) = 2 [ 0.000] fib( 3) = 3 [ 0.000] fib( 4) = 5 [ 0.000] fib( 5) = 8 [ 0.000] fib( 6) = 13 [ 0.000] fib( 7) = 21 [ 0.000] fib( 8) = 34 [ 0.000] fib( 9) = 55 [ 0.000] fib(10) = 89 [ 0.000] fib(11) = 144 [ 0.000] fib(12) = 233 [ 0.000] fib(13) = 377 [ 0.000] fib(14) = 610 [ 0.000] fib(15) = 987 [ 0.000] fib(16) = 1597 [ 0.000] fib(17) = 2584 [ 0.000] fib(18) = 4181 [ 0.000] fib(19) = 6765 [ 0.000] fib(20) = 10946 [ 0.000] fib(21) = 17711 [ 0.000] fib(22) = 28657 [ 0.000] fib(23) = 46368 [ 0.000] fib(24) = 75025 [ 0.000] fib(25) = 121393 [ 0.001] fib(26) = 196418 [ 0.001] fib(27) = 317811 [ 0.001] fib(28) = 514229 [ 0.002] fib(29) = 832040 [ 0.002] fib(30) = 1346269 [ 0.004] fib(31) = 2178309 [ 0.006] fib(32) = 3524578 [ 0.009] fib(33) = 5702887 [ 0.016] fib(34) = 9227465 [ 0.035] fib(35) = 14930352 [ 0.042] fib(36) = 24157817 [ 0.066] fib(37) = 39088169 [ 0.119] fib(38) = 63245986 [ 0.181] fib(39) = 102334155 [ 0.289] fib(40) = 165580141 [ 0.476] fib(41) = 267914296 [ 0.773] fib(42) = 433494437ちなみに、
fib(42)
の結果を Ruby, Python, PyPy, PHP, HHVM, Kinx, Kinx(native) で計測して比較してみた。JIT ライブラリ版は上記はrun()
の時間しか計ってないので、スクリプト解釈と JIT コード生成まで含め、全て公平にプロセス全体の user time で算出。速い順に並べると以下の通り。やはり JIT で直接ネイティブ・コードを出力させると際立って速い。何気に Kinx(native) が PyPy より速かったのは嬉しい誤算。HHVM とどっこいくらい。スクリプトでは Ruby 速くなったなー。1.8 時代とか知ってると感慨深いですねー。
言語 版数 User 時間 Kinx(Jit-Lib) 0.10.0 0.828 HHVM 3.21.0 2.227 Kinx(native) 0.10.0 2.250 PyPy 5.10.0 3.313 PHP 7.2.24 11.422 Ruby 2.5.1p57 14.877 Kinx 0.10.0 27.478 Python 2.7.15+ 41.125 尚、先の JIT ライブラリでジェネレートされたアセンブル・リストはこちら。Windows と Linux で違うのだが、今回は Linux。
0: 53 push rbx 1: 41 57 push r15 3: 41 56 push r14 5: 48 8b df mov rbx, rdi 8: 4c 8b fe mov r15, rsi b: 4c 8b f2 mov r14, rdx e: 48 83 ec 10 sub rsp, 0x10 12: 48 83 fb 03 cmp rbx, 0x3 16: 73 0d jae 0x25 18: 48 89 d8 mov rax, rbx 1b: 48 83 c4 10 add rsp, 0x10 1f: 41 5e pop r14 21: 41 5f pop r15 23: 5b pop rbx 24: c3 ret 25: 48 8d 43 fe lea rax, [rbx-0x2] 29: 48 89 fa mov rdx, rdi 2c: 48 89 c7 mov rdi, rax 2f: e8 cc ff ff ff call 0x0 34: 49 89 c7 mov r15, rax 37: 48 8d 43 ff lea rax, [rbx-0x1] 3b: 48 89 fa mov rdx, rdi 3e: 48 89 c7 mov rdi, rax 41: e8 ba ff ff ff call 0x0 46: 49 03 c7 add rax, r15 49: 48 83 c4 10 add rsp, 0x10 4d: 41 5e pop r14 4f: 41 5f pop r15 51: 5b pop rbx 52: c3 retConst の例
Const の例として、あえて書くならこんな感じ。ローカル変数にジャンプテーブルを作っているので、毎回テーブルを作り直していてイマイチ。テーブルだけ作成してアドレスを渡せるようなインターフェースを別途用意すれば解決しそうではある(やるかも)。
var c = new Jit.Compiler(); c.enter(); c.mov(Jit.R1, Jit.IMM(-1)); var jump0 = c.slt(Jit.S0, Jit.IMM(0)); var jump1 = c.sgt(Jit.S0, Jit.IMM(3)); var const0 = c.makeConst(Jit.VAR(0)); var const1 = c.makeConst(Jit.VAR(1)); var const2 = c.makeConst(Jit.VAR(2)); var const3 = c.makeConst(Jit.VAR(3)); // ローカル変数のアドレスを S0 レジスタ(第一引数)のオフセットで取得し R0 レジスタに格納。 c.localp(Jit.R0, Jit.S0); // ローカル変数の値自体を取得。 c.mov(Jit.R0, Jit.MEM1(Jit.R0)); // ローカル変数の中身をアドレスと見立ててジャンプ。 c.ijmp(Jit.R0); var l0 = c.label(); c.mov(Jit.R1, Jit.IMM(102)); c.ret(Jit.R1); var l1 = c.label(); c.mov(Jit.R1, Jit.IMM(103)); c.ret(Jit.R1); var l2 = c.label(); c.mov(Jit.R1, Jit.IMM(104)); c.ret(Jit.R1); var l3 = c.label(); c.mov(Jit.R1, Jit.IMM(105)); var l4 = c.label(); c.ret(Jit.R1); // ジャンプアドレスはコード生成前にセットする。 jump0.setLabel(l4); jump1.setLabel(l4); var code = c.generate(); // const 値はコード生成後にセットする。 const0.setLabel(l0); const1.setLabel(l1); const2.setLabel(l2); const3.setLabel(l3); for (var i = -1; i < 5; ++i) { var r = code.run(i); System.println(r); }結果。
-1 102 103 104 105 -1コード出力はこんな感じ。こちらは試しに Windows 版で出してみた。
0: 53 push rbx 1: 56 push rsi 2: 57 push rdi 3: 48 8b d9 mov rbx, rcx 6: 48 8b f2 mov rsi, rdx 9: 49 8b f8 mov rdi, r8 c: 4c 8b 4c 24 b0 mov r9, [rsp-0x50] 11: 48 83 ec 50 sub rsp, 0x50 15: 48 c7 c2 ff ff ff ff mov rdx, 0xffffffffffffffff 1c: 48 83 fb 00 cmp rbx, 0x0 20: 0f 8c 94 00 00 00 jl 0xba 26: 48 83 fb 03 cmp rbx, 0x3 2a: 0f 8f 8a 00 00 00 jg 0xba 30: 49 b9 95 ff 57 61 89 01 00 00 mov r9, 0x1896157ff95 3a: 4c 89 4c 24 20 mov [rsp+0x20], r9 3f: 49 b9 a7 ff 57 61 89 01 00 00 mov r9, 0x1896157ffa7 49: 4c 89 4c 24 28 mov [rsp+0x28], r9 4e: 49 b9 b9 ff 57 61 89 01 00 00 mov r9, 0x1896157ffb9 58: 4c 89 4c 24 30 mov [rsp+0x30], r9 5d: 49 b9 cb ff 57 61 89 01 00 00 mov r9, 0x1896157ffcb 67: 4c 89 4c 24 38 mov [rsp+0x38], r9 6c: 48 8d 44 24 20 lea rax, [rsp+0x20] 71: 48 6b db 08 imul rbx, rbx, 0x8 75: 48 03 c3 add rax, rbx 78: 48 8b 00 mov rax, [rax] 7b: ff e0 jmp rax 7d: 48 c7 c2 66 00 00 00 mov rdx, 0x66 84: 48 89 d0 mov rax, rdx 87: 48 83 c4 50 add rsp, 0x50 8b: 5f pop rdi 8c: 5e pop rsi 8d: 5b pop rbx 8e: c3 ret 8f: 48 c7 c2 67 00 00 00 mov rdx, 0x67 96: 48 89 d0 mov rax, rdx 99: 48 83 c4 50 add rsp, 0x50 9d: 5f pop rdi 9e: 5e pop rsi 9f: 5b pop rbx a0: c3 ret a1: 48 c7 c2 68 00 00 00 mov rdx, 0x68 a8: 48 89 d0 mov rax, rdx ab: 48 83 c4 50 add rsp, 0x50 af: 5f pop rdi b0: 5e pop rsi b1: 5b pop rbx b2: c3 ret b3: 48 c7 c2 69 00 00 00 mov rdx, 0x69 ba: 48 89 d0 mov rax, rdx bd: 48 83 c4 50 add rsp, 0x50 c1: 5f pop rdi c2: 5e pop rsi c3: 5b pop rbx c4: c3 ret7b 行目の
jmp rax
がポイント。テーブルを静的に定義できるようになればジャンプテーブルとして機能するようになるかと(今は簡単にできる方法が無い...)。4 つ以上の引数の例
ちょっと面倒くさいが、4 つ以上引数を渡したい場合は、ローカル変数領域に値を格納し、そのアドレス(ポインタ)を引数として渡す。以下の例では、最初に引数をローカル変数領域にセットするためのフック関数を経由させている。ちなみに、ローカル変数は全て 8 バイトで確保されるため、直接
Jit.MEM1()
などでアクセスする場合のオフセットは 8 の倍数でないと合わないので注意。var c = new Jit.Compiler(); var entry1 = c.enter(); c.mov(Jit.VAR(0), Jit.S0); c.mov(Jit.VAR(1), Jit.IMM(3)); c.mov(Jit.VAR(2), Jit.IMM(2)); c.mov(Jit.VAR(3), Jit.IMM(1)); c.localp(Jit.R0); var call1 = c.call(); c.ret(Jit.R0); var entry2 = c.enter(); c.mov(Jit.R1, Jit.S0); c.mov(Jit.S0, Jit.MEM1(Jit.R1, 0)); var jump0 = c.ge(Jit.S0, Jit.MEM1(Jit.R1, 8)); c.ret(Jit.S0); var l1 = c.label(); c.sub(Jit.R3, Jit.S0, Jit.MEM1(Jit.R1, 16)); c.mov(Jit.VAR(0), Jit.R3); c.mov(Jit.VAR(1), Jit.IMM(3)); c.mov(Jit.VAR(2), Jit.IMM(2)); c.mov(Jit.VAR(3), Jit.IMM(1)); c.localp(Jit.R0); c.call(entry2); c.mov(Jit.S1, Jit.R0); c.sub(Jit.R3, Jit.S0, Jit.MEM1(Jit.R1, 24)); c.mov(Jit.VAR(0), Jit.R3); c.mov(Jit.VAR(1), Jit.IMM(3)); c.mov(Jit.VAR(2), Jit.IMM(2)); c.mov(Jit.VAR(3), Jit.IMM(1)); c.localp(Jit.R0); c.call(entry2); c.add(Jit.R0, Jit.R0, Jit.S1); c.ret(Jit.R0); jump0.setLabel(l1); call1.setLabel(entry2); var code = c.generate(); for (var i = 1; i <= 42; ++i) { var tmr = new SystemTimer(); var r = code.run(i); System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r); }出力はさっきと同じ。
Double の引数と復帰値
Double 紹介していないのでそれも。こちらもフィボナッチでいきましょう。しかし、俺はフィボナッチ大好きだな。気づかなかったけど。0.1 刻みバージョンです。
var c = new Jit.Compiler(); var entry1 = c.enter(Jit.ArgType.FP); c.mov(Jit.FR0, Jit.IMM(0.3)); var jump0 = c.ge(Jit.FS0, Jit.FR0); c.ret(Jit.FS0); var l1 = c.label(); c.mov(Jit.FR0, Jit.IMM(0.2)); c.sub(Jit.FR0, Jit.FS0, Jit.FR0); c.call(entry1); c.mov(Jit.FS1, Jit.FR0); c.mov(Jit.FR0, Jit.IMM(0.1)); c.sub(Jit.FR0, Jit.FS0, Jit.FR0); c.call(entry1); c.add(Jit.FR0, Jit.FR0, Jit.FS1); c.ret(Jit.FR0); jump0.setLabel(l1); var code = c.generate(); for (var i = 0.1; i < 3.5; i += 0.1) { var tmr = new SystemTimer(); var r = code.frun(i); System.println("[%8.3f] fib(%3.1f) = %.1f" % tmr.elapsed() % i % r); }浮動小数点数の即値は直接比較メソッドで使えるようにしていないので(すればいいんだけど)一旦レジスタに格納して使う必要がある。
frun()
することで Double 値を受け取れる。結果は以下の通り。[ 0.000] fib(0.1) = 0.1 [ 0.000] fib(0.2) = 0.2 [ 0.000] fib(0.3) = 0.3 [ 0.000] fib(0.4) = 0.5 [ 0.000] fib(0.5) = 0.8 [ 0.000] fib(0.6) = 1.3 [ 0.000] fib(0.7) = 2.1 [ 0.000] fib(0.8) = 3.4 [ 0.000] fib(0.9) = 5.5 [ 0.000] fib(1.0) = 8.9 [ 0.000] fib(1.1) = 14.4 [ 0.000] fib(1.2) = 23.3 [ 0.000] fib(1.3) = 37.7 [ 0.000] fib(1.4) = 61.0 [ 0.000] fib(1.5) = 98.7 [ 0.000] fib(1.6) = 159.7 [ 0.000] fib(1.7) = 258.4 [ 0.000] fib(1.8) = 418.1 [ 0.000] fib(1.9) = 676.5 [ 0.000] fib(2.0) = 1094.6 [ 0.000] fib(2.1) = 1771.1 [ 0.000] fib(2.2) = 2865.7 [ 0.000] fib(2.3) = 4636.8 [ 0.000] fib(2.4) = 7502.5 [ 0.000] fib(2.5) = 12139.3 [ 0.001] fib(2.6) = 19641.8 [ 0.001] fib(2.7) = 31781.1 [ 0.002] fib(2.8) = 51422.9 [ 0.003] fib(2.9) = 83204.0 [ 0.004] fib(3.0) = 134626.9 [ 0.006] fib(3.1) = 217830.9 [ 0.015] fib(3.2) = 352457.8 [ 0.020] fib(3.3) = 570288.7 [ 0.027] fib(3.4) = 922746.5出力コードは以下の通り。これも Windows 版。浮動小数点数を引き渡すために、最初に簡単なフック関数がある。SLJIT は関数のエントリポイントでの引数で浮動小数点数を指定できないので、こういう形で回避している。
そういう意味でも、SLJIT を直接使うよりこっちを使うほうが色々面倒見てくれて良い。ローカル変数領域で必要なサイズを自動的に計算したり、非破壊レジスタのための一時保存コードなども必要な数行うように自動で計算もさせているので。
0: 53 push rbx 1: 56 push rsi 2: 57 push rdi 3: 48 8b d9 mov rbx, rcx 6: 48 8b f2 mov rsi, rdx 9: 49 8b f8 mov rdi, r8 c: 4c 8b 4c 24 d0 mov r9, [rsp-0x30] 11: 48 83 ec 30 sub rsp, 0x30 15: 0f 29 74 24 20 movaps [rsp+0x20], xmm6 1a: f2 0f 10 03 movsd xmm0, qword [rbx] 1e: 48 89 f2 mov rdx, rsi 21: 49 89 f8 mov r8, rdi 24: 48 89 c1 mov rcx, rax 27: e8 0d 00 00 00 call 0x39 2c: 0f 28 74 24 20 movaps xmm6, [rsp+0x20] 31: 48 83 c4 30 add rsp, 0x30 35: 5f pop rdi 36: 5e pop rsi 37: 5b pop rbx 38: c3 ret 39: 53 push rbx 3a: 56 push rsi 3b: 57 push rdi 3c: 48 8b d9 mov rbx, rcx 3f: 48 8b f2 mov rsi, rdx 42: 49 8b f8 mov rdi, r8 45: 4c 8b 4c 24 b0 mov r9, [rsp-0x50] 4a: 48 83 ec 50 sub rsp, 0x50 4e: 0f 29 74 24 20 movaps [rsp+0x20], xmm6 53: f2 0f 11 6c 24 38 movsd [rsp+0x38], xmm5 59: f2 0f 10 f0 movsd xmm6, xmm0 5d: 49 b9 33 33 33 33 33 33 d3 3f mov r9, 0x3fd3333333333333 67: 4c 89 4c 24 40 mov [rsp+0x40], r9 6c: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40] 72: 66 0f 2e f0 ucomisd xmm6, xmm0 76: 73 17 jae 0x8f 78: f2 0f 10 c6 movsd xmm0, xmm6 7c: f2 0f 10 6c 24 38 movsd xmm5, qword [rsp+0x38] 82: 0f 28 74 24 20 movaps xmm6, [rsp+0x20] 87: 48 83 c4 50 add rsp, 0x50 8b: 5f pop rdi 8c: 5e pop rsi 8d: 5b pop rbx 8e: c3 ret 8f: 49 b9 9a 99 99 99 99 99 c9 3f mov r9, 0x3fc999999999999a 99: 4c 89 4c 24 40 mov [rsp+0x40], r9 9e: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40] a4: f2 0f 10 e6 movsd xmm4, xmm6 a8: f2 0f 5c e0 subsd xmm4, xmm0 ac: f2 0f 11 e0 movsd xmm0, xmm4 b0: 48 89 c1 mov rcx, rax b3: e8 81 ff ff ff call 0x39 b8: f2 0f 10 e8 movsd xmm5, xmm0 bc: 49 b9 9a 99 99 99 99 99 b9 3f mov r9, 0x3fb999999999999a c6: 4c 89 4c 24 40 mov [rsp+0x40], r9 cb: f2 0f 10 44 24 40 movsd xmm0, qword [rsp+0x40] d1: f2 0f 10 e6 movsd xmm4, xmm6 d5: f2 0f 5c e0 subsd xmm4, xmm0 d9: f2 0f 11 e0 movsd xmm0, xmm4 dd: 48 89 c1 mov rcx, rax e0: e8 54 ff ff ff call 0x39 e5: f2 0f 58 c5 addsd xmm0, xmm5 e9: f2 0f 10 6c 24 38 movsd xmm5, qword [rsp+0x38] ef: 0f 28 74 24 20 movaps xmm6, [rsp+0x20] f4: 48 83 c4 50 add rsp, 0x50 f8: 5f pop rdi f9: 5e pop rsi fa: 5b pop rbx fb: c3 retおわりに
JIT 面白いですね。これでパーサ・コンビネータとか実装して組み合わせたらたら、ちょっとした JIT 付き言語処理系が作れますね。そういう道を目指してもいいかも。
おそらく想定する使い方としては次の 2 通りでしょうか。
- Kinx のライブラリ作成の際に数値計算等の範囲で JIT 化し、高速化する。
- DSL(ドメイン固有言語)やオレオレ言語のホストとなり、バックエンドの出力に使う。
ではまた。
- 投稿日:2020-06-29T22:41:52+09:00
【Docker入門】Deno環境をDockerで構築する??
はじめに
※ Docker入門者の学習記事です!
Deno が盛り上がって来ているように見えます。?
興味をそそられたので、学習も兼ねてさっそくDockerでコンテナ化してみました。
ゴール
- Dockerfileを自作して、イメージを作ってみる
- コンテナにDenoをインストールして、触れる環境を作る
- Denoの標準APIを使用して、Webサーバーを立ち上げる
Dockerfileを作成
コンテナイメージの元となる
Dockerfile
を作成します。最終的には、以下のようなものが出来上がりました。
FROM debian:stable-slim WORKDIR /var/www/html RUN apt-get -qq update \ && apt-get -qq -y install curl zip unzip \ && curl -fsSL https://deno.land/x/install/install.sh | sh \ && apt-get -qq remove curl zip unzip \ && apt-get -qq remove --purge -y curl zip unzip \ && apt-get -qq -y autoremove \ && apt-get -qq clean \ && echo 'export DENO_INSTALL="/root/.deno"' >> ~/.bash_profile \ && echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bash_profile CMD ["/bin/bash", "-c", "source ~/.bash_profile && bash"]Dockerfileの内容
基本設定
元となるOSイメージ: 今回は軽量な
debian-slim
を選択FROM debian:stable-slim今回はWebサーバーを起動させるので、
/var/www/html
を作業ディレクトリに指定してみました。WORKDIR /var/www/htmlDenoのインストール
基本的には、Deno公式のやり方と同じです。
まずは、Denoのインストールと、シェルスクリプトの実行に必要な
curl
zip
unzip
をインストールします。RUN apt-get -qq -y install curl zip unzipDeno公式のやり方と同じようにインストールコマンドを実行。
RUN curl -fsSL https://deno.land/x/install/install.sh | shあとしまつ
不要なファイルを削除します。
—purge
オプションを使用すると設定ファイルごとすべて削除します。再び使わないようなパッケージに使用。RUN apt-get -qq remove --purge -y curl zip unzip不要になった依存パッケージまで削除
RUN apt-get -qq -y autoremoveパスを通す
deno
コマンドを使用するために、パスを通してやる必要があります。RUN echo 'export DENO_INSTALL="/root/.deno"' >> ~/.bash_profile \ && echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bash_profileたった今変更したプロファイルを、bashに適用します。
CMD
にはコンテナ起動直後に実行されるコマンドを記述します。コンテナ起動後も引き続きbashを使用するので、最後に
&& bash
を引っ付けておきました。CMD ["/bin/bash", "-c", "source ~/.bash_profile && bash"]イメージのビルド
-t
オプションをつけることで、名前をつけてイメージをビルドします。今回はdeno-docker
と名付けました。Dockerfileを作ったディレクトリで、以下のコマンドを実行します。
docker build ./ -t deno-docker
コンテナの起動
以下のコマンドで、コンテナを起動します。
ちょっと長いですが...
docker container run -it --rm -p 8080:8080 --mount src=`pwd`,target=/var/www/html,type=bind --disable-content-trust deno-docker1行づつコメントを以下につけました。
docker container run \ # コンテナ起動 -it \ # コンテナに入って標準入力を待ち受ける -p 8080:8080 \ # ポートの指定(ホストのポート:コンテナのポート) --rm \ # コンテナを停止したら自動でコンテナ廃棄する --mount src=`pwd`,target=/var/www/html,type=bind\ # バインドマウント --disable-content-trust\ # 証明書のないイメージを信頼する deno-docker # 先ほど作成したイメージDenoでWebサーバーを立ち上げる
コチラも公式チュートリアルのまんまです。
詳しい解説は、公式から確認できます。
helloDeno.tsimport { serve } from "https://deno.land/std@0.50.0/http/server.ts"; const s = serve({ port: 8080 }); // さっきコンテナに指定したポート console.log("http://localhost:8080/"); for await (const req of s) { req.respond({ body: "<h1>Hello Deno</h1>\n" }); }
Denoは標準でTypescriptをサポートしています! 面倒な設定ともオサラバ?✋
async
なしで、await
が使用可能です!denoコマンドで実行する
先ほどバインドマウントしたディレクトリに、Typescriptファイル(今回は
helloDeno.ts
)を保存し、
以下のコマンドを実行します。deno run --allow-net helloDeno.ts
Denoは標準でファイル、ネットワーク、環境変数等にアクセスすることができないセキュアな設定になっています。
ネットワークを許可するには
--allow-net
オプションを付与して実行します。見事、Webサーバーが起動できました!
まとめ
- Dockerfileを自作して、イメージを作ることができた
- コンテナにDenoをインストールして、(少しだけど)Denoプログラムを書けた
- Denoの標準APIを使用して、Webサーバーを立ち上げた
参考
- 投稿日:2020-06-29T22:21:21+09:00
nodejsを使ってウエブサーバーを作ろう - majidai
majidaiとは
majidaiとは、nodejs用のWebフレームワークです。
サードパーティーライブラリが使っていないため非常に軽いです。
データ量 < 50KBインストール
nodejsがインストールはこの記事のスコープ外とします。
1. プロジェクトの初期化
タミナルから作業フォルダーに移動し、プロジェクトの初期化を行います。
npm init -y
2. majidaiインストール
以下のコマンドで「majidai」をインストールします。
npm install majidai
3. サーバーコード
以下のコマンドで「majidai」をインストールします。
server.js// majidaiの読み込み const majidai = require("majidai"); // インスタンス化 const server = new majidai(); // 受付開始 server.start();4. serverの起動
以下のコマンドでウエブサーバーの起動します。
node server.jsそして、ブラウザを起動し、http://localhostにアクセスします。以下の画面が表示されればOKです。
デフォルトでは80ポートで受け付けしますが、以下のようにポートを指定する事が可能です。
const config = { http: { port: 8000 } }; const server = new majidai(config);静的コンテンツの配信
デフォルトでは静的コンテンツの配信が無効にしてますが、以下のように「Document Root」指定する事で指定のフォルダーは以下のものを配信する事が可能です。
const config = { http: { documentRoot: './public' } }; const server = new majidai(config);使い方
majidaiの書き方は以下のようになります。
server.METHOD(PATH, CALLBACK)METHODとは
以下の3種類があります。
- get : http GETリクエストのみ受け付けます
- post : http POSTリクエストのみ受け付けま
- listen :複数のhttpメソッドを一つのPATHで受け付けます
※詳細についてはこちらPATHとは
"/" , "/top", "/user/dakc",..みたいにどこでリクエストを受け付けるかの事です。
CALLBACKとは
これはコールバック関数の事です。指定のPATHにリクエストを受け付けたら、この関数が実行されます。関数の引数が以下の2つです。
- request : <http.IncomingMessage>
- response : <http.ServerResponse>
majidaiが両方のオブジェクトに「mj」オブジェクトを追加します。request.mj
requestに追加したmjオブジェクトがクライアントから送られて来たデータを操作するために以下の2つの関数(メソッド)を有します。
- getParams : GETで送られて来た情報をJSONオブジェクトとして返します。キーを引数として指定することで特定のキーに対する値のみを取得する事が可能です。
- postParams: POSTで送られて来た情報をJSONオブジェクトとして返します。キーを引数として指定することで特定のキーに対する値のみを取得する事が可能です。※x-www-form-urlencoded, application/json, form-dataどれでデータが送られて来てもJSONオブジェクトとして返します。
response.mj
クライアントにレスポンスを返すために以下のメソッドを有します。
- text : plain/textとしてレスポンス
- json : application/jsonとしてレスポンス
- static : 静的コンテンツのレスポンス
- redirect : 別のURLへリダイレクト
- error : HTTPエラーとしてレスポンス
※ 詳細についてはこちら
samples
サンプルを見ながらどのようなことできるかやってみましょう!
1. GET受付
get-sample.jsconst server = new majidai(); server.get("/home", (request,response) => { // リクエストがhttp://〇〇/home?price=230) var price = request.mj.getParams("price"); console.log(price); // 230 // OR // 全データをJsonオブジェクトとして取得 var getData = request.mj.getParams(); console.log(getData); // { price: '230' } // 「price」の値を取得 var price_ = getData.price; console.log(price); // 230 return "ここで返すものがクライアントへのレスポンスになります"; }); server.start();2. POST受付
post-sample.jsconst server = new majidai(); server.post("/login", (request,response) => { // リクエストがhttp://〇〇/login // <form action="/login" method="POST"> // <input name="id" type="text" value="abc"> // <input name="pass" type="password" value="p@ss"> // </form> // 「id」の値を取得 var id= request.mj.postParams("id"); console.log(id); // abc // OR // 全データをJsonオブジェクトとして取得 var postData = request.mj.postParams(); console.log(postData); // { id: 'abc', pass: 'p@ass' } // 「id」の値を取得 var id_ = postData.id; console.log(id_); // abc return "ここで返すものがクライアントへのレスポンスになります"; }); server.start();3. 複数HTTPメソッド受付
multiple-methods.jsconst server = new majidai(); server.listen({ method: ["GET", "POST"], path: "/profile" }, (request, response) => { // GETでもPOSTでもこのの処理を実行されます。 // DO SOMETHING return "something"; }); server.start();Parameterized URL
どのような表現が分かりやすいかよくわからなかったため、上記の表記にしました。ごめんなさい。何か気付くところがありましたら是非コメントに残してやってください。
この機能がどうしても実現したかった機能です。
「http://〇〇/user?id=abdf&command=edit」より
「http://〇〇/user/abdf/edit」
ほうが好きだったからです。parameterized-url.js// {}の中のものがその名前のキーでデータを取得する事ができます。 server.get("/books/{year}/{price}", function (req, res) { // リクエストがhttp://〇〇/books/2020/5000 // {}の中のものがその名前のキーでデータを取得する事ができます。 // 「year」の値を取得 var year = req.mj.getParams("year"); console.log(year ); // 2020 // 「price」の値を取得 var price= req.mj.getParams("price"); console.log(price); // 5000 // GETと受けたものをレスポンスとして返す return req.mj.getParams(); });※post,listenの時もParameterizedURL機能が使えます。
Docker
以下の1行のコマンドを実行する事でブラウザーからmajidaiの動作を確認できます。
docker run -it --rm -p 80:80 dakc/majidai npx /data/server.js最後に
nodejsの標準の機能のみで動く軽量なフレームワークを欲しかったため、majidaiを開発しました。MITライセンスでリリースしておりますのでご自由にお使いください。
DOCUMENTATION - https://dakc.github.io/majidai.html
- 投稿日:2020-06-29T22:17:46+09:00
ある一定の所までスクロールすると下に出てくるウザいバナーの出し方。コピペで使える。
くそサイトでよく見かけるアレです。
成果物
https://youthful-mccarthy-ce6430.netlify.app
リポジトリ
https://github.com/yuzuru2/annoying_banner
index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>ウザいバナー</title> <style> body { margin: 0; } #banner { display: none; width: 100%; position: fixed; bottom: 0; } #banner img { width: 100%; position: relative; } </style> </head> <body> <div> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> <h3>ある一定の所までスクロールするとウザいバナーが出る</h3> </div> <footer> <!-- ウザいバナー --> <div id="banner"> <img src="img/banner.png" /> </div> </footer> <!-- jquery読み込み --> <script src="https://code.jquery.com/jquery-3.5.1.js"></script> <script> $(function () { var _dom = $('#banner'); // スクロールした時 $(window).on('scroll', function () { if (document.documentElement.scrollTop <= 800) { if (_dom.css('display') === 'block') { _dom.fadeOut(1000); } return; } if (document.documentElement.scrollTop > 800) { if (_dom.css('display') === 'none') { _dom.fadeIn(500); } } }); }); </script> </body> </html>img/banner.png
- 投稿日:2020-06-29T20:24:47+09:00
【javascript】Object.assign
Object.assignについて
Object.assignは引数として任意のオブジェクトを受け取り、後続のオブジェクトの値をすべて最初のオブジェクトに割り当てルシ組になってます。
いずれかのオブジェクトに同じキーが含まれている場合はリスト右側のオブジェクトによってオブジェクトが上書きされます。
let a = { x: 9, y: 2, z: 5}; let b = { x: 4, y: 28}; let c = { x: 21}; Object.assign(a, b, c); console.log(a) //{x:21,y:28,z:5};不完全なオブジェクトを受け取り欠けている値を補う、またはカスタマイズが可能になります。
Object.assing使用時のミューテーション回避
Object.assignが変更するのはパラメータリストの最初のオブジェクトであり、残りは変更されない。
上記の例だとオブジェクトaを上書きしていることになる。aは変更せずコピーを作成し、それを変更したい時にはどうするか。
常套手段は、最初のオブジェクトを空のオブジェクトリテラルにすること。let newObject = {}; Object.assign(newObject, { foo: 1}, { bar: 2}); console.log(newObject); //{foo: 1, bar: 2}これは以下のように簡略化もできる。
let newObject = Object.assign({}, { foo: 1}, { bar: 2}); console.log(newObject); //{foo: 1, bar: 2}Object.assignが値を割り当てる仕組み
ここまでを見ているとObject.assignは右のオブジェクトを左にオブジェクトにマージすると思ってしまうが、必ずしもそうではない。
- Object.assignは右のオブジェクトを左のオブジェクトに割り当てているだけ。
- 代入を実行するだけで再定義をしない。
- 割当の対象となるのはオブジェクト自体の列挙可能なプロパティだけ 3に関してはオブジェクトのプロトタイプチェーンのプロパティには割り当てられないよ言うことである。
Object.assignまとめ
- Object.assign複数オブジェクトのプロパティからの値をベースオブジェクトに割り当てる。
- Object.assignはデフォルト値を設定するために使用できる。
- Object.assignは。オブジェクトを新しいプロパティで拡張するために使用できる。
- Object.assignは変更されたオブジェクトを返すため、最初の引数として{}を使用するとコピーできる。
- Object.assignはプロパティに値を割り当てるだけで、プロパティを再定義しない。
- 投稿日:2020-06-29T20:02:14+09:00
【Rails】画像プレビュー機能の実装
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装実装
1.ビューを編集
users/edit.html.slim/ 追記 = f.file_field :profile_image, class: 'img_field', style: 'display:none;' = attachment_image_tag @user, :profile_image, fallback: 'no-image.png', onClick: "$('.img_field').click()", class: 'center-block img-thumbnail img-responsive img_prev' br【解説】
①
file_field
をdisplay:none
の非表示にし、クラスを付与する。= f.file_field :profile_image, class: 'img_field', style: 'display:none;'②
①
で付与したクラスのHTML(file_field)がクリックされたらJavaScriptの処理を実行する。onClick: "$('.img_field').click()"2.
appliacation.scss
を編集appliacation.scss// 追記 .img_prev:hover { cursor: pointer; opacity: 0.7; transform: scale(1.01, 1.01); }【解説】
① 画像にマウスが乗っている時にCSSを反映させる。
.img_prev:hover {}② マウスカーソルをポインターに変更する。
cursor: pointer;③ 不透明度を低くして、画像を少し白くする。
opacity: 0.7;④ 少しだけ画像を拡大する。
transform: scale(1.01, 1.01);3.JavaScriptファイルを作成・編集
ターミナル$ touch app/assets/javascripts/image_preview.jsimage_preview.js$(function () { function readURL(input) { if (input.files && input.files[0]) { var reader = new FileReader(); reader.onload = function (e) { $('.img_prev').attr('src', e.target.result); } reader.readAsDataURL(input.files[0]); } } $('.img_field').change(function () { readURL(this); }); });【解説】
①
1.ビューを編集の②
で実行する処理。$('.img_field').change(function () { readURL(this); });
- 投稿日:2020-06-29T19:37:30+09:00
TypeScriptの型記法「オブジェクトリテラル 」と「インデックスシグネチャ」を理解する
TypeScriptの型において、「オブジェクトリテラル」と「インデックスシグネチャ」について、まとめます。
「オブジェクトリテラル」も「インデックスシグネチャ」も、あまり聞き慣れない言葉で、雰囲気で理解した気になっていませんか?
私はなっていました。
私の場合、この記法に遭遇するたびに「これって、なんだっけ?」ってなることが多いです。
自分の理解のために、ここにまとめておきたいと思います。
誰かの理解の助けになれば幸いです。
初見殺しな「オブジェクトリテラル」と「インデックスシグネチャ」
例えば、以下のコードにおいて
let obj: { [key: string]: string // インデックスシグネチャ } // オブジェクトリテラル変数objの型が、何を意味しているか理解できますでしょうか。
私には、初見殺しすぎて、さっぱりわからず「???」となりました。この型の意味は、「変数objは複数のkeyを含む可能性があり、そのすべてのstring型のkeyは、string型の値をもつ」となります。
以下のコードにおいて、Errorの箇所では、idにnumber型の値を入れているのでエラーとなります。
let obj: { [key: string]: string // インデックスシグネチャ } // オブジェクトリテラル // OK obj = { id: "123" token: "hogetoken" name: "fuganame" } // Error // Type 'number' is not assignable to type 'string'. obj = { id: 123 token: "hogetoken" name: "fuganame" }オブジェクトリテラルとは
let obj: { id: string token: string name: string } // オブジェクトリテラルこのように中括弧({})を用いて、オブジェクトの型を定義する記法のことを指します。
オブジェクトは、ハッシュテーブルの一種で、キーと値の組みを持ちます。
TypeScriptでは、多くの場合、オブジェクトの型は「クラス」を用いて定義されます。しかし、用途によってはクラスを定義せずに、即席の型定義を記述する方が便利な場合があります。
その際に用いられるのが「オブジェクトリテラル」です。
「オブジェクトリテラル」は、匿名のオブジェクト型を宣言したい場合に用いられます。
(例えばJavaScriptからTypeScriptへの移行期に、"とりあえず"の型を定義したい場合など)インデックスシグネチャとは
let obj: { // [key: T]: U [key: string]: string // インデックスシグネチャ }インデックスシグネチャは、角括弧([])を用いて「[key: T]: U」という構文で記述されます。
これは「オブジェクトが、複数のkey(型T)を含む可能性があること」を示します。
そして、「すべてのkey(型T)は型Uの値を持つこと」も示します。
オブジェクトのキーと値の型を抽象化して記述することができます。備考
例えば、idだけnumber型で、その他のパラメータはstringの場合、以下のように書けるのかなと思ったのですが、これはエラーになります。
let obj: { id: number // エラー [key: string]: string } // Property 'id' of type 'number' is not assignable to string index type 'string'. // Type '{ id: number; token: string; name: string; }' is not assignable to type // '{ [key: string]: string; id: number; }'. Property 'id' is incompatible with // index signature. Type 'number' is not assignable to type 'string'. obj = { id: 123 token: "hogetoken" name: "fuganame" }keyがstringの場合は、stringの値を持つと宣言してしまっているので、
[key: string]: stringidをnumber型だと定義すると矛盾が発生してしまうためです。
id: numberよって、この場合はおとなしく、インデックスシグネチャを用いないで書きましょう。
let obj: { id: number token: string name: string } // オブジェクトリテラルまとめ
・オブジェクトリテラル({})は、オブジェクトの型を即席で定義するために用いられる。
・インデックスシグネチャ([key: T]: U)は、オブジェクトが、複数のkey(型T)を含む可能性があり値はU型になることを示している。結論としては、オブジェクトリテラルもインデックスシグネチャもクセが強いので、あまり用いたくないですが、そうは言っても現場ではすでにオブジェクトリテラルやインデックスシグネチャを使用している箇所については、コードを読むために理解しておきたいと思います。
- 投稿日:2020-06-29T19:32:23+09:00
CLS とは?ブラウザとNode.jsで CLS を実装してみます
資料を調べる際に、CLS の存在を知りました。エンジニアリングで結構いいデカップリングのやり方と感じまして、シェアしたいと思います。
シチュエーション
ブラウザか、サーバーのNode.jsか、どっちでもエラーハンドリング、ユーザートラッキングのニーズは日常茶飯事。例えユーザーを特定しなくても、id をつけて、ユーザーの行為を追跡して、エラーの再現にも重要し、プロダクトの改善にも役たちます。
仮に今エラーハンドリングを書こうと思って、このエラーハンドリングはすべてのエラーを処理しますが、どのリクエストから生み出したエラーを知りたいと
log.error("Error occured", req);このハンドリングは req と結合しちゃった
仮に今このエラーどのユーザーから出たエラー、ユーザーが何をやったかを知りたいと
log.info("User has done xxx", user); log.error("Error occured by", user);ユーザーとも結合しちゃった
この2つの例は一見するとそんなに大きいな問題ではなさそう、ただ2つのパラメータが増えただけじゃ。
だけど、大型サービスを作る時、どんどん増えた機能に対して、関数の引数と関数の長さと共にどんどん伸びちゃって気持ち悪くてリファクタリングしようとしょうもないこと、少なくありませんでしょうか?解決してみよう
関数が同期のであれば、グローバルで変数につけたらいいじゃんー
const global = {}; $("button").click((event) => { global.event = event; log("button clicked"); }); function log(...args) { console.log(global.event, ...args); // { x: xxx, y: xxx, target: xxx } 'button clicked' // other logic }だが、非同期関数のであれば
const global = {}; $("button").click((event) => { global.event = event; setTimeout(() => { log("button clicked"); }, 1000); }); function log(...args) { console.log(global.event, ...args); // other logic }すべての global.event は同じイベントになちゃった(´;ω;`)!それはだめですね。
我々必要なのは非同期呼び出しチェーンに最初から最後まで持続的なストレージ、
もしくは今走ってる非同期関数の呼び出しの唯一の識別子。CLS が登場
他の言語では、Thread-local storageと呼ばれるものがあります。が JavaScript はマルチスレッドはありません(Web Workerなどはメインと関係ないし、自分でもマルチスレッドしない)。CLS という名前は TLS みたいに関数型プログラミングからの Continuation-passing style 名前をもらって、Continuation-local Storage、そのチェインの呼び出しの中で持続的データストレージをメンテナンスする。
ブラウザの解決 Zone.js
どうのように解決したかちょっと見てみましょう
$('button').click(event => { Zone.current.fork({ name: 'clickZone', properties: { event } }).run( setTimeout(() => { log('button clicked'); }, 1000); ); }); function log(...args) { console.log(global.event, ...args); // other logic }
Zone.js
は Angular 2.0 から誕生したもので、もちろん他の機能も持ってる。この方法は残念なところがあります
考えてみましょう、
Zone.js
はどうやってこれを実現しました。ブラウザは呼び出しに対して唯一の識別子を提供するAPIがなければ、すべての非同期関数をリライトしかできなく、そうすれば非同期が入る時と出る時 hook できて、この効果が実装できますね。自分も書いてみました。
const Zone = { _currentZone: {}, get current() { return { ...this._currentZone, fork: (zone) => { this._currentZone = { ...this._currentZone, ...zone, }; return this; }, set: (key, value) => { this._currentZone[key] = value; }, }; }, }; (() => { const _setTimeout = global.setTimeout; global.setTimeout = (cb, timeout, ...args) => { const _currentZone = Zone._currentZone; _setTimeout(() => { const __after = Zone._currentZone; Zone._currentZone = _currentZone; cb(...args); Zone._currentZone = __after; }, timeout); }; })(); for (let i = 0; i < 10; i++) { const value = Math.floor(Math.random() * 100); console.log(i, value); Zone.current.fork({ i, value }); setTimeout(() => { console.log(Zone.current.i, Zone.current.value); }, value); }また問題なさそうだけど、
angular with tsconfig target ES2017 async/await will not work with zone.js
ブラウザ今では完璧の解決方法はありません
実験をやってみましょう、console で下のコードを打ったら、
const _promise = Promise; Promise = function () { console.log('rewrite by ourselves') }; new Promise(() => {}) instanceof Promise // rewrite by ourselves // true async function test() {} test() instanceof Promise // false test() instanceof _promise // true async function test() { return new Promise() } test() instanceof Promise // rewrite by ourselves // false test() instanceof _promise // rewrite by ourselves // trueブラウザは、async 関数のリターンをネイティブの Promise で再ラッピングします。ネイティブ文法なので、async 関数はリライトできない。
もちろん transpiler で async 関数を generator もしくは Promise にすることは可能ですが、完璧とは言わないでしょう。Node.js の解決
async_hooks
Node.js バージョン 8 以降出た
async_hook
モジュール、バージョン 14 の今でもExperimental
ステータスから脱却してない。出たごろ性能に関しての議論もあったが、今はどうなってるかまだわからない状態ですがExperimental ステータスにしても安定性としては問題なさそう、大量な Node.js のトラッキング / APM が依存していて、問題があったら issue が立てられるはずです。
性能に関する問題はここは展開しない、コードの低結合と少しパフォーマンスの低下を交換するかしないかによりますね。
使い方
async_hooks
はcreateHook
という関数を提供した、これが非同期関数のライフサイクルに hook できます、しかも唯一識別子も提供してくれますので、CLS を簡単に作れます。const { executionAsyncId, createHook, } = require("async_hooks"); const { writeSync: fsWrite } = require("fs"); const log = (...args) => fsWrite(1, `${args.join(" ")}\n`); const Storage = {}; Storage[executionAsyncId()] = {}; createHook({ init(asyncId, _type, triggerId, _resource) { // log(asyncId, Storage[asyncId]); Storage[asyncId] = {}; if (Storage[triggerId]) { Storage[asyncId] = { ...Storage[triggerId] }; } }, after(asyncId) { delete Storage[asyncId]; }, destroy(asyncId) { delete Storage[asyncId]; }, }).enable(); class CLS { static get(key) { return Storage[executionAsyncId()][key]; } static set(key, value) { Storage[executionAsyncId()][key] = value; } } // --- seperate line --- function timeout(id) { CLS.set('a', id) setTimeout(() => { const a = CLS.get('a') console.log(a) }, Math.random() * 1000); } timeout(1) timeout(2) timeout(3)Node.js バージョン 13 からオフィシャルの実装も
コミュニティの中でたくさんの CLS ライブラリーがあった上に、Node.js 13.10 から
AsyncLocalStorage
の API がありました。https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage
実はこれはすでにすぐに使える CLS です。
const { AsyncLocalStorage, } = require("async_hooks"); const express = require("express"); const app = express(); const session = new AsyncLocalStorage(); app.use((_req, _res, next) => { let userId = Math.random() * 1000; console.log(userId); session.enterWith({ userId }); setTimeout(() => { next(); }, userId); }); app.use((_req, res, next) => { const { userId } = session.getStore(); res.json({ userId }); }); app.listen(3000, () => { console.log("Listen 3000"); }); const fetch = require('node-fetch') new Array(10).fill(0).forEach((_, i) => fetch('http://localhost:3000/test', { method: 'GET', }).then(res => res.json()).then(console.log)) // Output: // Listen 3000 // 355.9573987560112 // 548.3773445851497 // 716.2437886469793 // 109.84756385607896 // 907.6261832949347 // 308.34659685842513 // 407.0145853469649 // 525.820449114568 // 76.91502437038133 // 997.8611964598299 // { userId: 76.91502437038133 } // { userId: 109.84756385607896 } // { userId: 308.34659685842513 } // { userId: 355.9573987560112 } // { userId: 407.0145853469649 } // { userId: 525.820449114568 } // { userId: 548.3773445851497 } // { userId: 716.2437886469793 } // { userId: 907.6261832949347 } // { userId: 997.8611964598299 }参照
- 投稿日:2020-06-29T19:25:23+09:00
連想配列を指定した順番に並び替える
例えば、
const hoge = [ { eto: '卯', }, { eto: '丑', }, { eto: '子', }, { eto: '辰', }, { eto: '寅', }, ];のような連想配列があって、これを
const etoIndex = ['子', '丑', '寅', '卯', '辰'];の順番で並び替えたいみたいな場合、
以下のコードで解決です。hoge.sort((a, b) => etoIndex.indexOf(a.eto) - etoIndex.indexOf(b.eto), );sort()は1か-1を返す使い方しか知りませんでした!
- 投稿日:2020-06-29T17:06:49+09:00
【vue】トランジションのあらゆるタイミングでjsを走らせる【トランジションフック】
概要
トランジションが発生したタイミングでjsを処理させたいときに便利な機能
例
トランジションの中でも、以下のタイミングでjsの処理をさせてみる。
- DOMに要素が追加された後(
enter
)- トランジションが終わるとき (
after-enter
)規定に沿ったフック名を
transition
要素に与えることで、各タイミングで処理が走る。.html<div id="app"> <button v-on:click="hook = !hook">hook</button> <transition v-on:enter="enter" v-on:after-enter="afterEnter"> <div v-if="hook">example</div> </transition> </div>.jsnew Vue({ el: '#app', data: { hook: true }, methods: { /*1.DOMに要素が追加された後の処理*/ enter: function(el, done) { console.log('enter') setTimeout(done, 1000) }, /*2. トランジションが終わるときの処理*/ afterEnter: function(el) { console.log('after-enter') } }, })トランジションのタイミング
トランジションが発生するタイミングとして下記のように分類できる。
- 要素が追加されるとき
- 要素が削除されるときさらに細かく分類したときのフック名は以下の通り
要素が追加されるとき(Enterフェーズ)
- DOMに要素を追加:
before-enter
v-enter
を追加:enter
- トランジション終了:
after-enter
- Enterフェーズが途中でキャンセル:
enter-cancelled
要素が削除されるとき(leaveフェーズ)
- クラス付与前:
before-leave
.v-leave
が追加後:leave
- 要素削除後:
after-leave
- leaveフェーズが途中でキャンセル:
leave-cancelled
- 投稿日:2020-06-29T16:30:57+09:00
DenoでGoogle Chromeでもブックマークニックネーム!
はじめに
Google Chrome でもブックマークニックネーム機能を使うために、検索エンジンを追加する拡張機能を Deno で自動作成し、それを読み込むことにしました。
旧記事: Chrome でもブックマークニックネームがしたかった私は拡張機能を自動作成することにした。
削除済み旧旧記事: Python で検索エンジン拡張機能を自動生成するツールを作った(for Chrome)Google Chrome とは
天下の Google 様がお作りになった Web ブラウザです。
残念ながら私にとって必要な機能が存在しないため使えません。
私も普段は使っていません。Firefox のほうがいいです。Q. 必要な機能って何だよ
A. ↓ブックマークニックネームとは
ブックマークに一意のニックネーム(キーワード)を与えることで、アドレスバー(オムニボックス)での補完をより使いやすくする機能です。
あらかじめブックマークに与えた一意の文字列をアドレスバーに入力すると、該当するブックマークが強制的に 1 位にサジェストされ、選択状態になります。
そして、そのまま Enter キーを押下することでそのブックマークが開かれます。キーボード操作だけですぐにブックマークを開くことができるのです。
とても素晴らしい機能なのですが、なぜか天下のGoogle Chrome様にはありません。Google Chrome でのその機能の再現方法
しかし、裏技的な感じにはなりますが再現は可能です。
カスタム検索エンジンとは
検索エンジンをカスタマイズする機能です。
検索エンジンの名前・検索エンジンを呼び出すキーワード・URLを設定画面で登録します。
通常はそのURLとして検索語句を%s
に置換した文字列を指定するのですが、%s
が含まれない文字列を指定した場合、ブックマークニックネームと同じような挙動をするようになります。その自動化方法
しかし設定画面(
chrome://settings/searchEngines
)は恐ろしいほど使い勝手が悪いです。
腱鞘炎になります。そこで…
拡張機能の作成
本題です。
検索エンジンを追加する拡張機能を作成し、それをWebブラウザに自動で読み込ませることで省力化を図ります。拡張機能の仕様
細かな説明は省きますが、
manifest.json
にてchrome_settings_overrides
キーを使えばsearch_provider
を追加することが可能です。しかし、拡張機能では一つしか
manifest.json
を使えず、一つのmanifest.json
では一回しかchrome_settings_overrides
できません。
よって、複数のブックマークを登録するためにはその分の拡張機能を作る必要があります。拡張機能の自動作成
Denoを使いました。
src/main.ts
が、src/bookmarks.ts
という設定を記述したモジュールをインポートして生成します。特に解説することがないのでGitHubを参照してください。
GitHub見に行くのが怠い人用
main.ts#!/usr/bin/env deno run --allow-read --allow-write // config ///////////////////////////////////////////////// const chromePath = '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"'; import { bookmarks } from './bookmarks.ts'; // init /////////////////////////////////////////////////// const tmpDir = Deno.makeTempDirSync(); Deno.chdir(tmpDir); const textEncoder = new TextEncoder(); const generateUUID = () => { const $ = (a: number, b: number) => (Math.floor(Math.random() * a) + b).toString(16); return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' .replace(/x/g, () => $(16, 0)) .replace(/y/g, () => $(4, 8)); }; // main /////////////////////////////////////////////////// const extensions = bookmarks .map(({ key, name, url }) => { const searchUrl = new URL(url.replace('%s', '{searchTerms}')); const faviconUrl = new URL('https://www.google.com/s2/favicons'); faviconUrl.searchParams.set('domain', searchUrl.host); const manifest = { chrome_settings_overrides: { search_provider: { encoding: 'utf-8', favicon_url: faviconUrl.toString(), is_default: false, keyword: key, name, search_url: searchUrl.toString(), }, }, manifest_version: 2, name, version: '1.0.0', }; return manifest; }) .map((manifest) => { const uuid = generateUUID(); Deno.mkdirSync(uuid); Deno.writeFileSync( `${uuid}/manifest.json`, textEncoder.encode(JSON.stringify(manifest)), ); return uuid; }) .map((uuid) => Deno.realPathSync(uuid)); console.log(chromePath + ' --load-extension=' + extensions.join(','));
拡張機能の自動読み込み
できません。
よってコマンドラインからの起動時に引数として指定します。"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --load-extension=path/to/extension-foo,path/to/extension-barみたいな感じです。
src/main.ts
を実行すると起動用コマンドが標準出力(console.log
)されるのでsh
にパイプして使っています。おわりに
特に解説することがないので感想を。
Denoはいいぞ。
小難しいことなくTypeScript使えるのいいぞ。
ESModulesも標準でいい感じだしいいぞ。
- 投稿日:2020-06-29T15:59:30+09:00
【Javascript】ワンライナーで現在時刻をフォーマットした日付で表示する
getDay()、getHours()とかやってから繋げるのめんどくさい
コード
ワンライナーnew Date().toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });↑改行するとこうnew Date().toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });結果8月10日 19:19別のやり方
new Intl.DateTimeFormat(undefined, { [ここにオプション] }).format(new Date());使い方
dateObj.toLocaleString([locales[, options]])詳細:Date.prototype.toLocaleString()
- locales "ja"とかが入ります。undefinedなら、OSのデフォルトです
- options フォーマットを指定できます(自由度は低い)。詳しくはMDNで
※ localesが日本だと"2-digit"で表示されないことがあります
まとめ
moment.jsのが楽です
- 投稿日:2020-06-29T15:48:12+09:00
Web Components 入門 〜Class ライブラリー編〜
最初に
Web Components 入門シリーズへまたようこそ。このシリーズであなたとあなたの運命の Web Components ライブラリーの出会いをプロデュースしてみています!
前の記事で Web Components の標準仕様や Web Components を作る場合ライブラリーを使う方がいい理由について話しました。
今回は Web Component を作るためのライブラリーが使うパターンの一つ、Class ベースのパターンについて説明したいと思います。
しかし、その前に記事の後半に出てくる bundle サイズについてのお知らせです。
各ライブラリーの minify + gzip 後の bundle データの元としてBundlePhobiaを使います。
ですが、各ライブラリーのコードの書き方によってこれらを実際にアプリの一部として使う場合でのサイズの影響は大きく変わる場合はあります。
WebComponents.dev のみなさんはこういうデータを細かく分析していますので興味があればそれも読んでみてください。(このシリーズでカバーするライブラリーはだいたいこのサービスの Web IDE で試せます。)
さて、そろそろ始めましょう。
Class ベースのパターンは何ですか?
このシリーズの最初の記事でも言った通り、Web Component を作るためにはまず
HTMLElement
を継承する class を作ってそれをCustomElementRegistry
に登録しなければなりません。もちろん、
HTMLElement
を継承する class を自分の class で継承することでも可能です。このパターンのライブライーはこれを利用して、
HTMLElement
を継承する class を作って、それにコンポーネントを作りやすくするコードを入れます。例えば、下記にある
SuperAwesomeElement
と言う class を使えばHTMLElement
を継承しているときよりコンポネントの属性の変更がある場合でのアプデートがしやすくなります。コードのデモはここにあります
export class SuperAwesomeElement extends HTMLElement { constructor() { super(); this.state = {}; } static get attributes() { return {}; } static get observedAttributes() { return Object.keys(this.attributes); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) { return; } // 属性をタイプごとにパーズしてステートを更新します const type = this.attributes[name].type; if (/array|object/i.test(type)) { this.state[name] = JSON.parse(newValue); } else if (/number/i.test(type)) { this.state[name] = parseFloat(newValue); } else { this.state[name] = newValue; } this.update(); } }この class を使ってコンポーネントを作るとこういう風になります
import { SuperAwesomeElement } from "super-awesome-element"; const template = document.createElement("template"); template.innerHTML = ` <p>Text: <span class="text"></span></p> <p>Number: <span class="int"></span></p> <p>Object: <span class="obj"></span></p> <p>Array: <span class="arr"></span></p> `; export class MyComponent extends SuperAwesomeElement { constructor() { super(); this.state = { text: "", int: 0, obj: {}, arr: [] }; this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); this._textNode = this.shadowRoot.querySelector(".text"); this._intNode = this.shadowRoot.querySelector(".int"); this._objNode = this.shadowRoot.querySelector(".obj"); this._arrNode = this.shadowRoot.querySelector(".arr"); } static get attributes() { return { text: { type: "string" }, int: { type: "number" }, obj: { type: "object" }, arr: { type: "array" }, }; } update() { this._textNode.textContent = this.state.text; this._intNode.textContent = this.state.int; this._objNode.textContent = JSON.stringify(this.state.obj); this._arrNode.textContent = JSON.stringify(this.state.arr); } } customElements.define("my-component", MyComponent);もちろん、これはものすごく簡単な例で実際にプロジェクトに使えるというレベルではありません。
しかし、こういった簡単なものでもコンポーネントの作成に必要なコードがかなり縮小されます。
ちゃんとしたライブラリーでできることは段違いで多くなります。 ?
良し悪し
Class ベースのライブラリーを使うと作るコンポーネントは標準に近いものになります。
これはそのままいくつかのいいところと悪いところはあります。
いいところ
- マイグレーションが簡単 - バニラ JS や他の class ベースのライブラリーにマイグレーションすることになったら他のパターンのライブラリーを使っているときよりスムーズにできます。
- 拡張可能 - ライブラリーにないフィーチャーが複数のコンポーネントに必要になったら mixin class を通してコンポーネントに追加できます。しかも、mixin class の作り方によっては、このパターンのどのライブラリーにも使えることもあります。
- 標準仕様を学べる - このパターンのライブラリーを使うと標準仕様への理解が深まる。
悪いところ
- ボイラープレートが若干多い - このパターンのライブラリーを使うとコンポーネントのコードがかなり縮小されますが、JS の class の性質のせいで他のパターンより若干多いです。
- 属性が変更後に副作用を入れるときなどに見られます。
- ですが、これでビルドサイズが多くなるとは限りません、あくまで自分の書くコードの話です。
このパターンを使うライブラリー
スターやバージョンやサイズのデータは投稿時の最新です。
CanJS
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 1.8k+ MIT 1.1.2 (2020/06) 見つけられなかった 66kB can-stache (mustache みたい) 豆知識
CanJS はこのシリーズで紹介するライブラリーの中ではかなり大きいサイズのものです。
なぜかというと、他のライブラリーと違って、CanJS はコンポーネントを作るためのライブラリーというより Web Components ベースのフレームワークからです。
なので、自分のアプリを全て CanJS で作るならいいですけど、単に色々なプロジェクトで利用できるコンポーネントを作るなら他のライブラリーを使った方がいいです。
HyperHTML Element
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 0.1k+ ISC 3.12.3 (2020/03) あり 8.7kB hyperHTML (JS テンプレート文字列) 豆知識
このライブラリーは主に hyperHTML でレンダーされた Web Components を作るためのヘルパーです。
ちなみに、hyperHTML はパーフォマンスで言えばトップクラスのレンダリングライブラリーです。⚡️
LitElement
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 3.5k+ BSD 3-Clause 2.3.1 (2020/03) あり、decorator 含む 7.1kB lit-html (JS テンプレート文字列) 豆知識
Polymer v3 が存在する中 LitElement も Polymer Project のチームが作っているということは色々とややこしいですけど。
簡単に言うと、LitElement は Polymer v4 です、ですが、作り方もパフォーマンスも劇的に変わったのでライブラリーの名前も帰られました。
なので「Polymer」を使いたいのなら LitElement を使うといいです。?
ちなみに LitElement の最初のリリースは v2.0.0 でした、なぜかというと lit-element の npm package はもともと別の人が持っていましたので、所有権はもらえたものの、v1.0.0 はすでにあったのでそれは使えませんでした。
LitElement の姉妹ライブラリーである lit-html は先ほどメンションした hyperHTML と色々と共通点はあります、トップクラスのパフォーマンスの点も含めてです。 ⚡️
Omi
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 11.1k+ MIT 6.19.3 (2020/05) あり、decorator 含む decorators 8.3kB JSX (Preact) 豆知識
Omi はこの記事のライブラリーの中で唯一公式ドキュメントが複数言語対応しています。
ドキュメントが全部英語と中国語にあって、一部は韓国語もあります。??????
SkateJS
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 3.1k+ MIT 0.0.1 (2018/12) 見つけられなかった 1.8kB + レンダーライブラリー hyperHTML/lit-html (JS テンプレート文字列), JSX 豆知識
SkateJS は結構独特なライブラリーでして、公式なテンプレートシステムを持っていません。
それに変わって、JS テンプレート文字列か JSX 系なレンダリングライブラリーと一緒に使うように設計されています。
ただ、コアチームはどうも今は SSR 周りの作業だけをしていてコンポーネントを作成するためのライブラリーは最近アプデートされていない様子です。
SlimJS
スター ライセンス 最新バージョン TS サポート バンドルサイズ テンプレートシステム 0.7k+ MIT 4.0.7 (2019/04) あり、decorator 含む 3.3kB 自分のライブラリー (mustache みたい) 豆知識
名前の通り SlimJS はかなり小さいライブラリーです、この記事の中では一番小さめでシリーズ全体的でもかなり小さめの方です。
一つだけ注意点ですが、プロジェクトはここ一年ぐらい前から更新されていません。☹️
次は?
今回は class ベースパターンについて色々と話しました。
この記事で紹介したライブラリーの中で気に入ったものがあれば幸いです。 ?
まだ見つけていないのならご安心ください、このシリーズで紹介するパターンはまだありますので、次の記事をぜひ読んでみてください。
ご意見やご感想もぜひコメントしてください。
- 投稿日:2020-06-29T15:28:20+09:00
[JavaScript]配列を極めたい奴はこれを見ろ
配列とは何か?
複数の値をまるで1つのデータのように取り扱うことができるようになるもの。
配列データを作成することで大量の値を1つのデータのように扱えるわけです。
よく言う例えとしては、データを入れる箱のようなものと覚えておけばいいでしょう。配列の作成方法
[]のなかにデータを入れていく。
const array = ['リンゴ', 'バナナ', 'イチゴ'];配列の要素を取得する方法
配列の0番目から数えて取得したい値の番号を指定する。
const array = ['リンゴ', 'バナナ', 'イチゴ']; console.log(array[0]); // =>リンゴ配列の要素を検索する方法
「indexOf()」は、指定した「値」が配列データに存在する場合にその場所を「インデックス番号」で取得できる機能を持っています。なのでこちらのメソッドを使用します。
const array = ['リンゴ', 'バナナ', 'イチゴ']; const result = items.indexOf('リンゴ'); console.log( result ); // =>0配列要素の追加
pushメソッドを使用することで配列の最後に要素を追加できる。
const array = ['リンゴ', 'バナナ', 'イチゴ']; array.push('メロン'); console.log(array); // =>['リンゴ', 'バナナ', 'イチゴ', 'メロン']配列の要素を削除する方法
deleteメソッドを使用することで、指定した要素を削除できます。
※削除した部分は空になります。const array = ['リンゴ', 'バナナ', 'イチゴ']; delete array[1]; console.log(array); // =>['リンゴ', '', 'イチゴ']配列から要素を削除・追加して組み替える
splice()メソッドを使用することで、配列の要素を自由に削除・追加してデータを組み替えることが出来ます。
const array = ['リンゴ', 'バナナ', 'イチゴ']; // const newArray = array(追加・削除する位置, 削除する要素の数, 追加する要素...); const newArray = array.splice(1, 2, 'メロン', 'マンゴー'); console.log(newArray); // =>['リンゴ', 'メロン', 'マンゴー']配列を連結・結合する方法
join()メソッドを使用することで、配列の各要素を連結・結合して「文字列(String)」として出力することができる。
※引数に何も指定しなければカンマ区切りになります。const items = [2020, 6, 29]; const result = items.join('/'); console.log( result ); // =>2020/6/29配列を分割する方法
slice()を使用することで、指定した配列のインデックス番号を境目に末尾まで分割して、新しい配列データを取得することができます。
※第一引数=開始位置、第二引数=終了位置 と言う指定方法も可能。const array = ['リンゴ', 'バナナ', 'イチゴ']; const result = array.slice(2); console.log(newArray); // =>['イチゴ']配列要素を抽出する方法
filter()メソッドは、任意の条件に合致する配列要素だけを抽出して新しい配列データを作成することができます。
const items = [55,20,70,80,30,15,65,85,40]; const result = items.filter( function( value ) { //50よりも小さい数値だけを抽出 return value < 50; }) console.log( result ); // =>[55, 70, 80, 65, 85]処理した要素を新しい配列で作成する方法
map()メソッドを使用することで、処理した後に新たに配列を作成してくれます。
const items = [1,2,3,4,5]; const result = items.map(function( value ) { //配列の各要素を2倍にする return value * 2; }); console.log( result ); // =>[2, 4, 6, 8, 10]配列要素を繰り返し処理する方法
forEach()メソッドを使用することで、配列データに対してのみ実行することが可能で、各要素1つずつに任意の処理を実行させることができます。
let items = [1,2,3,4,5]; let result = items.map(function( value ) { //配列の各要素を2倍にする return value * 2; }); // =>2 // =>4 // =>6 // =>8 // =>10配列をコピーする方法
concat()メソッドを使用することで、指定した配列を対象の配列に連結出来ます。
空の配列に連結させることでコピーしているような形になります。const array = ['リンゴ', 'バナナ', 'イチゴ']; const newArray = array.concat(); console.log(array); // => ['リンゴ', 'バナナ', 'イチゴ'] console.log(newArray); // => ['リンゴ', 'バナナ', 'イチゴ']配列の要素数を調べる方法
lengthメソッドを使用することで配列内の要素数を調べることが出来ます。
const array = ['リンゴ', 'バナナ', 'イチゴ']; array.length;配列をソートする方法
sort()メソッドを利用することで、効率よくソート(並び替え)を行うことができます。
※文字列の場合はそのままで良いが、数値やオブジェクトの場合は比較関数を使います。//文字列の場合 const str = ['sa', 'mu', 'ra', 'i']; str.sort(); console.log(str); // => ["i", "mu", "ra", "sa"] //数値の場合 function compareFunc(a, b) { return a - b; } var num = [5, 3, 10, 6, 55]; num.sort(compareFunc); console.log(num); // => [55, 10, 6, 5, 3] //オブジェクトの場合 const lists = [ {name: 'メロン', price: 500}, {name: 'バナナ', price: 100}, {name: 'スイカ', price: 280}, {name: 'イチゴ', price: 300} ]; lists.sort(function(a, b) { if (a.name > b.name) { return 1; } else { return -1; } }) console.log(lists); // => [{name: "イチゴ", price: 300} {name: "スイカ", price: 280} {name: "バナナ", price: 100} {name: "メロン", price: 500}]スプレッド構文
配列やオブジェクトの要素を文字通り展開する構文のこと。
const foo = [1, 2]; // 要素を追加して新しい配列を生成 const baz = [...foo, 3, 4]; // => [1, 2, 3, 4]レスト構文
複数の要素を集約して 1 つのオブジェクトにまとめることが出来ます。
関数の引数や分割代入での不特定多数の値を配列として受け取る際に利用します。const arr = [1, 2, 3, 4, 5]; const [a, b, ...c] = arr; console.log(a, b, c); // => 1 2 [3, 4, 5]まとめ
JavaScriptで使うであろう配列の知識をまとめました。
これらを使いこなせば基礎は身につくんではないかと思います!
- 投稿日:2020-06-29T15:12:44+09:00
備忘:JSONPでサーバへアクセス時に、タイムアウト検知する
備忘です
特徴
JSON P のためドメインの違うサイトからデータを取得できます。
- タイムアウト検知処理
- 一定時間、サーバから応答が無い場合を検知できる
コード
// JSONPでアクセスする関数 function accessByJSONP(url, errfunc) { var key = createUniqueStr(); console.log(" key created by AccessSuccessCheckList [" + key + "]"); var script = document.createElement("script"); script.src = url + "&key=" + key; //一意となるkeyを勝手に追加 document.head.appendChild(script); AccessSuccessCheckList[key] = url; script.onerror = function (e) { console.log(" Error on accessByJSONP"); errfunc(); }; //呼び出しタイムアウトした場合の処理 window.setTimeout(function (e) { if (accessSuccessCheck(key)) { console.log(" access Timeout key is cleared. " + key); } else { console.log(" Err access Timeout key [" + key + "]"); var url_1 = accessSuccessURL(key); console.log(" URL " + url_1); //errfunc(); } }, 30000); //タイムアウト待ち30秒 //↓関数である必要はない。。。 function accessSuccessURL(key) { return AccessSuccessCheckList[key]; } function accessSuccessCheck(key) { if (AccessSuccessCheckList[key]) { return false; } else { return true; } } function createUniqueStr(myStrong) { //let strong = 1000;//copy元 var strong = 100; if (myStrong) strong = myStrong; return ("k" + new Date().getTime().toString(16) + Math.floor(strong * Math.random()).toString(16)); } }グローバル変数で、タイムアウト待ちリストを保持
// JSONPでアクセス中のリスト // レスポンスタイムアウトをチェックする var AccessSuccessCheckList = {};コールバック関数で、タイムアウト待ちを解除する
// keyはcallbackとしてサーバから返してもらうこと function callback(data, key) { AccessSuccessCheckList[key] = null; }github
- 投稿日:2020-06-29T14:39:24+09:00
2Dのドット絵作成ツールを作ってみる
2Dドット絵作成ツール開発をしました。
参考にした資料
- 『いちばんやさしいJavascript 入門教室』 (廣瀬豪)
実施できたこと
- 任意の色を選択してドット絵を作成できる機能 (16×16)
- 削除機能
- ドット絵を画像として保存する機能
- 実際に手元にあるナノブロックの色に近いカラーパレット作成
コード
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ピクセルアート作成</title> <style> table { border-collapse: collapse; } td { width: 30px; height: 30px; border: solid 1px; } </style> <a href="index.html"><img src="ant-logo4.png" alt="街を作ろう"></a> </head> <body> <div> <table id="pixelTBL"></table> <br> <table id="paletteTBL"><tr></tr></table> </div> <br> <div> <input type="image" src="clear.png" alt="削除" onclick="clearCell()"> <input type="image" src="download.png" alt="画像保存" value="画像保存" onclick="drawCanvas()"> <canvas id="picture" width="16" height="16" style="border:solid 1px;"></canvas> </div> <script> const Matrix_Rows = 16; const Matrix_Cols = 16; let Color_Index = "black"; const pixelTBL = document.getElementById("pixelTBL"); const paletteTBL = document.getElementById("paletteTBL"); const cvs = document.getElementById("picture"); const ctx = cvs.getContext("2d"); function pixelTable() { for(let i=0; i<Matrix_Rows; i++) { let row = pixelTBL.insertRow(-1); for(let j=0; j<Matrix_Cols; j++) { let cell = row.insertCell(-1); cell.onclick = function(){ this.style.backgroundColor = Color_Index; } } } } let COL_palette = [ "#ffffff", "#000000", "#ff4500", "#1e90ff", "#f5deb3", "#a52a2a", "#fffacd", "#808080", "#800080", "#ff0000", "#32cd32", "#00ff00", "#d2691e", "#5f9ea0", "#008b8b" , "#00bfff", "#ffff00", "#8b4513", "#c71585", "#ffb6c1"]; /* 白 #ffffff 黒 #000000 オレンジレッド #ff4500 青 #1e90ff アイボリー #f5deb3 ワイン #a52a2a 黄色 #fffacd グレー #808080 紫 #800080 赤 #ff0000ß 緑 #32cd32 黄緑 #00ff00 チョコレートブラウン #d2691e 深緑1 #5f9ea0 深緑2 #008b8b 水色 #00bfff 黄色 #ffff00 こげ茶 #8b4513 ショッキングピンク #c71585 薄ピンク #ffb6c1 */ function paletteTable() { for (let j=0; j<COL_palette.length; j++) { const cell = paletteTBL.rows[0].insertCell(-1); cell.style.backgroundColor = COL_palette[j]; cell.innerHTML = "<img src='cube1.png'>"; cell.style.padding = "0px"; cell.style.border = "0px"; cell.onclick = function(){ Color_Index = this.style.backgroundColor; } } } function clearCell() { for (let i=0; i<Matrix_Rows; i++) { for (let j=0; j<Matrix_Cols; j++) { pixelTBL.rows[i].cells[j].style.backgroundColor = "white"; } } ctx.fillStyle = "white"; ctx.fillRect( 0, 0, 16, 16 ); } function drawCanvas() { for (let i=0; i<Matrix_Rows; i++) { for (let j=0; j<Matrix_Cols; j++) { let col = pixelTBL.rows[i].cells[j].style.backgroundColor; ctx.fillStyle = col; ctx.fillRect( j, i, 1, 1 ); } } } window.onload = function() { pixelTable(); paletteTable(); clearCell(); } </script> </body> </html>動き
以下が削除ボタンです。
- 投稿日:2020-06-29T14:32:37+09:00
私の学習記録
私の学習記録
4月から6月にかけて、自己学習をする機会がありました。
私はシステムエンジニアとして、自宅学習ならではの学習テーマを選びました。以下の点を踏まえるように心がけました。・プログラミング(コーディング)がプロジェクトの全てではない ・プログラミングはスキルの全部ではなく一部である ・業務の幅の拡大あるいは業務効率化のために役立つが、今の自分に抜けがあるものは何か実際に学んだのは以下のテーマです。(Github)
プログラミング "周り" のスキルに関する学習
- 優秀なエンジニアとはどんな人か
優秀なSEに必要な考え方
技術系カンファレンスにいこう
働きがいとキャリアの高め方
エンジニアのキャリアと組織内での働き方の話- エンジニアとしての「ヒューマンスキル」
ヒューマンスキル
質問力
対人力
システムエンジニアのための文章力を向上させるコツ
ビジネスメール問題集- プロジェクトマネジメント関連
これからはじめるWebプロジェクトマネジメント
プロジェクト・マネジメントのメカニズムとプロセス設計
プロセス設計- ITサービスマネジメント
ITサービスマネジメント
情報セキュリティ- エンジニアとしての経営学
エンジニアの経営学- エンジニアとしての英語との向き合い方
IT英語
英文メール- マークダウンの書き方
Markdown
プロジェクトの "流れ" に関する学習
- IT企画及び要件定義(いわゆる「上流工程」)
IT企画~要件定義
最速で身につく要件定義入門- 設計に関する学習
サクッとわかる外部設計入門
サクッとわかる内部設計入門
画面遷移図
ER図
機能定義書
テーブル設計・データベース設計- Webデザインに関する学習
Webデザイン入門
Bootstrap
Sass- ソフトウェアテストに関する学習
ソフトウェアテスト- ログ収集に関する学習
ログ収集・分析基盤構築
"コンピュータサイエンス" に関する学習
"業務自動化" に関する学習
- Excel VBAに関する学習
(ExcelVBA)基礎
(ExcelVBA)エキスパートへの道
(ExcelVBA)ファイル出力編- Google Apps Scriptに関する学習
GoogleAppsScript
"Javaが一定以上できるようになったときに合わせて身に付けたい技術" に関する学習
- Go言語に関する学習
Golang
- Dockerに関する学習
Docker&Kubernetesによる開発- Scalaに関する学習
Scala- Groovyに関する学習
Groovy
"JavaScriptフレームワーク" に関する学習
- VueJS及びFirebaseに関する学習
Vue.js学習(速習編)
Vue.js学習(詳細編)
Firebase- TypeScriptに関する学習
typescript-lesson
typescript-type
interfaceの使い方
ジェネリクスの仕組み
デコレーターの使い方
TypeScriptの応用的な使い方
健康管理アプリを作る- AngularJSに関する学習
AngularJS- ReactJSに関する学習
React
iTunes楽曲検索アプリ
"Java関連のスキルアップ" に関する学習
- アルゴリズムに関する学習
Javaアルゴリズム- コレクションフレームワークに関する学習
コレクションフレームワーク- インターフェース・ラムダ式・ストリームに関する学習
インターフェース・ラムダ式・ストリーム- 日付の扱い方及び国際化
日付の扱い方
プロパティファイルの読み込み・国際化- データベースアクセス・CSV出力
Spring BootでMySQLにアクセス・CSV出力今後は、資格取得を目指したり、これらの学習内容を線で結んで(勿論、抜けのある、身に付ける必要のある技術はまだまだ盛沢山です)大規模なアプリケーションを自分の手で実装してみる(Githubで星の数を上げられるようにしたい)といったことをしようと考えています。さらには、ITの専門職大学院で専門性をさらに極めていくということも検討しています。
- 投稿日:2020-06-29T11:33:33+09:00
「ElectronJSベースのプロジェクト」ElectronJSの機能に沿ったユーザーインターフェース(UI)の作成
高性能なキーバリューデータベースであるRedisは、ビッグデータアプリケーションに欠かせない要素となっています。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
電子テンプレートの作成
まずはElectronテンプレートの基本的なバージョンから始めてみましょう。これは、Official Electron RepositoryからフォークされたテンプレートのGitHubリンクです。試してみたい方は、以下のリンクにアクセスしてコードをクローンしてみてください: https://github.com/saichandu415/electron-quick-start
以下は、コードをクローンした後に表示されるファイル構造です。
各ファイルが何を担当しているのかを簡単に説明します。
.gitignore
多くの人がこのファイルを当たり前のように受け取っていますが、このファイルの本当の目的は、コードをコミット / git リポジトリにプッシュしている間、ファイルを無視することです。これにより、不要なファイルがリポジトリにプッシュされないようにします。LICENSE.md
これは、オープンソースとしてリリースするコードのライセンス構造の概要を示します。すべてのライセンスをチェックして、自分に合ったものを選ぶようにしましょう。README.md
これはリポジトリを開いたときに表示されるフロントページです。マークダウンはドキュメントを書くための特別なフォーマットで、内容を読みやすくするための十分な書式オプションがあり、非常に簡単です。Index.html
プレゼンテーションのロジックを保存するファイルです。HTML と CSS を使用したすべての UI デザインはここにあります。あなたが従うUIアーキテクチャに基づいて、異なるファイルやアプリケーション全体のための1つのHTMLページを管理することが起こるかもしれません。また、タグが動作するようにするためには、いくつかの変更が必要です。main.js
これはElectronアプリケーションの心臓部であり、IPCMainプロセスからIPCRendererプロセスへの完全な通信で構成されています。このファイルがどのように動作するかについては、コードを説明するときに詳しく説明します。このファイルはすべてのプロセス間通信を担当しています。Package.json
このファイルは、Node.jsの経験がある人には馴染みのあるファイルでしょう。これと同等のものは、pom.xmlのようなjavaでも見つけることができます。Renderer.js
これはプロセス間通信のレンダラー部分です。複数のrenderer.jsを持つことができますが、イベントの連鎖を維持したり追跡したりするのが非常に難しいので、複数のmain.jsを持つことはお勧めできません。ElectronJSを使用したデスクトップ請求書発行アプリケーション
ElectronJSをベースに請求書発行アプリケーションを作成しています。アプリケーションのUIを作成することから始めます。
UIとUX
私はBoomerang UI Kit (https://github.com/webpixels/boomerang-ui-kit)を選びました。これは、レスポンシブで美しいユーザーインターフェースを簡単に開発する方法を提供してくれるbootstrapフレームワークの上に作成されています。このキットには、あらかじめ定義されたコードスニペットがたくさん用意されているので、簡単に始めることができます。今日ご紹介するコードスニペットは、以下のレポから取得したものです。
https://github.com/saichandu415/Electron-Invoice-ApsaraDB-MongoDB
コードをクローンすると、"Electron_Invoice "と "Invoice_Backend "があるはずです。Electron_Invoiceは、この記事で説明しているフロントエンドのコード全体で構成されています。
<link href="https://fonts.googleapis.com/css?family=Nunito:400,600,700,800|Roboto:400,500,700" rel="stylesheet"> <!-- Theme CSS --> <link type="text/css" href="./assets/css/theme.css" rel="stylesheet"> <!-- Font Awesome --> <link href='http://fonts.googleapis.com/css?family=Grand+Hotel' rel='stylesheet' type='text/css'>基本的なBootstrapフレームワークの要素をスタイリングするために必要なCSSの構成要素をすべて含んだ「theme.css」をリンクしています。CSS のリンクは、Web 開発で行うように、簡単にできます。
bodyタグの最後に移動すると、いくつかのスクリプトがあります。そのうちの一つが<script> // You can also require other files to run in this process require('./renderer.js'); </script>ここではrenderer.jsを注入しています。renderer.jsでは、完全なDOMとNPM(Node Package Manager)モジュールにアクセスできます。
<script> if (typeof module === 'object') { window.module = module; module = undefined; } </script> <!-- Start Script dependencies --> <!-- Core --> <script src="./assets/vendor/jquery/jquery.min.js"></script> <script src="./assets/vendor/popper/popper.min.js"></script> <script src="./assets/js/bootstrap/bootstrap.min.js"></script> <!-- FontAwesome 5 --> <script src="./assets/vendor/fontawesome/js/fontawesome-all.min.js" defer></script> <!-- Page plugins --> <script src="./assets/vendor/bootstrap-select/js/bootstrap-select.min.js"></script> <script src="./assets/vendor/bootstrap-tagsinput/bootstrap-tagsinput.min.js"></script> <script src="./assets/vendor/input-mask/input-mask.min.js"></script> <script src="./assets/vendor/nouislider/js/nouislider.min.js"></script> <script src="./assets/vendor/textarea-autosize/textarea-autosize.min.js"></script> <!-- Theme JS --> <script src="./assets/js/theme.js"></script> <!-- Chart JS Plugin --> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script> <!-- Mustache JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.js"></script> <!-- Ending Script dependencies --> <script> if (window.module) module = window.module; </script>これは、依存関係が適切に統合されるのを妨げるので、理解しておく必要がある最も重要なコードです。.jsの依存関係を追加し始める前に、「スクリプトの依存関係を開始する」というコメントの上にスクリプトを追加する必要があることに注意してください。そうしないと、JQueryなどのモジュールや他のライブラリが利用できなくなります。
Electronではすべてのライブラリがモジュールとして利用できるようになっているはずです。Starting & Ending script dependencies」のスクリプトの間にインポートを挟むことで 全てのライブラリが正常にインポートされ、利用可能になります。
使用しているインポートは以下の通りです。
Jquery, Popper, Bootstrap - Boomerang UIの相互依存性のためのもので、UIキットはその上に構築されています。
Font Awesome - フォントとして統合された完全にスケーラブルなアイコン。
Page plugins - HTMLの一部として使用している既存の要素に追加機能を作成するために使用します。
Theme - このJavaScriptコードは、アニメーション、補間などを担当するtheme.cssにバインドされています。
ChartJS - 分析結果をグラフィカルに表示するためにChartJSを使用しています。このライブラリはオープンソースであり、カスタマイズが容易な多くのチャートを提供しています。
MustacheJS - 実行時に簡単に動的にhtmlを作成するためにMustacheを使用しています。このライブラリは動的なhtmlを生成し、リストを使って値を置き換える作業を大幅に簡略化してくれます。
他のコードスニペットについて説明する前に、最終製品がどのようになるか見てみましょう。
販売の見える化
現在デザインしている2つの画面です。
- New Invoice これは、ユーザーが新しい請求書を作成するためのフォームを持っているウィンドウです。
- Dashboard ダッシュボードは、ユーザーの売上分析、総注文数、総売上高をグラフィカルな方法で見ることができます。
上記のUI部分は、以下のコードで実現しています。
<div class='navigationBar'> <ul class="nav nav-pills nav-fill flex-column flex-sm-row" id="myTab" role="tablist"> <li class="nav-item"> <a class="nav-link mb-sm-3 active" id="home-tab" data-toggle="tab" href="#newInvoice" role="tab" aria-controls="home" aria-selected="true">New Invoice</a> </li> <li class="nav-item"> <a class="nav-link mb-sm-3" id="profile-tab" data-toggle="tab" href="#dashboard" role="tab" aria-controls="profile" aria-selected="false">DashBoard</a> </li> </ul> <div class="tab-content" id="myTabContent"> <div class="tab-pane fade show active" id="newInvoice" role="tabpanel" aria-labelledby="home-tab"> ** Content of Home ** </div> <div class="tab-pane fade" id="dashboard" role="tabpanel" aria-labelledby="profile-tab"> ** Content of DashBoard ** </div>我々は、単一のページのUIアーキテクチャに従っており、1つのhtmlファイルで一度に全体のページを作成し、我々はそれが必要であることを示しています。
myTab "には新規請求書とダッシュボードのタブの完全なリストが表示されています。HTMLタグに適用されているクラスは無視しても良いかもしれません。
他の
の "tab-pane "は "New Invoice "と "Dashboard "に関連した内容を保持しています。<div class="input-group-prepend"> <span class="input-group-text"> <i class="fas fa-user"></i> </span> </div> <input type="text" class="form-control" id="customerName" placeholder="Customer Name">これはフォームで使用したコンポーネントの一つで、アイコンにはタグが作成され、先ほど追加したフォント awesome で利用可能になります。
インプット
<table class="table"> <thead class="thead-dark"> <tr> <th scope="col" class='centerme'>Item Details</th> <th scope="col" class='centerme'>Qty</th> <th scope="col" class='centerme'>Rate</th> <th scope="col" class='centerme'>Amount</th> <th scope="col"></th> </tr> </thead> <tbody> <tr> <td> <div class="form-group"> <input type="text" id="itemDetails" class="form-control border-0" placeholder="Item Details"> </div> </td> <td> <div class="form-group"> <input type="number" id="quantity" class="form-control border-0" placeholder="Quantity"> </div> </td> <td> <div class="form-group"> <input type="number" id="rate" class="form-control border-0" placeholder="Rate"> </div> </td> <td> <div class="form-group"> <input type="number" id="amount" class="form-control border-0" placeholder="Amount" readonly> </div> </td> <td class=''> <button type="button" onclick="addItemCard()" class="btn btn-success btn-icon-only rounded-circle"> <span class="btn-inner--icon"> <i class="fas fa-check"></i> </span> </button> </td> </tr> </tbody> </table>この特定のコードは、テーブルヘッダーとそれに対するボタンと一緒に入力のセットを作成します。
緑色のボタンは、項目の詳細を提供するエントリを作成し、次のエントリを見ることができます。
アイテム詳細のカード入力は、以下のコードで作成されます。
<!-- Template here Starts --> <script id="tutorial-template" type="text/template"> <tr class="bg-white"> <th scope="row"> <div class="media align-items-center"> <span class="avatar avatar-lg bg-pink mr-3">{{itemShortName}}</span> <div class="media-body"> <h6 class="h5 font-weight-normal mb-0">{{ItemName}}</h6> <span class="font-weight-normal text-muted">Quantity : <b>{{qty}}</b> </span> </div> </div> </th> <td>Rate/pc : {{rate}}</td> <td>Amount : {{amount}}</td> </tr> <tr class="table-divider"></tr> </script> <!-- Template here ends -->このテンプレートは動的に生成され、以下の関数を使用して実行時に以下の表に追加されます。
function addItemCard() { var data = {}; data.itemShortName = ($("#itemDetails").val()).substring(0, 2).toUpperCase(); data.ItemName = $("#itemDetails").val(); data.qty = $("#quantity").val(); data.rate = $("#rate").val(); data.amount = $("#amount").val(); if (!itemsObj.customerName) { itemsObj.customerName = $("#customerName").val(); } if (!itemsObj.invoiceNumber) { itemsObj.invoiceNumber = $("#invoiceNumber").val(); } if (!itemsObj.invoiceDate) { itemsObj.invoiceDate = $("#invoiceDate").val(); } if (!itemsObj.dueDate) { itemsObj.dueDate = $("#dueDate").val(); } itemsObj.itemsData.push(data); var template = $("#tutorial-template").html(); var html = Mustache.render(template, data); $("#cardsList").append(html); }クリックすると、"addItemCard() "が呼び出され、データがItemsObjにプッシュされます(後にサーバーにデータをプッシュするために使用されます)。また、Mustacheライブラリを使用してカードテンプレートを作成し、テーブルに追加します。
<table class="table table-hover table-cards align-items-center"> <tbody id="cardsList"> <!-- Dynamically generated template goes here --> </tbody> </table>Reset
「リセット」ボタンを押すと、フォーム内のデータがリセットされ、新しい請求書フォームのようになります。Submit
これは、データベースへのデータの挿入の世話をするAPIコールを行うもう一つの機能です。function submitData() { console.log('submit Clicked'); var itemsArr = itemsObj.itemsData; var totalAmount = 0; console.log(itemsArr); for (var i = 0; i < itemsArr.length; i++) { totalAmount = totalAmount + parseFloat(itemsArr[i].amount); } console.log(totalAmount); itemsObj.totalAmount = totalAmount; console.log(JSON.stringify(itemsObj)); $.ajax({ type: 'POST', data: JSON.stringify(itemsObj), contentType: 'application/json', url: 'http://149.129.130.26:443/data/invoice', success: function (data) { console.log('success'); console.log(JSON.stringify(data)); $("#modal_5").modal("show"); } }); }これは、ECSインスタンスで動作している上記URIへのPOSTコールで、MongoDBインスタンスへのデータの挿入を担当しています。バックエンドについては近日中に詳しく説明します。
ダッシュボード
ダッシュボードは以下のコンポーネントで構成されています。上記のコンポーネントは、以下のコードで作成しています。
<div class="card"> <div class="card-header"> <div class="row align-items-center"> <div class="col-8"> <h4 class="heading h5 mb-0">Today Orders</h4> </div> <div class="col-4"> <div class="card-icon-actions text-right"> <a href="#" class="favorite" data-toggle="tooltip" data-original-title="Save as favorite"> <i class="fas fa-star"></i> </a> <a href="#" class="love active" data-toggle="tooltip" data-original-title="Love this"> <i class="fas fa-heart"></i> </a> </div> </div> </div> </div> <div class="card-body"> <p class="card-text" id="saleCount">Count : 0</p> </div> <div class="card-footer"> <div class="row align-items-center"> <div class="col-6"> <a href="#" class="btn btn-sm btn-primary" onclick="fetchAndLoadValues()">Refresh Now</a> </div> <div class="col-6 text-right"> <span class="text-muted" id="saleCountUpdateValue">2 hrs ago</span> </div> </div> </div>上記のコードでは、MongoDBから最近更新された時間とともに、アイコン、ヘッダー、ボタンを持つカードを作成しています。
また、売上をそれぞれの日付でグラフィカルに表示するために chart.js を使用しています。スクリプトセクションの
タグを参考にしてください。非常にわかりやすいはずです。
データを取得して分析するために使用した機能については、コード内でfunction fetchAndLoadValues() { var ajaxResult = {}; $.ajax({ url: "http://149.129.130.26:443/data/dashboard", success: function (result) { console.log(result); ajaxResult = result; dataVariable.data.labels = ajaxResult.datesArr; dataVariable.data.datasets[0].data = ajaxResult.salesArr; $('#saleValueUpdateTime').text(timeNow()); $('#saleCountUpdateValue').text(timeNow()); $('#saleValue').text("sale Value : $ " + (ajaxResult.salesArr)[0]); $('#saleCount').text("Count : " + (ajaxResult.ordersArr)[0]); loadLineChart(); } });fetchAndLoadValues()関数は、ECSインスタンス上のAPIにAJAX(Asyncronous JavascriptとXML)呼び出しを行い、値を取得し、以下の操作を行います。
- セールバリューリフレッシュタイムの値を更新
- 総受注件数更新時間の値を更新
- 現在の総売却額
- その日の全注文の合計注文数
- グラフ用の総売却額と過去5日間の日付の配列の作成
次は何をするのか?
今回は、このアプリケーションで使用するバックエンドサービスをどのように作成したのか、また、アプリケーションでどのような操作ができるのかを説明します。もしまだElectronの利点について読んでいないのであれば、このチュートリアルシリーズの前回の記事をぜひチェックしてみてください。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-06-29T11:19:21+09:00
Power Apps component framework で使用できる CDS データ型と、対応する JavaScript/TypeScript の型
目的
CDS の TwoOptions などのフィールドタイプに対応する JS/TS の型をまとめたかった。
Power App component framework で使用できる CDS データ型
マニフェスト定義と生成される JS/TS データ型
前提
$ npm list --depth=0 pcf-project@1.0.0 C:\Users\... +-- @types/node@10.17.6 +-- @types/powerapps-component-framework@1.2.1 +-- pcf-scripts@1.0.6 `-- pcf-start@1.0.6挙動
ControlManifest.Input.xml... <property name="newAttr1" display-name-key="newAttr1_Display_Key" description-key="newAttr1_Desc_Key" of-type="Currency" usage="bound" required="true" /> <property name="newAttr2" display-name-key="newAttr2_Display_Key" description-key="newAttr2_Desc_Key" of-type="DateAndTime.DateAndTime" usage="bound" required="true" /> <property name="newAttr3" display-name-key="newAttr3_Display_Key" description-key="newAttr3_Desc_Key" of-type="DateAndTime.DateOnly" usage="bound" required="true" /> <property name="newAttr4" display-name-key="newAttr4_Display_Key" description-key="newAttr4_Desc_Key" of-type="Decimal" usage="bound" required="true" /> <property name="newAttr5" display-name-key="newAttr5_Display_Key" description-key="newAttr5_Desc_Key" of-type="Enum" usage="bound" required="true" /> <property name="newAttr6" display-name-key="newAttr6_Display_Key" description-key="newAttr6_Desc_Key" of-type="FP" usage="bound" required="true" /> <property name="newAttr7" display-name-key="newAttr7_Display_Key" description-key="newAttr7_Desc_Key" of-type="Multiple" usage="bound" required="true" /> <property name="newAttr8" display-name-key="newAttr8_Display_Key" description-key="newAttr8_Desc_Key" of-type="OptionSet" usage="bound" required="true" /> <property name="newAttr9" display-name-key="newAttr9_Display_Key" description-key="newAttr9_Desc_Key" of-type="SingleLine.Email" usage="bound" required="true" /> <property name="newAttr10" display-name-key="newAttr10_Display_Key" description-key="newAttr10_Desc_Key" of-type="SingleLine.Phone" usage="bound" required="true" /> <property name="newAttr11" display-name-key="newAttr11_Display_Key" description-key="newAttr11_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" /> <property name="newAttr12" display-name-key="newAttr12_Display_Key" description-key="newAttr12_Desc_Key" of-type="SingleLine.TextArea" usage="bound" required="true" /> <property name="newAttr13" display-name-key="newAttr13_Display_Key" description-key="newAttr13_Desc_Key" of-type="SingleLine.Ticker" usage="bound" required="true" /> <property name="newAttr14" display-name-key="newAttr14_Display_Key" description-key="newAttr14_Desc_Key" of-type="SingleLine.URL" usage="bound" required="true" /> <property name="newAttr15" display-name-key="newAttr15_Display_Key" description-key="newAttr15_Desc_Key" of-type="TwoOptions" usage="bound" required="true" /> <property name="newAttr16" display-name-key="newAttr16_Display_Key" description-key="newAttr16_Desc_Key" of-type="Whole.None" usage="bound" required="true" /> ...ManifestTypes.d.tsexport interface IOutputs { newAttr1?: number; newAttr2?: Date; newAttr3?: Date; newAttr4?: number; newAttr5?: string; newAttr6?: number; newAttr7?: string; newAttr8?: number; newAttr9?: string; newAttr10?: string; newAttr11?: string; newAttr12?: string; newAttr13?: string; newAttr14?: string; newAttr15?: boolean; newAttr16?: number; }
- 投稿日:2020-06-29T10:47:16+09:00
ElectronJSの入門編
ElectronJSは、クロスプラットフォームのアプリケーション開発を支援します。アプリ開発に特別なスキルは必要ありません。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
ElectronJSの話をする前に、Desktopアプリケーションがどのように作られているのかを見てみましょう。また、現在のDesktopアプリケーションの開発方法の問題点を分析します。その上で、ElectronJSを使ってフル機能の請求書アプリケーションのバックエンドを作る方法を探っていきます。
デスクトップでのアプリ開発
Windows
ウィンドウズベースのアプリはどのように作られているのか、という話をしてみましょう。ほとんどの人はC++、C# (C Sharp)、VB (Visual Basic)を使って開発しています。これには、Windowsストア、Windows電話、WindowsデスクトップアプリケーションのようなすべてのWindowsエコシステム全体でアプリケーションを作成するために、いくつかのオープンソースの.NETライブラリを使用/使用することが含まれている場合があります。MacOS
Macでは、アプリケーションを作成するために使用する技術を明確に抽象化しています。「Cocoa」レイヤーには、アプリケーションのユーザーインターフェイスを作成するために必要なすべての技術が含まれています。「メディア」レイヤーには、2Dおよび3Dアニメーション、写真やビデオの編集を含むメディア操作に必要なすべてのツールと技術が含まれています。また、"Core Services "エリアでは、低レベルのネットワーク操作、文字列やデータの操作を行うことができます。"Core OS "は、すべてのCPUやGPUが高性能なタスクを実行するためのすべてのAPIを公開/提供しています。"Kernel & Device Drivers "は、ファイルシステム、ネットワーク、セキュリティ、プロセス間通信、プログラミング言語、デバイスドライバ、カーネルの拡張機能を提供します。このレベルでは、Machカーネル環境、デバイスドライバ、BSDライブラリ関数(libSystem)、その他の低レベルコンポーネントも公開しています。
ご覧のように、MacOS上でアプリを開発するためには、技術やフレームワークについて学ぶ必要があることがたくさんあります。私たちが使用しているWindowsの技術スタックとは類似性がないことに注意してください。
Linux/Ubuntu
Linuxは開発者にとって最もポピュラーなOSの一つなので、自社の製品がLinuxユーザーに届くようにしたいと考えています。今回はUbuntuとLinuxの両方について一緒にお話しします。Linuxは一般的にアプリケーションのユーザーインターフェースにPythonなどのライブラリを使用します。Linuxの場合、3Dや2Dレンダリングのようなサービスが必要な場合は、OpenGLが必要になります。Linuxカーネルは、低レベルの機能を公開しています。Web技術とは何か?
Web開発は今最もホットな話題の一つであり、Web開発者の人口がかなり多いのは当然のことです。また、素晴らしいUI/UXを持つアプリをデザインし、作成することができるウェブ開発者もたくさんいます。これらの技術は標準的なものであり、世界のトップ企業が制作プロジェクトで使用しているため、見つけるのは簡単です。業界の大手技術者に支えられ、他のチームを支援するフレームワークは無数に存在します。
テンポの速い世界では、企業はテック業界の急速で絶え間ない変化に対応する必要があります。つまり、製品をより早くプロトタイプ化し、すべてのプラットフォームで利用できるようにし、単一のコードベース、より多くの機能のサポート、優れたUI/UXを提供する必要があるということです。従来の方法だけに頼っていると、これを実現するためには多くの資本と時間が必要になります。その代わり、Web技術を利用すれば、多くのオープンソースフレームワークを再利用し、効率的に課題を解決することができます。
ElectronJSとは何か?
ElectronJSは、既存のWeb技術を利用したクロスプラットフォームアプリケーションの開発を支援します。ほとんどの場合、特別なスキルは必要ありません。Web開発者の方で、Electronを使ったアプリ開発に興味がある方は、このシリーズの以下のチュートリアルをチェックしてみてください。
ここまで説明したすべてのプラットフォームでアプリケーションを利用できるようにするには、異なる技術を使ってアプリをデプロイする必要があります。これは非常にマニュアル化されており、時間がかかります。さて、ElectronJSの話をすれば、これがJavaScriptベースのフレームワークであることは明らかです。すべてのプラットフォームがWeb技術をサポートしているため、ElectronJSはクロスプラットフォームアプリの開発を容易にします。ElectronJSを使用している一般的なアプリケーションには、Visual Studio Code、Slack、Atom Editorなどがあります。
ElectronJSの機能
セキュリティ
作成するアプリケーションはデスクトップアプリケーションであり、データはシステムのローカルに留まるため、既存のアプリケーションをElectronJSに移行する際にはあまり考える必要はありません。そのため、データのセキュリティを確保することができます。クラウドにデータを保存する必要がある場合は、事前にクラウドネットワークに十分なセキュリティ機能があるかどうかを確認して、不意を突かれないようにしましょう。低レベルのアクセシビリティ
また、始める前に、デスクトップアプリケーションに提供している機能がすべてElectronJSでも利用できるかどうかを確認する必要があります。私の経験では、ElectronJSはキーボードショートカットなどの拡張インタラクティブ機能をアプリケーションに搭載するのに十分なコントロールを提供しています。また、ハードウェアやオペレーションシステムのコンポーネントへの低レベルのアクセシビリティも提供しています。ハードウェアのアクセシビリティ
開発者は、JavaScript/Pluginを介して公開されているすべてのハードウェアレベルのアクセスAPIに完全にアクセスすることができます。Electron JSに移行したい場合は、この機能に妥協する必要はありません。パフォーマンス
ElectronJSはこの点で成功しています。開発中に適切な注意を払っていれば(必要なものだけをロードする)、ネイティブアプリケーションと比較して、ElectronJSはパフォーマンスの面で大きな向上を示すことができます。ElectronJSはすべての主要なプラットフォームに対応した単一のコードベースを持つことで、多くの時間を節約し、遊びや開発のオプションを提供します。これらはアプリケーションのネイティブ開発を扱う際の大きな問題ですが、これらはすべてElectronJSによって効果的に解決されています。しかし、多くの人がElectronJSは非常にメモリを消費すると言います。私もこの意見には賛成ですが、適切な注意を払わずにアプリケーションを開発する場合に限ります。
コードとアプリの管理
プロダクトオーナーとして、プラットフォームごとに異なるチームを維持する必要はなく、異なるチームに要件を再度説明する手間から解放されます。また、製品がプラットフォーム間で同じ機能を持っていることを確認するための監査作業も軽減されます。開発者として、異なるコードベースについて心配する必要はありません。どのプラットフォームでもバグが発生した場合は、コードベースで修正することができます。バグが他のプラットフォームに現れることはありません。しかし、それでもOSレベルの機能には目を光らせておく必要があります。
再利用性
単一のコードベースを使用しているので、ウェブアプリケーションとデスクトップアプリケーションの両方に使用できることを意味します。ある意味では、「一度コードを書き、どこにでも配布する」ので、異なるプラットフォーム間でもベースコードを再利用することができます。本番環境
有名なフレームワークを使えば使うほど、より多くのサポートを得ることができます。これにより、再利用できるオープンソースのライブラリが増え、生産までの時間が短縮されますが、より多くの機能を備えています。デプロイ/ビルド
これはElectronJSの興味深い側面の一つです。Electron-packagerモジュールというものがあり、これはコードベース全体をそれぞれのパッケージにバンドルするのに役立ちます。Electronはメモリを大量に消費するという議論がありますが、これを避けるためには開発中に少し注意が必要です。UI / UX
ウェブ技術を使えば、すべてのユーザーに快適なユーザーインターフェース(UI)とユーザーエクスペリエンス(UX)を提供する複数の技術を利用することができます。また、異なるプラットフォーム上のすべてのユーザーに同じ体験を提供していることを確認することができます。コストと時間
私たちが使用している技術スタックでは、より少ないコストで良い結果を出すことができる開発者がたくさんいるので、開発時間とコストを大幅に節約することができます。単一のコードベースを使用することで多くの時間を節約することができ、どんな開発者でも何でも取り組むことができます。エレクトロンの歴史
どんな技術の話をするときも、その背景にある歴史について少し話をする必要があります。ここでは、Electronの歴史を簡単に見てみましょう。
Electronは2013年7月15日にCheng Zhao氏によって設立されたクロスプラットフォームのテキストエディタAtomの開発中に設立されました。C++、JavaScript、Objective C、Pythonをコアにしたオープンソースで、GitHubで開発・サポートされています。クロスプラットフォームでのAtom作成を容易にすることを目的としています。
Electron JSへのディープダイブ
典型的なElectronJSアプリケーションは、Chromiumエンジンの上にHTML、CSS、JavaScript、Node JSを使用して開発します。Chromiumという言葉に怯えてはいけません。ブラウザやクライアントサイドのアプリケーションを開発しているウェブ開発者であれば、Chromiumについて学ぶ必要はありません。多くのネイティブ機能にアクセスしているのであれば、Chromiumエンジン(Chromium)について詳しく知る必要があります。
また、Next.js、Vue.js、Angular 5など、基本的な設定の上に他のフレームワークを使うこともできます。こうすることで、アプリケーションをより事前に定義してモジュール化しておくことで、デバッグや理解が容易になり、開発が楽になります。また、ユニットテストケースを作成して、変更が既存の機能に影響を与えていないことを確認することもできます。
エレクトロンのハイレベルなアーキテクチャとユースケース
電子がコアレベルでどのように働くかを示す簡単なユースケース図を作成しました。
それでは、ElectronJSの動作を見てみましょう。
1、クライアントは、通常、Windows/MacOS/LinuxベースのUbuntuなどのプラットフォームからElectronJSアプリケーションを起動します。
2、アプリケーションは、メインプロセスを使用してウィンドウにリクエストを行います。
a、メインプロセス。これは、リクエストの送受信、異なるウィンドウ間のデータ転送を担当しています。ウィンドウの作成と破壊のようなすべてのメモリ管理はここで処理されます。
3、メインプロセスはrenderer.js(Rendererプロセス)と一緒にウィンドウの起動要求を行います。
a、Rendererプロセス。ウィンドウ内の全てのhtmlで参照する追加のjsファイルです。Renderer Processで完全なDOMにアクセスすることができます。HTMLの中で使用することもできますが、Renderer Processを使用した方がよりクリーンな方法です。アプリケーションが成長するにつれて、このアプローチを評価するようになるでしょう。
4、サインインウィンドウが起動され、メインプロセスにアタッチされ、操作が可能になります。
5、ユーザーがログイン情報を入力した後、レンダラプロセスでボタンクリックを処理し、製品ウィンドウを起動するためのメインプロセスへの要求を行います。
6、メインプロセスは、イベントリスナーを介してそれぞれの情報を受信し、レンダラプロセスを使用して要求に基づいて製品ウィンドウを起動します。
7、このループは、完全なユースケースが解決されるまで、アプリケーション全体のために続けられます。
もしまだわからない場合は、心配する必要はありません。以下のチュートリアルでは、ElectronJSを使用して作成したライブアプリケーションで、バックエンドデータベースをAlibaba Cloud ApsaraDB for MongoDBとした場合のコンセプトを説明します。
バックエンドとしてのMongo DBのためのアリババクラウドApsaraDB
デスクトップアプリケーションは、システムクラッシュや自然災害の際にデータを失う可能性があります。貴重なデータを保存するために、クラウド上に配置されたバックエンドとプロジェクトを接続します。保存されたデータに対してデータ分析を行う予定です。
MongoDBにAlibaba Cloud ApsaraDBを選んだ理由は、シンプルで低コスト、高可用性にあります。この人気のサービスにセキュアに接続して、どのように活用していくのか、私の体験談を交えてデモを行います。
次のチュートリアルは?
次回のチュートリアルでは、ElectronJSに初めて触れる方のために、多くの概念を明確にするシンプルな2部構成のステップバイステップのチュートリアルを予定しています。
ipcRenderer、ipcMain、印刷機能などの様々なコンポーネントを使って、どのようにしてElectronJSアプリをゼロから作成するのかを詳しく見ていきます。また、生産や配布を目的としたElectronアプリのパッケージ化やビルド方法についても探ります。チュートリアルの最後には、インタラクティブなチャートを作成したり、オープンソースのライブラリを使用して、素晴らしいUIをすぐに作成できるようになるはずです。
新しい技術を使うときはいつも、いくつかの障害に遭遇する可能性が高いです。だからこそ、私が開発中に直面したいくつかの落とし穴について、多くの時間を節約できるヒントやコツをお伝えします。
次に、ExpressJSノードモジュールを使用してNodeJSベースのバックエンドサービスを作成し、ECSインスタンスでホストします。これをElectron Appがアクセスできるように公開します。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ
- 投稿日:2020-06-29T10:31:32+09:00
Nuxt.js のライフサイクルを踏まえてトラッキングタグ埋め込みを実装する
Web サイトのエンハンスやマーケティングといった目的でユーザー分析や広告表示用のトラッキングタグを埋め込むことは開発の現場でよくあると思います。
Google Tag Manager などのツールを利用すれば比較的簡単に設定できますが、ユーザー ID など Web アプリケーション固有のデータをタグと併せて設定したい場合など、アプリケーション側にタグ埋め込み用のコードを書かなければならない場面もあります。Nuxt.js のユニバーサルモードにおけるライフサイクル処理には幾つか種類があり、実行タイミングおよび SSR/CSR の実行有無がそれぞれ異なります。
これらを踏まえ、タグやカスタム変数を設定する際にどのライフサイクル処理を選択すれば良いのか整理します。
TL;DR
- クライアントサイドで一度だけ実行したい処理(タグ埋め込みなど)は plugins を利用する。
- ページ遷移ごとに実行したい処理(ページ遷移時の状態を表す変数の設定など)は Vue Router を利用する。
シチュエーション
以下のようなタグやカスタム変数に関する処理をまとめた composition を適宜 import して利用するようなシチュエーションを想定します。
コードは TypeScript で記述していますが、本記事の要点に関しては JavaScript の場合と特に大きな相違はありません。compositions/sample-tag.tsexport function useSampleTag() { return { addTag() { // タグの埋め込み }, addCustomValue() { // カスタム変数の設定 } } }タグの埋め込み
タグの埋め込みは直アクセスされたタイミングで行い、 nuxt-link での遷移時には実行したくないケースが多いと思います。
このような用途では直アクセス時のみ実行される plugins が利用できます。plugins/sample-tag.tsimport { useSampleTag } from '~/compositions/sample-tag' export default () => { const st = useSampleTag() st.addTag() }カスタム変数の設定
ページ遷移ごとにログイン状態やユーザー ID などの値を設定したい場合は Vue Router の
afterEach()
が利用できます。plugins/router.tsimport { useSampleTag } from '~/compositions/sample-tag' export default () => { ctx.app.router!.afterEach((to, from) => { if (process.client) { const st = useSampleTag() st.addCustomValue() } }) }middleware を利用する場合
middleware を用いても同様のコードで似たような処理は実現できます。
ただしユニバーサルモードでは直アクセス時に SSR でしか処理されないため、 window オブジェクトの利用といった CSR が前提となる処理が失敗する可能性があります。
特別な理由がない限りは Vue Router を利用するのが無難だと思います。Vuex の Store を利用する場合
Vuex の Store から getter で値を取得したい場合、
addCustomValue()
の引数に Store を取るようにして内部で getter を呼び出す作りにしても良いかもしれません。compositions/sample-tag.tsimport { Store } from 'vuex/types' import { AuthenticationState } from '~/store/authentication' export function useSampleTag() { return { addTag() { // タグの埋め込み }, addCustomValue(store: Store<AuthenticationState>) { // カスタム変数の設定 } } }plugins/router.tsimport { Context } from '@nuxt/types' import { useSampleTag } from '~/compositions/sample-tag' export default (ctx: Context) => { ctx.app.router!.afterEach((to, from) => { if (process.client) { const st = useSampleTag() st.addCustomValue(ctx.store) } }) }
- 投稿日:2020-06-29T10:15:18+09:00
React hooksを基礎から理解する (useCallback編) と React.memo
React hooksとは
React 16.8 で追加された新機能です。
クラスを書かなくても、state
などのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。
- React hooksを基礎から理解する (useState編)
- React hooksを基礎から理解する (useEffect編)
- React hooksを基礎から理解する (useContext編)
- React hooksを基礎から理解する (useReducer編)
- React hooksを基礎から理解する (useCallback編) 今ここ
- React hooksを基礎から理解する (useMemo編)
- React hooksを基礎から理解する (useRef編)
useCallbackとは
useCallbackはパフォーマンス向上のためのフックで、メモ化したコールバック関数を返します。
useEffectと同じように、依存配列(=[deps] コールバック関数が依存している要素が格納された配列)の要素のいずれかが変化した場合のみ、メモ化した値を再計算します。
メモ化とは
メモ化とは同じ結果を返す処理について、初回のみ処理を実行記録しておき、値が必要となった2回目以降は、前回の処理結果を計算することなく呼び出し値を得られるようにすることです。
イベントハンドラーのようなcallback関数をメモ化し、不要に生成される関数インスタンスの作成を抑制、再描画を減らすことにより、都度計算しなくて良くなることからパフォーマンスを向上が期待できます。
基本形
useCallback(callbackFunction, [deps]);sampleFuncは、再レンダーされる度に新しく作られますが、a,bが変わらない限り、作り直す必要はありません。
const sampleFunc = () => {doSomething(a, b)}usecallbackを使えば、依存配列の要素a,bのいずれかが変化した場合のみ、以前作ってメモ化したsampleFuncの値を再計算します。一方で全て前回と同じであれば、前回のsampleFuncを再利用します。
const sampleFunc = useCallback( () => {doSomething(a, b)}, [a, b] );再レンダーによるコストについて検証してみる
Text,Count,Buttonコンポーネントを子に持つ親コンポーネントCounterコンポーネントを作成しました。
testVol1.jsximport React, {useState} from 'react' //Titleコンポーネント(子) const Title = () => { console.log('Title component') return ( <h2>useCallBackTest vol.1</h2> ) } //Buttonコンポーネント(子) const Button = ({handleClick,value}) => { console.log('Button child component', value) return <button type="button" onClick={handleClick}>{value}</button> } //Countコンポーネント(子) const Count = ({text, countState}) => { console.log('Count child component', text) return <p>{text}:{countState}</p> } //Counterコンポーネント(親) const Counter = () => { const [firstCountState, setFirstCountState] = useState(0) const [secondCountState, setSecondCountState] = useState(10) //+ 1 ボタンのstateセット用関数 const incrementFirstCounter = () => setFirstCountState(firstCountState + 1) //+ 10 ボタンのstateセット用関数 const incrementSecondCounter = () => setSecondCountState(secondCountState + 10) //子コンポーネントを呼び出す return ( <> <Title/> <Count text="+ 1 ボタン" countState={firstCountState}/> <Count text="+ 10 ボタン" countState={secondCountState}/> <Button handleClick={incrementFirstCounter} value={'+1 ボタン'}/> <Button handleClick={incrementSecondCounter} value={'+10 ボタン'}/> </> ) } export default Counterconsole.logを実行しているだけですが、すべてのコンポーネントが再レンダーされています。この部分で高コストな処理を行っていれば、その分だけパフォーマンスに悪影響を与えることになりますし、サイトが大きくなると、負荷も大きくなっていきます。
React.memoについて
React.memoでは、コンポーネントが返した React 要素を記録し、再レンダーされそうになった時に本当に再レンダーが必要かどうかをチェックして、必要な場合のみ再レンダーします。
デフォルトでは、等価性の判断にshallow compareを使っており、オブジェクトの1階層のみを比較することになります。React.memoは、メモ化したいコンポーネントをラップして使います。
//Titleコンポーネント(子) const Title = React.memo(() => { console.log('Title component') return ( <h2>useCallBackTest vol.1</h2> ) }) //Buttonコンポーネント(子) const Button = React.memo(({handleClick,value}) => { console.log('Button child component', value) return <button type="button" onClick={handleClick}>{value}</button> }) //Countコンポーネント(子) const Count = React.memo(({text, countState}) => { console.log('Count child component', text) return <p>{text}:{countState}</p> })コンポーネントをReact.memoでラップしてメモ化すると、初回ににTitleコンポーネント、Countコンポーネント2つ、Buttonコンポーネント2つがすべてレンダリングされました。
2回目以降、Titleコンポーネントについてはpropsがないので再レンダリングされていません。
Countコンポーネントについては、数字が更新されたコンポーネントについてのみ再レンダーされているので、最適化されています。
Buttonコンポーネントについては、ボタンのどちらかをクリックしたときにクリックされていないボタンも合わせ、2つのボタンが再レンダーされているので、最適化出来ていないようです。
Buttonコンポーネントは何故再レンダーされたか
Counter.jsx//Counterコンポーネント(親) const Counter = () => { const [firstCountState, setFirstCountState] = useState(0) const [secondCountState, setSecondCountState] = useState(10) //+ 1 ボタンのstateセット用関数 const incrementFirstCounter = () => setFirstCountState(firstCountState + 1) //+ 10 ボタンのstateセット用関数 const incrementSecondCounter = () => setSecondCountState(secondCountState + 10) //子コンポーネントを呼び出す return ( <> <Title/> <Count text="+ 1 ボタン" countState={firstCountState}/> <Count text="+ 10 ボタン" countState={secondCountState}/> <Button handleClick={incrementFirstCounter} value={'+1 ボタン'}/> <Button handleClick={incrementSecondCounter} value={'+10 ボタン'}/> </> ) }
<Button handleClick={incrementFirstCounter} value={'+1 ボタン'}/>
、<Button handleClick={incrementSecondCounter} value={'+10 ボタン'}/>
の2つのButtonコンポーネントについて、いずれかのボタンをクリックしたときに、stateが更新されるので再レンダーされますが、更新されていないほうのstateのボタンも再レンダーされています。一方のボタンがクリックされて親コンポーネントであるCounterコンポーネントが再レンダーされたタイミングで関数も再生成されており、再生成された関数をReact.memoが別の関数と認識したことによります。React.memoについてもう少し詳しく
React.memoの第二引数には関数を渡すことができます。
第一引数として前回のprops(prevProps)を、第二引数として今回のprops(nextProps)を受け取ることが出来、真偽値を返すように書くことが出来ます。(areEqual)const メモ化されたコンポーネント = React.memo(元のコンポーネント, (prevProps, nextProps) => {/* true or flase */})このareEqual関数はpropsが等しいときにtrueを返し、propsが等しくないときにfalseを返します。
trueを返したときは再レンダーをスキップ、falseを返したときは再レンダーを行います。
(areEqualを省略した場合は、propsのshallow compareで等価性を判断することになります。)
また等価性のチェックにも、当然コストがかかることを考慮しなければなりません。useCallbackとReact.memoを組み合わせて最適化
親コンポーネントであるCounterコンポーネントが再レンダーされたタイミングで関数が生成性されないようにするため、useCallbackを使って最適化していきます。
//ReactからuseCallbackをimport import React, {useState, useCallback} from 'react' //Titleコンポーネント(子) //React.memoでラップ const Title = React.memo(() => { console.log('Title component') return ( <h2>useCallBackTest vol.1</h2> ) }) //Buttonコンポーネント(子) //React.memoでラップ const Button = React.memo(({handleClick,value}) => { console.log('Button child component', value) return <button type="button" onClick={handleClick}>{value}</button> }) //Countコンポーネント(子) //React.memoでラップ const Count = React.memo(({text, countState}) => { console.log('Count child component', text) return <p>{text}:{countState}</p> }) //Counterコンポーネント(親) const Counter = () => { const [firstCountState, setFirstCountState] = useState(0) const [secondCountState, setSecondCountState] = useState(10) //+ 1 ボタンのstateセット用関数 //useCallbackで関数をラップし、依存配列には関数内で利用しているfirstCountStateを入れます。 const incrementFirstCounter = useCallback(() => setFirstCountState(firstCountState + 1),[firstCountState]) //+ 10 ボタンのstateセット用関数 //useCallbackで関数をラップし、依存配列には関数内で利用しているsecondCountStateを入れます。 const incrementSecondCounter = useCallback(() => setSecondCountState(secondCountState + 10),[secondCountState]) //子コンポーネントを呼び出す return ( <> <Title/> <Count text="+ 1 ボタン" countState={firstCountState}/> <Count text="+ 10 ボタン" countState={secondCountState}/> <Button handleClick={incrementFirstCounter} value={'+1 ボタン'}/> <Button handleClick={incrementSecondCounter} value={'+10 ボタン'}/> </> ) } export default Counterうまく最適化出来ました!
関数をCounterコンポーネントの外に出せばいいんじゃないの!?って、そうなんですが、ここはuseCallbackの動作サンプルを作りたかったので。。。。
React.memoとuseCallback、さっそくリファクタリングに役立ちそうです
- 投稿日:2020-06-29T10:03:36+09:00
sortをいれるとUnexpected side effect in computed propertyってエラー
Vue.jsで
Unexpected side effect in "xxx" computed property
というエラーがでたのでメモcomputedで配列をsortして返そうとしたらエラー
computed: { members() { return this.xxx.sort((a, b) => { if (a < b) return -1 if (a > b) return 1 return 0 }) } }↓
computed: { members() { return this.xxx.slice().sort((a, b) => { if (a < b) return -1 if (a > b) return 1 return 0 }) } }これでエラー回避できた。
- 投稿日:2020-06-29T08:38:09+09:00
Vueで開発するときに整形ルールがごっちゃになるのをどうにかしたい
はじめに
vscodeでvueの開発をしているときに、しばしばコード整形ルールがごっちゃになってしまう。
自分の場合、慣例的にGoogle JavaScript Styleを採用しているので更にややこしいことになってしまっている。
特に困るのが以下の問題である:
- シングルクオートにならない
- 配列の末尾にカンマが入らない
- 変数の終わりのセミコロンが消える
<template>
内のタグの属性が同じ行になってしまう結論
結論から先にいうと、vscodeのprettierがeslintの値を読まないのが原因だった。eslintの
prettier/prettier
の値と同じ内容の.prettierrc.jsを作成することで解決できた。
<template>
タグ内の整形ルールがおかしいのはprettierのバグのようなのでoffで無効化し、eslint-plugin-vueのルールを採用するようにする。(参考:prettier not fixing vue/max-attributes-per-line)なお、vueのバージョンが新しくなってエラー扱いになるのは嫌なのでvueのコード整形ルールは
vue3-recommended
を採用。filters
がmethods
と統合され非推奨になるので注意。npm install -D eslint-plugin-vue@next eslint-config-google.eslintrc.jsmodule.exports = { root: true, env: { node: true, }, extends: [ 'google', 'plugin:vue/vue3-recommended', 'eslint:recommended', '@vue/prettier', ], parserOptions: { parser: 'babel-eslint', }, plugins: ['vue', 'prettier'], rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', quotes: ['error', 'single'], 'prettier/prettier': [ 'error', { htmlWhitespaceSensitivity: 'ignore', semi: true, singleQuote: true, trailingComma: 'all', }, ], 'vue/max-attributes-per-line': 'off', }, };.prettierrc.jsmodule.exports = { htmlWhitespaceSensitivity: 'ignore', semi: true, singleQuote: true, trailingComma: 'all', };おまけ
さて、vueのプロジェクトをつくったときにしばしば以下の行が追加される。
.eslintrc.js'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',しかし、要はプロダクションモードのときに圧縮するついでにconsole.log()を削除すれば済む話なのでわざわざルールとして入れる必用はないと思う。
これらの行は削除し、vue.config.jsの圧縮時の設定をいじって前回使ったTerserPluginを使って圧縮とconsole.log()の削除を同時に行う。
npm install -D terser-webpack-pluginvue.config.jsconst TerserPlugin = require('terser-webpack-plugin'); module.exports = { configureWebpack: { // ... optimization: { minimize: process.env.NODE_ENV === 'production', minimizer: [ new TerserPlugin({ terserOptions: { ascii_only: true, compress: { drop_console: true }, mangle: true, ecma: 6, output: { comments: false, beautify: false }, }, }), ], }, }, // ...省略 };