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

jQueryの書き方 〜中級〜

jQueryの書き方について、備忘録としてまとめておきます。
誰かの役にたったなら幸いです〜♪( ´▽`)

要素の取得(eqメソッド)

jQueryオブジェクトは、取得した要素が配列の「ような形」で入っています。

<ul>
 <li>リスト1</li>
 <li>リスト2</li>
 <li>リスト3</li>
</ul>
$(li)
//[<li>リスト1</li>,<li>リスト2</li>,<li>リスト3</li>]

この配列の「ような」ものにはindexが0からついています。
そのため以下のようにして書くことができます。

$(li).eq(1).css('color', 'red')
//リスト2のcssを変更できる

eq()の引数指定で便利なめメソッドにindex()があリます。
index($(this))でイベントが実行された要素のindex番号を取得することができます。

カスタムデータ属性

jQueryで要素や属性等を取得する方法はいくつかありますが、
その一つに属性を指定して、その値を取得することもあります。
属性はsrcやidなど標準で用意されているもの以外に、自分で作ることができます。
この自分で作った属性を、カスタムデータ属性と呼びます。
その場合、「data-」から始めます。

<img src="#">
//srcが属性

<div data-option="hoge">...</div>
//data-optionがカスタムデータ属性
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

誰でもわかりやすいGitのコミットメッセージが書けるジェネレーターを作りました

前置き

個人開発でもチーム開発でも、Gitは開発者にとって、ほぼ必要不可欠なツールです。

僕も当然Gitを使っていますが、コミット時のメッセージを書くときにどう書いていいかわからなくて、毎回頭を抱えてしまいます。

そこでジェネレーターを作ることにしました。

出来上がったもの

Git Commenter

git-commenter.gif

Gitのコミットメッセージについて

僕が調べたところGitのコミットメッセージには、統一された書き方は存在しませんでしたが

メッセージ自体の可読性、コミット内容のわかりやすさや、Gitの仕様を考慮して

コミット内容の要約 // 英語では V + O (+M) と書くのが一般的

コミット内容の具体的な理由

という書き方が一般的でした。

また、ここ最近ではコミット内容の把握を簡単にしたりlogやGithubのレポジトリがオシャレになるという理由で、コミット内容のカテゴリを表現する絵文字をコミット内容の要約にprefixとしてつけるのが流行っているようです。(Qiitaにもいくつか絵文字コミットについて言及している記事があります)

このジェネレーターでは、絵文字もサポートして

絵文字 : コミット内容の要約

コミット内容の具体的な理由

という形式のコミットメッセージを生成するようにしました。(もちろん絵文字もない、コミット内容の具体的な理由もない要約だけのコミットメッセージもここでは作れます)

要約については、Githubのファイル欄に表示されたりするためここは日本語英語が入り混じっていると汚いなと思ったので英語統一にしました

コミット内容の具体的な理由については英語が望ましいですが、

  • 具体的な理由を書く必要がある (要約より、英語力が必要であり内容が様々であるためテンプレートもあまり機能しない)
  • Githubのファイル欄や、git log --onelineコマンドで表示されない

以上の理由から日本語でも問題ないのかなと思います。

具体的な使い方

基本的には上記のgifのように実際に使っていただければわかると思います。

まずフォームについて簡単に説明すると

  • Emoji: コミットカテゴリを絵文字で表したもの
  • Verb: コミットの具体的な操作内容 (例: 何かを追加した => Add、何かを更新した => Update)
  • Adjective: コミットの対象を修飾するもの (例: 不必要な => unneeded)
  • Object: コミットの対象 (例: README.mdを更新した => (Update) README.md)
  • Modifier: コミット文を修飾するもの (例: sample.txtをAからBに移動した => (Move sample.txt) from A to B)
  • Reason: コミット内容の具体的な理由
  • Git Comment: 入力から生成されたコミットメッセージが入ります

テンプレート

各フォームの左のセレクトボックスで「テンプレ」を選択すると、コミットメッセージでよく使われる英単語のテンプレート一覧、およびその日本語訳が一覧で出てきます。

テンプレの英単語は簡単なものが多く、コミットメッセージ中でよく使われているため、テンプレの英単語でコミット内容を表せる場合は、これらを使ったメッセージのほうが読みやすいメッセージになると思います。参考にしたQiita記事

また、デフォルトのテンプレート以外に個人がよく使う単語やフレーズは、ユーザーテンプレートとして登録することができます。

登録した単語やフレーズはフォームのテンプレ一覧に表示されるようになります。

git-commenter-template.gif

開発

このWebアプリ自体は、react(+redux)を使って開発しました。 クライアント制作ではよく使うので特に苦労することなくすんなりできました。
(しいて言えば、クリップボードへのコピーがReactの仮想DOMだと難しいです。結局専用のライブラリを使いました。)

当初はWebアプリにするつもりはなかったのですが、必ずGUIを持たせたいとは考えていました。

ただWebアプリ開発以外でGUI開発の経験が特になく、Webアプリでも特に問題はないのでWebアプリにしました。

デプロイに関しては、いつもGithub Pagesにホスティングしていたのですが、勉強もかねて今回はS3にデプロイしました。(しかもRoute53で独自ドメインまで無駄に取りました。)

補足

このWebアプリ開発の前準備のときに調べていて初めて知ったのですが、Gitにはtemplate機能があります。(こちらのQiita記事とかに詳しく載っています)

ですが、僕が普段使っているVSCodeのGitツールではこのテンプレート機能が利用できず、ターミナルをぽちぽちしたり、自分でわかりづらいコミットメッセージを考えたりしなくてはいけなかったため、今回このようなWebアプリをつくりました。

感想

しっかりGoogleで調べましたがGitについてはあまり理解しているとはいえないので、間違っているところやこの機能使えばもっと便利でいいよってところがあったら教えてくださると助かります。

利用した感想とかもあると嬉しいです。

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

JavaScriptでのthis

本記事を投稿しとうとしたきっかけ

JavaScriptの勉強をしていて、prototypeでのオブジェクト指向について勉強をやっていた時
thisの挙動がよくわからなくなったため、色々調べました。
その際に調べたthisについて、本記事で色々まとめてみたいと思います。
何か不足している情報などあれば、コメントください。。。

prototypeオブジェクト

よくJavaScriptで、機能ごとにオブジェクトにまとめたいなどあると思います。
その際に必ず、prototypeや、ES6だとclassに出会うことができると思います。

var UserInfo = function(userName, age, sex) {
    this.userName = userName;
    this.age = age;
    this.sex = sex;
}

UserInfo.prototype = {
    toString() {
        console.log('名称:' + this.userName);
        console.log('年齢:' + this.age);
        console.log('性別:' + this.sex);
    }
}

var userInfo = new UserInfo('太郎', 20, '男');
userInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男

「名称」「年齢」「性別」の3つのメンバ変数を持ち、prototypeには、ユーザ情報について表示するtoStringメソッドのあるユーザ情報オブジェクトです。
メンバ変数に注目すると、thisが出現します。
そのthisで定義された変数は、prototypeで定義したメソッド内(上記ソースだとtoStringメソッド)などで用いることができます。

しかし、以下のソースを実行してみると、予想とは違う挙動になります。

var UserInfo = function(userName, age, sex) {
    this.userName = userName;
    this.age = age;
    this.sex = sex;
}

UserInfo.prototype = {
    toString: function() {
        console.log('名称:' + this.userName);
        console.log('年齢:' + this.age);
        console.log('性別:' + this.sex);
    },
    // 追記
    set: (userName, age, sex) => {
        this.userName = userName;
        this.age = age;
        this.sex = sex;
    }
}

var userInfo = new UserInfo('太郎', 20, '男');
// 追記
userInfo.set('花子', 30, '女');

userInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男

あれ?変わらない?

先ほどの追記したソースをみてみると、setメソッドはアロー関数(Arrow_functions)で記述されています。
勉強していく上で、いちいちfunctionって書くのがめんどくさくなり、アロー関数をたくさん書くようにしたら、上記のようなソースでハマりました。。。

よくわからないまま直そうとした際、とりあえず、アロー関数を普通のfunctionにしてみようと思い、戻してみました。
すると表示結果は自分の思っていたような正常動作になりました。

    // ...省略
    set: function(userName, age, sex) {
        this.userName = userName;
        this.age = age;
        this.sex = sex;
    }
}

var userInfo = new UserInfo('太郎', 20, '男');
userInfo.set('花子', 30, '女');
userInfo.toString();
// -> 名称:花子
// -> 年齢:30
// -> 性別:女

いやー、そんなに長くハマらずに済みました〜。

うん?つまり、アロー関数が悪い?

アロー関数とthisの相性が悪いのか?っと考え、色々調べてみました。
MDNさんのアロー関数の説明によると

2 つの理由から、アロー関数が導入されました。1 つ目の理由は関数を短く書きたいということで、2 つ目の理由は this を束縛したくない、ということです。

あぁなるほど〜束縛したくないからね〜。
裏側の実装とか、細かいことなどが理解していないと、上記の説明では理解に苦しみます。。。

さらに色々調べてみました。戦いですね。
するといい記事が発見できました。

@mejileben, @takkyunさん、ありがとうございます!
とってもわかりやすかったです!

そもそもthisというのは

簡潔にいうと、thisというのは

  • thisfunction を呼んだ時の . の前についているオブジェクトを指している
    @takkyunさんの記事引用

らしい。

.の前に何もなければ、グローバルオブジェクトthisは参照するらしい。
例として、以下のソースを見ていただきたい。

var userName = '花子';
var age = 30;
var sex = '女';

var toString = function() {
    console.log('名称:' + this.userName);
    console.log('年齢:' + this.age);
    console.log('性別:' + this.sex);
}

var UserInfo = {
    userName: '太郎',
    age: 20,
    sex: '男',
    toString: toString
}

// toStringの . の前はUserInfoです。
// よって、thisはUserInfoを参照するわけです。
UserInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男

// toStringメソッドをいきなり呼ぶと
// . の前には何もないため、thisはグローバルオブジェクトを参照するわけです。
toString();
// -> 名称:花子
// -> 年齢:30
// -> 性別:女

グローバルオブジェクトについては、知っている前提で話を進めます。
もし、わからない方がいれば

Global object (グローバルオブジェクト)

を参照してください。

以下のソースは、グローバル領域とグローバルオブジェクトについてのソースとなります。

// グローバル領域でthisを参照すると、グローバルオブジェクト(Window Object)が出力されます。
console.log(this);
// -> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

// グローバル領域で変数を定義。
// すると、以下のargはグローバルオブジェクトのメンバ変数として追加される。
var arg = "argument0";

// 以下は全て同じものを参照している。
console.log(arg);        // -> argument0
console.log(this.arg);   // -> argument0
console.log(window.arg); // -> argument0

なるほど、だんだんわかってきましたぞ。

あ。アロー関数は?

そもそもアロー関数を使用したら、うまくいかなかったというのに、少しthisの話が長くなってしまいました。。。
それでは、アロー関数でのthisの挙動はどうなるのでしょうか?
こちらも簡潔にいうと

  • アロー関数式で宣言された関数は、宣言された時点で、thisを確定(=束縛)させてしまうのです。
    @mejilebenさんの記事引用

こちらも実際にソースをみてみましょう。

var arg = "argument0";

// アロー関数でメソッドを定義
// 定義した時点でthisの参照先は束縛されます。
var func = () => {
    console.log(this.arg);
}

// 上記で宣言したメソッドをメンバ変数として追加。
var obj = {
    arg: "argument1",
    func: func
}

// こちらは、先ほどと変わらず、グローバル領域の変数を参照します。
func();      // -> argument0
// アロー関数で定義した時点でthisの参照先が決定するため
// objを参照せず、その外側の領域(グローバル領域)を参照します。
obj.func();  // -> argument0

いやー、結構わかってきたぞー

thisでの挙動についてある程度理解しました。
そこで、自分なりに色々意地悪なソースを考えてみたりして。。。
皆さんも、以下のソースで実際にどのような出力結果となるか考えてみてください!

arg = "arg1";

function consoleLog() {
    console.log(this.arg);
}

var obj1 = {
    arg: "argument2",
    func: consoleLog
}

var obj2 = {
    arg: "argument3",
    func: () => {
        console.log(this.arg);
    }
}

var obj3 = {
    arg: "argument4",
    func: function() {
        var arg = "argument5";
        var subFunc = () => {
            console.log(this.arg);
        }

        subFunc();
    }
}

var obj4 = {
    arg: "argument6",
    func: function() {
        var arg = "argument7";
        var subFunc = function() {
            console.log(this.arg);
        }

        subFunc();
    }
}

consoleLog(); // -> ???
obj1.func();  // -> ???
obj2.func();  // -> ???
obj3.func();  // -> ???
obj4.func();  // -> ???

ちょっと応用がきいているのではないかと思いますが、考えてみる価値はありそうです!
答えは、Chromeなどの開発者ツールのコンソールに上記ソースをはっつけていただいて、実行すれば答えがわかります!
個人的には、obj4func実行時の出力結果が難しいのではないかと思っています。
ただ、thisの最初の説明にあった箇所を思い出せば解けると思います。
どんなにスコープがネストしても、いきなり . の前にオブジェクトがないfunctionを呼び出したりすると。。。

まとめ

ここまで、説明が足りない箇所もあったかと思われますが、ざっとまとめてみました。
JavaScriptでのthisは少し複雑だなと感じました。
あとは、何も考えずにアロー関数をバンバン用いるのは危険であることもわかりました。(気をつけます)

thisの操作に慣れると、ソースも安全に組むことができますし、正しいthisの使い方でコーディングできると思われます。
また、thisを正しく理解して用いると、コード量も減るのではないか?とも感じました。

もっとthisについて、上記以外の複雑なとこがあれば教えてください!
ここまで読んでくれた方、ありがとうございました?‍♂️

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

【初投稿テスト】非同期ネットゲーム作ってみた

非同期ネットゲーム仕様書

はじめに

本書は、RPGアツマールで開催される非同期ネットゲームコンテスト(2019年3月3日(日)23:59〆切)に向けて作成するゲームの仕様を書いた物である。

  • マークダウン記法で記述するテキストデータである。
  • UML部分はPlantUMLを使用する。

マークダウン記法

本書で使用するマークダウン記法について、使い方と書き方を以下に記述する。

使い方

書き方

マークダウン記法では、文章内の改行は無視される。
画面の右端で自動改行される。
明示的に改行させたい時は行末に空白2つを付ける。

このようになります。

ですよ。

  • ハイフン+スペース「- 」を行頭に付けると、箇条書きになる。
  • 行頭に「●」が付く。
    • 行頭にスペース2個を付けると、インデントも可能。
  • 本書では要件や仕様に関する記述は、基本的に箇条書きで記述している。
  1. 連番による箇条書きは、番号+スペースを行頭に付ける。
    1. 番号は「1. 」だけでも自動採番される
    2. だから、「1. 」の方が変更に強い
  2. 改行が必要な時は、このように
    行末にスペース2個を付ける。
  3. 番号の場合はスペース4個でインデントする。
  4. 本書では処理の順序を表したい場合に使用する。

【注意】

箇条書きや引用などの文章の塊がある場合、その塊の種類が変わる所で空白行を入れる。

「> 」は引用です。

引用する時に開始行の先頭に使います。
このように表示されます。

表は以下のように記述する。

|A|ヘッダ|てすと|項目名|
|---|:---:|---|---|
|AAA|B|テスト|表の内容|
|Aの内容2|センタリング|C|表の内容2|
|Aの内容03|表はこのように書く|Test|D|

この記述により、以下のように表示される。

A ヘッダ てすと 項目名
AAA B テスト 表の内容
Aの内容2 センタリング C 表の内容2
Aの内容03 表はこのように書く Test D
バッククォート3個「```」を開始行、終了行に書いて囲った
  この範囲は
自由に空白や改行できる。

開始側のバッククォート3個「```」の後に、テキストデータの形式を記述する。
この例では「```text」としているが、
「```javascript」「```plantuml」と書くと、その言語として表示される。

そのため
  ソースコードを
    書くのに最適。

本書で使いそうなマークダウン記法の書き方は以上。

なお、下の横線は「***」と記述する。


ゲーム情報 (概要)

ゲームの目的

  • 花を育てる。
    • 当初予定では、「いちご狩り」をするゲームであったが、いちごの素材がないため断念した。
  • 花をプレゼントして好感度を上げる。
  • キャラクターの人気投票をする。

ゲームタイトル

  • 『花摘み【パケキャラ総選挙】』を仮タイトルとする。
    • 「花摘み」は品がないがインパクトはあると思われるので、仮タイトルとする。
  • 最終的に、タイトルは『PKG16 総選挙』とした。

ゲームの流れ (基本設計)

概要

  • 放置系ゲームの要領で花を育て、花をパケキャラにプレゼントし、プレゼントの量でパケキャラの人気を競う。
  • 「パケキャラ」とは、RPGツクールMVの「パッケージキャラクター」の事である。本ゲームではNPCである。
  • 他人が育てた花を取る事が出来る。
  • パケキャラごとに好感度ランキングを用意する。

花を育てる

放置系ゲーム要素は以下の流れとする。

  1. 種を植える(プレイヤー操作)。
  2. 種が草に育つ(放置)。
  3. 草が花に育つ(放置)。
  4. 花が実になる(放置)。
  5. 実が種になる(放置)。
  6. 種が草に育つ(放置)。繰り返し。
  • 花壇は全プレイヤー共通の領域とする。
  • 花壇の数はプレイヤーの数に応じて増やす。
    • 自動でマップの拡張できる?
      しかし、1プレイヤーに1マップなどにするとマップ数が増えると困るので、自動で増やすのはやめた方が良さそう。
    • 花壇の数は何らかのタイミングで作者が手動で増やす。(増やせるように作る事)
  • 花の成長状況を花の上に表示させたい。「あと何秒で次の状態になるか」を表示する。

花を摘む

  • プレイヤーは、「花」または「実」を採取する事が出来る。
  • 「花」と「実」以外は採取できない。
  • 花壇は共通領域なので、他のプレイヤーが育てた「花」や「実」を採取する事が出来る。
  • 花を摘んだ花壇は「空き」になる。プレイヤーが種を植えるまで「空き」になる。

花をプレゼントする。

  • プレイヤーはパケキャラに「花」または「実」をプレゼントする事が出来る。
  • プレイヤーがプレゼントする事で、「好感度」と「種」と「所持金」を得る事が出来る (このゲームに公職選挙法のようなものは無い)。
  • プレイヤーがプレゼントする事で、パケキャラの人気ポイントが上昇する。

種の入手

  • プレイヤーはゲーム開始時に「種」をいくつか持っている。
  • プレイヤーがパケキャラにプレゼントした時に、「種」と「所持金」をもらえる。
    • 「実」をプレゼントした時に、その「種」をもらえると面白いかも。 出所がはっきりしているので。
  • プレイヤーは所持金を使用して「種」を買う事が出来る。

プレイヤーのデータ

  • アイテム(植える前の「種」、採取した「花」または「実」)
    • 「種」と「花」&「実」でアイテム種別を分ける。
  • プレイヤーの実績を表す何か。
    • 実績アイテム(大事なもの)
  • 経験値(レベル)
  • 所持金
  • 好感度(パケキャラそれぞれなので、16人分)
    • ステータス画面を使うのと、アイテムの個数で表現するの、どっちが楽?
    • 変数を16個用意して、ステータス画面でその変数を表示する?

アツマールAPIを使う場面

  • 花壇はプレイヤー共有なので、何らかのAPIを使用する。
    • 使うAPIは、グローバルシグナルになりそう。
    • 花の成長はプレイヤーごとに行い、採取した時にシグナルを送る。
    • 採取シグナルを受け取ると、その花壇は「空き」になる。
    • タイミングによっては、同じ花壇で複数プレイヤーが同時に採取できるが、許容する。
    • APIの通信は『5秒に1回よりも緩やかにすること』との事なので、採取シグナル送受信のタイミングで、何らかの演出を行い、時間を稼ぐ。
  • ランキング機能を使う。
    • パケキャラごとの好感度をランキングにする。
    • パケキャラ総選挙の順位はランキング機能を使わない。プレイヤーのランキングではないので。
  • 総選挙の順位に、グローバルサーバー変数を使用する。
    • 1キャラにつき1変数を使用すると、16個のグローバルサーバー変数が必要になる。
    • グローバルサーバー変数では、トリガーを使用して値の変更を行う。

パケキャラ

  • RPGツクールMVのパッケージキャラ16体を使う。
  • マップに表示する。
  • プレゼントを渡す事が出来る。プレゼントは総選挙の票(人気ポイント)を兼ねている。
    • プレゼントは「花」または「実」である。
    • アイテム選択の処理でプレゼントを渡す。
  • 好きな「花」「実」があり、それによってプレゼント時の好感度に差がある。
  • 好きな「花」「実」の説明と、プレゼントを渡す場所は分けておく。
    • 好きな「花」「実」の説明はすぐに見れる場所に置く。
    • プレゼントを渡す場所は、パケキャラごとにマップを用意する。
    • プレゼントを渡してお礼を受け取ると、ランキングAPIの通信を行う。そのため、(プレゼントは1アイテムずつ選択するが)プレゼントに対してのお礼はまとめて受け取るのが良さそう。そのマップを出る時でもイイかも。

メモ

  • メッセージWindowに歩行キャラを表示させたい。名前だけでパケキャラを識別するのが難しいと思われるため。
    • アイコン化(32×32)するか、プラグインを用意するか。
    • IconSet.pngを下に拡張できるので、アイコン化するのが良さそう。

詳細設計

  • 詳細設計の代わりに、JavaScriptのソースコードを記述する。

Promise全般

// promise() を使いたい場合
// thenはPromise受信時に実行される
// catchはエラー発生時に実行される
// ドット「.」区切りで連続して記述する
// 1行で書くとこのようになっている
promise().then(function(a){ 非同期処理 }).catch(function(b){ エラー処理 });

グローバルシグナル送信

window.RPGAtsumaru.experimental.signal.sendSignalToGlobal(data)
// catchはエラー発生時に実行される
// 引数errを受け取る
.catch(function(err){
    switch(err.code) {
    case "UNAUTHORIZED":
        // プレイヤーがログインしていない
        console.log('send error : UNAUTHORIZED');
        break;
    case "BAD_REQUEST":
        // ゲーム側で何か間違えているとき=指定したボードIDが大きすぎるかマイナスの場合などに発生
        console.log('send error : BAD_REQUEST');
        break;
    case "INTERNAL_SERVER_ERROR":
        // サーバー側で何らかの問題=通信不良やメンテ等で発生
        console.log('send error : INTERNAL_SERVER_ERROR');
        break;
    case "API_CALL_LIMIT_EXCEEDED":
        // 短時間にゲームAPIを利用しすぎて、一時的に利用を制限されている
        console.log('send error : API_CALL_LIMIT_EXCEEDED');
        break;
    }
});

グローバルシグナル受信

window.RPGAtsumaru.experimental.signal.getGlobalSignals()
// thenはPromise受信時に実行される
// Promiseの内容を引数vとして受け取る
// 非同期処理なので、いつ受信するかは分からない事に注意!
.then(function(v){
    KRD_checkSignal(v); // 別関数で処理する
})
// catchはエラー発生時に実行される
// 引数errを受け取る
.catch(function(err){
    switch(err.code) {
    case "INTERNAL_SERVER_ERROR":
        // サーバー側で何らかの問題=通信不良やメンテ等で発生
        console.log('get error : INTERNAL_SERVER_ERROR');
        break;
    case "API_CALL_LIMIT_EXCEEDED":
        // 短時間にゲームAPIを利用しすぎて、一時的に利用を制限されている
        console.log('get error : API_CALL_LIMIT_EXCEEDED');
        break;
    }
});

データフロー

plantuml_2019-03-07T22-01-19.png

@startuml
title 畑の処理 (収穫する)
actor プレイヤー
participant 畑
control 時間
control 受信ループ
actor NPC #RED
database シグナル
database サーバ変数
database ランキング
control 他の受信ループ #GREEN
actor 他プレイヤーの畑 #GREEN
受信ループ -> シグナル: チェック
受信ループ <-- シグナル: 変更なし
他の受信ループ -> シグナル: チェック
他の受信ループ <-- シグナル: 変更なし
プレイヤー -> 畑: 種植え
activate プレイヤー
activate 畑
畑 -> シグナル: 植えた
受信ループ -> シグナル: チェック
畑 <- シグナル: 植えた
他の受信ループ -> シグナル: チェック
シグナル -> 他プレイヤーの畑: 植えた
時間 -> 畑: 経過
畑 -> 畑: 成長
受信ループ -> シグナル: チェック
受信ループ <-- シグナル: 変更なし
他の受信ループ -> シグナル: チェック
他の受信ループ <-- シグナル: 変更なし
時間 -> 畑: 経過
畑 -> 畑: 成長
プレイヤー <- 畑: 収穫
deactivate プレイヤー
畑 -> シグナル: 収穫した
受信ループ -> シグナル: チェック
畑 <- シグナル: 収穫した
deactivate 畑
他の受信ループ -> シグナル: チェック
シグナル -> 他プレイヤーの畑: 収穫した
@enduml

plantuml_2019-03-07T22-43-33.png

@startuml
title 畑処理 (収穫なし)
actor プレイヤー
participant 畑
control 時間
control 受信ループ
actor NPC #RED
database シグナル
database サーバ変数
database ランキング
control 他の受信ループ #GREEN
actor 他プレイヤーの畑 #GREEN
受信ループ -> シグナル: チェック
受信ループ <-- シグナル: 変更なし
他の受信ループ -> シグナル: チェック
他の受信ループ <-- シグナル: 変更なし
プレイヤー -> 畑: 種植え
activate プレイヤー
activate 畑
畑 -> シグナル: 植えた
受信ループ -> シグナル: チェック
畑 <- シグナル: 植えた
他の受信ループ -> シグナル: チェック
シグナル -> 他プレイヤーの畑: 植えた
時間 -> 畑: 経過
畑 -> 畑: 成長
受信ループ -> シグナル: チェック
受信ループ <-- シグナル: 変更なし
他の受信ループ -> シグナル: チェック
他の受信ループ <-- シグナル: 変更なし
時間 -> 畑: 経過
畑 -> 畑: 成長
時間 -> 畑: 経過
畑 -> 畑: 種に戻る
deactivate プレイヤー
受信ループ -> シグナル: チェック
deactivate 畑
受信ループ <-- シグナル: 変更なし
他の受信ループ -> シグナル: チェック
他の受信ループ <-- シグナル: 変更なし
@enduml

plantuml_2019-03-07T22-43-54.png

@startuml
title 投票処理
actor プレイヤー
participant 畑
control 時間
control 受信ループ
actor NPC #RED
database シグナル
database サーバ変数
database ランキング
control 他の受信ループ #GREEN
actor 他プレイヤーの畑 #GREEN
プレイヤー -> NPC: プレゼント(投票)
activate プレイヤー
プレイヤー <- NPC: お礼
プレイヤー -> サーバ変数:登録(投票)
プレイヤー -> ランキング:登録(好感度)
deactivate プレイヤー
@enduml

plantuml_2019-03-07T22-44-13.png

@startuml
title 集計処理 (投票結果)
actor プレイヤー
participant 畑
control 時間
control 受信ループ
actor NPC #RED
database シグナル
database サーバ変数
database ランキング
control 他の受信ループ #GREEN
actor 他プレイヤーの畑 #GREEN
プレイヤー -> サーバ変数:投票結果取得(要求)1キャラ目
プレイヤー <- サーバ変数:投票結果取得(応答)1キャラ目
プレイヤー -> サーバ変数:投票結果取得(要求)2キャラ目
プレイヤー <- サーバ変数:投票結果取得(応答)2キャラ目
プレイヤー -> サーバ変数:投票結果取得(要求)3キャラ目
プレイヤー <- サーバ変数:投票結果取得(応答)3キャラ目
プレイヤー -> サーバ変数:投票結果取得(要求)4キャラ目
プレイヤー <- サーバ変数:投票結果取得(応答)4キャラ目(以下16キャラまで)
@enduml

plantuml_2019-03-07T22-44-26.png

@startuml
title 集計処理 (好感度)
actor プレイヤー
participant 畑
control 時間
control 受信ループ
actor NPC #RED
database シグナル
database サーバ変数
database ランキング
control 他の受信ループ #GREEN
actor 他プレイヤーの畑 #GREEN
プレイヤー -> ランキング:好感度取得(要求)1キャラ分
プレイヤー <- ランキング:好感度取得(応答)1キャラ分
@enduml
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsで開発をする場合にまずやるべきこと

概要

僕がNuxt.jsで開発をする場合にまずやることを紹介したいと思います。

インストール

僕はcreate-nuxt-appでインストールします。

npx create-nuxt-app <my-project>

入れる拡張は
1. Use a custom server framework → express
2. Choose features to install → Linter / Formatter, Prettier, Axios
3. Use a custom UI framework → bootstrap
4. Use a custom test framework → jest
5. Choose rendering mode → Universal

が個人的に好きです。

次にとりあえず入れとけなパッケージもインストールします。
とりあえずsassを入れます。

npm i -D node-sass sass-loder

次にsass変数をグローバルに使うためのパッケージを入れます。

npm i @nuxtjs/style-resources

Font Awesome入れます。公式のgitにnuxt用の導入方法が載っています

npm i @fortawesome/fontawesome-svg-core @fortawesome/free-regular-svg-icon @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome

適当に書き換える

package.jsonにnpm scriptを一つ追加します。lintfixは絶対追加したほうがいいです、エラー内容を人力で追っていくのは時間の無駄です。
herokuにデプロイする場合はそれも追記します。

package.json
{
    // lintfixを追加
    "lintfix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
  // herokuデプロイ用
    "heroku-postbuild": "npm run build"
  },
}

.eslintrc.jsにルールを追加します。よく分かってないのですがconsole.logしたら時たまエラーになるので、それの回避です。

.eslintrc.js
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }

sass変数のあれやこれやの設定はこれこれをみたら良いですよ(宣伝)。

終わり

毎回苦労して初期設定してた気がするけど、そんな大した作業量でもなっかたです。
半年くらい前はeslintrcをゴニョゴニョ編集してた気がしますが進化してるっぽいですね。
あと最新のnuxt.js ver2.40以降はランダムにホットリロードが失敗するというちょっとムカつくバグがあるので気をつけてください。

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

[ES6] import でディレクトリー内すべてのファイルを読み込む

課題

export default しているファイルを読み込むときに以下の書き方をしてたけどhogeディレクトリ全体を一括で読み込みたいと思った。

import hoge1 from './hoge/hoge1.js'
import hoge2 from './hoge/hoge2.js'

hoge/index.js で import 書いてまとめるのもめんどくさい。

方法

これでいいのかわからないけど、以下の方法でやってみたら動いた。

import fs from "fs"
const hoges = {}
fs.readdirSync("./hoge").forEach(async file => {
  const module = await import(`./hoge/${file}`)
  hoges[file] = module.default
})
hoges.hoge1.hogeFunction()

ファイル形式を選択したいときはフィルタリングすればいけるし、サブディレクトリ読み込みたいときは再帰的に読めばいいと思う。

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

JavaScriptの文字列から配列、オブジェクトから配列、配列の再構築のあれこれ

個人的によく使うのでメモがてら公開。

2次配列を1次配列に統合

const array = [
    1, [2, 3, 4], 5
]
const flattened = array.reduce(
    (accumulator, currentValue) => accumulator.concat(currentValue),
    []
)
console
[ 1, 2, 3, 4, 5 ]

文字列から配列

let array2 = "テキスト1,テキスト2,テキスト3,テキスト4"
array2 = array2.split(',')
console
[ 'テキスト1', 'テキスト2', 'テキスト3', 'テキスト4' ]

配列の中にあるオブジェクトの値の文字列を配列化

const array3 = [
    { names: "テキスト1,テキスト2,テキスト3,テキスト4" },
    { names: "テキスト1,テキスト2,テキスト3" },
    { names: "" }
]
const values = array3.map((contact, index) => {
    return contact.names.split(',')
})
console
[
  [ 'テキスト1', 'テキスト2', 'テキスト3', 'テキスト4' ],
  [ 'テキスト1', 'テキスト2', 'テキスト3' ],
  [ '' ]
]

2次配列を1次配列にして空白の配列を削除

const array3 = [
    { names: "テキスト1,テキスト2,テキスト3,テキスト4" },
    { names: "テキスト1,テキスト2,テキスト3" },
    { names: "" }
]
const values = array3.map((contact, index) => {
    return contact.names.split(',')
})
// 2次配列を1次配列に
let flattened2 = values.reduce(
    (accumulator, currentValue) => accumulator.concat(currentValue),
    []
)
// 空の要素を消す
flattened2 = flattened2.filter(function (x) {
    return !(x === null || x === undefined || x === "")
})
console
[ 'テキスト1', 'テキスト2', 'テキスト3', 'テキスト4', 'テキスト1', 'テキスト2', 'テキスト3' ]

同じ文字列を数えて一つに絞りオブジェクトに変換

const array3 = [
    { names: "テキスト1,テキスト2,テキスト3,テキスト4" },
    { names: "テキスト1,テキスト2,テキスト3" },
    { names: "" }
]
const values = array3.map((contact, index) => {
    return contact.names.split(',')
})
let flattened2 = values.reduce(
    (accumulator, currentValue) => accumulator.concat(currentValue),
    []
)
flattened2 = flattened2.filter(function (x) {
    return !(x === null || x === undefined || x === "")
})
// ここから
let counts = {}
for (var i = 0; i < flattened2.filter(v => v).length; i++) {
    var key = flattened2[i]
    counts[key] = (counts[key]) ? counts[key] + 1 : 1;
}
const local = Object.keys(counts).map((contact, i) => {
    return { id: i, value: contact, count: counts[contact] }
}, counts)
console
[
  { id: 0, value: 'テキスト1', count: 2 },
  { id: 1, value: 'テキスト2', count: 2 },
  { id: 2, value: 'テキスト3', count: 2 },
  { id: 3, value: 'テキスト4', count: 1 }
]

まとめ

ES2019のflat(),flatMap()早く使いたい。

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

How to use vuex in vue.js 3.4.1

Introduction

In this article I will discuss about how we can use vuex in a nutshell. So before we get our hand dirty we should talk a little bit about flux design pattern.

Flux

Flux is known as the concept of Unidirectional data flow. It is developed by Facebook while they are working with React. This Vuex pattern will illustrate on how flux work in Vue.js.

Typically, Flux has some individual components as listed below:

  • Action : Helper method that facilitate passing the data to dispatcher.
  • Dispatcher : Receives actions and broadcast payload to registered callback.
  • Stores : Containers for application state & logic that have callback registered to the dispatcher.
  • Controller Views : React component that grab the state from Stores and pass it down via props to child components.

V70cSEC.png

Vuex

  • Create new Vue.js project We will use vue cli for creating new project. In case you don't have vue cli install, please enter this command in your terminal
npm install -g @vue/cli

To create new vue project we have to enter this command:

vue create my-project

It will ask some questions for the project configuration setting. So just choose what ever you like such as test framework and so on.

  • Create a new view component call Counter.vue and add this following code:
<template>
    <figure>
        <button @click="decrement">-</button>
        <span>{{ $store.state.count }}</span>
        <button @click="increment">+</button>
    </figure>
</template>

<script>
export default {
    name: 'Counter',
    methods: {

        // component method handling increment store dispatch action
        increment: function (event) {
            this.$store.dispatch('increment')
        },

        // component method handing decrement store dispatch action
        decrement: function (event) {
            this.$store.dispatch('decrement')
        }

    }
}
</script>

<style>
    span {
        padding: 0 10px;
    }
</style>
  • In store.js add this following code:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {

    INCREMENT (state) {
      state.count++
    },

    DECREMENT (state) {
      state.count--
    }

  },
  actions: {

    increment ({ commit }) {
      commit('INCREMENT')
    },

    decrement ({ commit }) {
      commit('DECREMENT')
    }

  }
})
  • Now in router.js update home route to match with Counter view component
   {
      path: '/',
      name: 'counter',
      component: () => import('./views/Counter.vue')
    }

Basically we have two actions in our store which are increment and decrement each action send signal to mutations and mutate the state value.

In Counter.vue we bind the value from the state with the respective UI component. That's what we call Controller View in flux.

So this is a short demonstration on how we can use vuex in Vue.js. If you have any further concern or suggestion please email me (phanithken@gmail.com) or leave the comment down bellow. I will try to answer.

And I also put all the sample into Github which you can check it out here
Thank you! Have a nice day :)

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

npmパッケージを公開していざ使おうと思ってもimportできなかったので調べたこと

こちらの記事でも書きましたが、アメーバのような形状をp5.jsで作り、generatorも公開しました。この記事はこれを機にこのアメーバーの生成をnpmパッケージにして公開してみようと思い、公開方法を調べた記録です。HowToではないです。つまづいた点などの記録ですので、作り方などは私も参考にしていた以下の方の記事が良いと思います。

参考

初めてのnpm パッケージ公開
3分でできるnpmモジュール

公開用のフォルダ

パッケージの作り方を参考にして、npmにアカウント登録とpackage.jsonにライブラリ情報を記入し、npm publishで公開するところまでいけました。公開するフォルダの中身はwebpackでアプトプットしたデータです。package.jsonに以下を記入すると、プロジェクトディレクトリの中のデータが、例えば他のプロジェクトでnpm install p5-amebaとインストールした時にnode_modulesに入ってきます。

package.json

...
  "files": [
    "README.md",
    "lib"
  ]
...

上の例だと、README.mdとlibフォルダにあるデータですね。なので、使い手がimport ameba from 'p5-ameba'とした時に必要なデータを書き込めば良いと思います。このp5-amebaはパッケージのname fieldで決められるので、libフォルダの中にp5-ameba.jsのようなファイルを用意する必要はないです。それで、npm install p5-amebaとした時に読み込むjsファイルは、package.jsonのmain fieldで指定します。

package.json
...
 "name": "", <- npm install するときの名前
  "main": "", <- importした時に読み込むファイル
  "files": [] <- node_modulesに入るファイルを指定
...

公開してもimportで読み込めない

なんとかパッケージを公開してみて、いざ使ってみようと思い、別プロジェクトで実際にnpm install p5-amebaとし、jsファイルからimport ameba from p5-amebaとやってみました。すると、以下のエラーが出て読み込みができません。

Uncaught SyntaxError: Unexpected identifier

開いた口が塞がりません。。。まだまだフロントエンドの勉強中なので、おそらく何かの知識が抜け落ちてるか、何となくの知識で使っている何かが原因だと思います。ただ、用語が多すぎたりでまだ自分の技術にできていません。仕方がないので、少しづつ原因を追っていきました。

確認作業

  • 開発環境で配布したライブラリが本当に使えているのかを確かめる  -> 使えた
  • 配布したライブラリを、新しいプロジェクトで直接読み込んでみる -> 使えた(要は開発環境と同じ)
  • 配布したライブラリを、node_modulesから読み込む -> エラー

つまり、node_modulesにある状態だとなぜか読み込まれないことが分かりました。ほかのnpmパッケージのライブラリはimportしても使えているので、node_modulesにデータを置く過程か、データそのものに原因がありそうです。作成したライブラリはES2015で書いているため、もしかしたらbabelとかwebpackも含めてこの辺りの知識の曖昧さが原因なのでは?と思い調べてみました。怪しいのはwebpackでアウトプットしたデータです。

他のパッケージの作りを研究

作成したアメーバーのアニメーションライブラリは、animejsというライブラリの構成をかなり参考にしています。初期化時にパラメータを指定するなど。なので、このanimejsのパッケージはどのような作りになっているのか調べました。

Usage

  • ES6 modules
  • CommonJS
  • File include

パッケージの使い方で、ライブラリの使用方法が上記の3方法あります。このうち、CommonJS...???と恥ずかしながら私はこの用語を知りませんでした。。。少し調べてみると、

CommonJS

  • モジュール管理の規格のひとつ。
  • require(<モジュール名>)でライブラリを読み込む仕様。
  • この記事が参考になりました。
  • node.jsでも使用する場合は必須のよう

これを踏まえてanimejsのpackage.jsonを見てみると作りは以下になっています。

animejsのpackage.jsonの一部
  "umd:main": "lib/anime.min.js", 
  "module": "lib/anime.es.js", 
  "main": "lib/anime.js", 
  "files": [ 
    "lib" 
  ], 

mainumd:とついてますが、UMDとはUniversal Module Definitionの略で、以下のように定義されています。(GitHubより抜粋)

The UMD pattern typically attempts to offer compatibility with the most popular script loaders of the day (e.g RequireJS amongst others). In many cases it uses AMD as a base, with special-casing added to handle CommonJS compatibility.

また不明な用語、、、AMDとはAsynchronous Module Definitionの略で、モジュールを非同期で読み込むため規格のようです。CommonJSの規格を非同期で読み込むための仕様。。。かな。それともう一つ、module fieldがあり、これの読み込み先のファイルがES2015基準のファイルを指していました。npmの仕様には

The main field is a module ID that is the primary entry point to your program. That is, if your package is named foo, and a user installs it, and then does require("foo"), then your main module’s exports object will be returned.
This should be a module ID relative to the root of your package folder.
For most modules, it makes the most sense to have a main script and often not much else.

このように書かれてあり、どうやらmain fieldにはCommonJS規格のファイルを指定する必要があるようです。ではこのmodulefieldは何だろうとしらべてみると、こちらに詳しく書いてありますが、

"module"フィールドに書くファイルパスは名前のとおりECMAScript モジュール形式のコードで、このコードはimport/exportのままのコードを配置します。webpackなどのbundlerは"module"フィールドが存在する場合はそちらを優先して読み込みます。

ES2015形式で書かれたコードのファイルを指定するfieldのようです。

CommonJS用にライブラリを書き出す

ライブラリを作成したのはES2015形式なので、これをCommonJSのファイルとして扱えるように書き出す必要があります。webpackでoutputしただけのファイルは、ライブラリのモジュールとしては使えないようです。やはりこれが読み込みができない原因と言えそうです。

ライブラリ用にビルドする

私が使っていたwebpackの設定はjsなどのアセットをまとめて、ブラウザで読み込める形に書き出すことはやりますが、ライブラリ用のモジュールとして書き出すことはしません。webpackでもライブラリ用にビルドする設定はあると思いますが、他にも調べてみるとRollupというモジュールバンドラがライブラリ開発に適しており、CommonJS形式に変換できるようです。基本的にこちらの記事を参考にすると変換までできます。記事元ではアウトプットのフォーマットがcjsとCommonJSのみの変換になっていたので、これでも良いと思いますが、今回私が参考にしたanimejsのパッケージはumd規格のファイルになっていたので、私もこれに習ってumd規格のファイルを作りました。

rollup.config.js
export default {
    input: './lib/ameba.es.js',
    output: [{
      file: './lib/ameba.js',
      format: 'umd',
      name: "p5-ameba"
    }],
    external: [
        'p5'
      ]
  };

formatのfieldのプロパティをumdに変えただけです。これでビルドして、CommonJSファイルを作り、ようやくnpmパッケージが使えるようになりました。

Conclusion

まだまだフロントエンドは分からないことが多く知らない用語がたくさんあります。それでも調べながらコツコツやっていくとレベルアップできるのではないかと感じました。このamebaライブラリはあまり役に立つものではないですが、パッケージ配布までの全体像が掴めたことは自分にとっては収穫です。その中で知らない単語を少しずつ吸収していけました。

ちなみに、ライブラリを開発していく中で、作ったライブラリを試すのにイチイチnpm publishとやって公開してから別プロジェクト作ってnpm install p5-amebaとやって確認作業していましたが、開発中のライブラリを擬似的にnode_modulesに入れて確認できるようです。

配布予定のライブラリを入れる/外す
npm link p5-ameba   <- node_modulesにいれる
npm unlink p5-ameba <- node_modulesから外す

今回作成したnpmパッケージはこちらです。
GitHubにもあります。

sample.gif

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

Express + passport(-twitter) + (PM2)で最速でTwitter認証機能付きWebAppを作る

いくつかTwitter認証を使うWebApplicationを作る過程でTwitterでの認証をテンプレート化させコピペで作れるようにしたのでそれをご紹介します。

この記事で出来るようになること

  • Twitter認証機能を持ったExpressAppをサクッと作れるようになる
  • WebAppとしても,ネイティブ使うAPIエンドポイントとしても使える
  • (後日出来るようになるかも) Swift4でのiOSネイティブからの当APIの使い方がサクッとわかる

前提

コピペで作るためには以下を満たしておく必要があります。

  • TwitterAppを作成できる

    • 以前にTwitterDeveloperの大規模な変更があって、TwitterAPPを作るためには作文を書かなきゃいけなかった気がします。
    • TwitterDeveloperにアクセスしてAppのリストとCreate an app ボタンが表示されればOK.
  • express-generatorが導入されている
    Expressプロジェクトをコマンド一つで生成してくれる便利な奴
    導入とか(公式)

  • PM2が導入されている
    ProcessManager(多分)
    nodeプロジェクトを手軽にデーモン化させられる奴(起動/ホットリロード/クラッシュ時の再起動/startup等々)
    導入とか(公式)

  • ドメインが用意されていてWebAppを公開できるようになっている
    長くなるので省きますが、https://xxx.your.domain/ とかにアクセスして生成したてのExpressを起動した際に

    Express
    Welcome to Express
    

    が表示されればOK.

今回はhttps://qiita.your.domain/というURLでAPIを公開してみたいと思います。

TwitterAppを作成

  1. twitterDeveloperからCreate an appを押し、App作成画面へ移ります。

  2. 必要事項を記述し、Allow this application to be used to sign in with Twitter項目のEnable Sign in with Twitterにチェックを入れます。

  3. Callback URLs項目がアクティブになるので、ここにhttps://qiita.your.domain/auth/twitter/callbackを入力します。
    ※ /auth/twitter/callbackの部分は本来任意ですが、これを変更する場合後述のルーティングのコードも変更する必要があります。

  4. 項目記述後、流れに沿ってAppを作成し最終的に自分のAppsDashboardに作成したAppが表示されていれば準備完了。

  5. AppsDashboardからAppを開き、keys and tokensのConsumer API Keysを確認できるようにしておきます。

実装

Express

まずはExpressを初期化します。

$ express -e qiita-twitter
$ cd qiita-twitter
$ npm i

次にpm2を初期化します

$ pm2 init
$ vi ecosystem.config.js

pm2

pm2の設定を変更します。

ecosystem.config.js
module.exports = {
    apps: [
        {
            name: 'qiita-twitter',
            script: 'npm',
            args: 'start',
            watch: true,
            env: {
                PORT: 3000,
                HOST_NAME: 'qiita.your.domain',
                USE_SSL: true,
                AUTH: {
                    twitter: {
                        active: true,
                        CONSUMER_KEY: 'Your CONSUMER_KEY',
                        CONSUMER_SECRET: 'Your CONSUMER_SECRET',
                    },
                }
            }
        }
    ]
};

nameはpm2上で管理するApp名です。
pm2 logs qiita-twitterとかするとログを見れたりします。

scriptとargsは起動時のコマンドです。
$ npm startで起動するので、このように記述します。

watchはホットリロード(ファイルを変更すると自動再起動してくれるやつ)を使うか否かです。
trueにすると起動ディレクトリ以下全てのファイルを、trueの代わりに['routes', 'models']みたいにするとroutesとmodelsディレクトリ以下を監視してくれるようです。

で、重要なのがenvです。

USE_SSLとかは後ほどコールバックURLを組み立てるときに使っていますが、そこでURLを直打ちしてしまえば必要ありません。

AUTH=>twitter=>activeみたいになってるのは、他にもlocal認証(よくあるメアドとPWの認証)やFaceBook認証とか使う時に分かるやすくなるメリットがあります。

この辺はコードと相談しながらお好みで変更しましょう。

gitで管理する場合ecosystem.config.jsはgitignoreに突っ込むなりしてgit監視下から外しましょう

個人的にはecosystem.config.js.sampleというenvの値を空白にしたものを用意しておき、これをgitに上げておくのがベターかなと思います。

休憩

一先ずここで起動確認をしておきましょう。

$ pm2 start ecosystem.config.js
$ pm2 logs qiita-twitter

適当なブラウザでhttps://qiita.your.domainにアクセスして

Express
Welcome to Experss

が表示されていればOKです。

Twitter認証

いよいよTwitter認証のroutingを実装します。

以下個人的に使っているディレクトリ構成となります。
お試す前にコードを読んで各自改変することをお勧めします。
(contorllers内でrouting設定しちゃってたりと健康被害を及ぼす可能性があります)

packageインストール

$ npm i -S express-session passport passport-twitter

ルーティング

$ mkdir controllers
$ touch controllers/authController.js
$ touch controllers/userController.js
controllers/authController.js
const passport = require('passport');
const TwitterStrategy = require('passport-twitter').Strategy;
const uc = require('./userController');

module.exports = { 
    initialize: function(app) {
        this.app = app;
        this.authSettings = JSON.parse(process.env.AUTH);

        app.use(passport.initialize());
        app.use(passport.session());
        passport.serializeUser(uc.userSerialize);
        passport.deserializeUser(uc.userDeserialize);

        this.twitterActivate();
    },
    twitterActivate: function() {
        if(!this.authSettings.twitter.active) {
            console.log('Twitter Auth is not activated');
            return;
        }
        let consumerKey = this.authSettings.twitter.CONSUMER_KEY;
        let consumerSecret = this.authSettings.twitter.CONSUMER_SECRET;
        let callbackURL = process.env.USE_SSL ? 'https' : 'http';
        callbackURL += '://';
        callbackURL += process.env.HOST_NAME;
        callbackURL += callbackURL.endsWith('/') ? '' : '/';
        callbackURL += 'auth/twitter/callback';
        passport.use(new TwitterStrategy({
            consumerKey: consumerKey,
            consumerSecret: consumerSecret,
            callbackURL: callbackURL
        }, uc.twitterAuth));

        this.app.get('/auth/twitter', passport.authenticate('twitter'));
        this.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) {
            const user = req.session.passport.user;
            res.json(user);
        });
        console.log('Twitter Auth is activated');
    },
}
controllers/userController.js
const ac = require('./authController');

module.exports = {
    twitterAuth: async function(token, tokenSecret, profile, done) {
        //本来ここでユーザー検索して、存在したらそのObjectを返す
        //存在しなければ新規ユーザー作成
        //みたいなことをする。
        //tokenとtokenSecretそのままobjectに格納してログイン状態として扱うのは良くないです。
        const userObj = {
            twitterId: profile.id,
            screenName: profile.username,
            token: token,
            tokenSecret: tokenSecret
        };

        //ログ表示もいくない。
        console.log(userObj);
        done(null, userObj);
    },
    userSerialize: async function(user, done) {
        done(null, user);
    },
    userDeserialize: async function(id, done) {
        console.log('user deserializing');
        console.log(id);
        done(null, id);
    },
};

app.jsから読み込み/初期化

$ vi app.js

最後にapp.jsを編集し、読み込み・初期化を行います。

app.js
//最魚のrequire群でsessionとauthControllerを読み込み(6行目くらい)
const session = require('express-session');
const authController = require('./controllers/authController');

//requireしたモジュール系をuseしている部分でsessionの読み込みをしてからauthControllerをinitialize(順番大事)を追加(23行目くらい)
app.use(session({
    secret: 'kokoikeapi',
    resave: false,
    saveUninitialized: true
}));
authController.initialize(app);

動作確認

最後にコールバックしてきたら、ユーザーのTweet一覧を表示してみましょう.

さらに体に良くないですが、authController内でTwitterモジュールを読み込んでお試し表示してみましょう。

$ npm i -S twitter
controllers/authController.js
//4行目くらいでrequireしておきます。
const Twitter = require('twitter');

// 36行目くらいの/auth/twitter/callbackのルーティングの処理の中身を変更
 const user = req.session.passport.user;
 const consumer = JSON.parse(process.env.AUTH).twitter;
 const client = new Twitter({
     consumer_key: consumer.CONSUMER_KEY,
     consumer_secret: consumer.CONSUMER_SECRET,
     access_token_key: user.token,
     access_token_secret: user.tokenSecret
 });

const params = { screen_name: user.screenName};
client.get('statuses/user_timeline', params, function(err, tweets, response) {
    if(!err) {
        res.json(tweets);
    } else {
        res.json(err);
    }
});

//res.json(user);

これで認証が終わってcallbackしてくると自分のツイート一覧のjsonがresponseに流れます。

本来の使い方としては、userController時点でuserデータを作成し、doneにそのuserデータを渡しておきます。
すると、authControllerで取得するuserにはidが含まれているので、これを使ってユーザーのプロフィールページやトップページにリダイレクトするといいと思います。

controllers/authController.js
this.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) {
    //トップへリダイレクトする場合
    //res.redirect('/');

   //user個別ページへリダイレクトする
   const user = req.session.passport.user;
   res.redirect('/users/' + user.id);

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

日付のチェック(フォーマットと閏年)

忘れないように

checkDate.ts
const checkDAte = (dateStr: string): string => {
  // リテラルの方が早いらしい
  // 2019-10-15のフォーマットの場合
  const reg_date = /^\d{4}-\d{1,2}-\d{1,2}$/.test(dateStr)
  if (!reg_date) {
    return 'invalid'
  }
  const parts = dateStr.split('-');
  const year = parseInt(parts[0], 10);
  const month = parseInt(parts[1], 10);
  const day = parseInt(parts[2], 10);
  const monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  // 閏年を考慮
  if (year % 400 === 0 || (year % 100 !== 0 && year % 4 ===0)) {
    monthLength[1] = 29;
  }
  if (!(day > 0 && day <= monthLength[month - 1])) {
    return 'invalid'
  }
  return 'valid'
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無限ループ∞最短選手権

さぁみんな無限ループしよう。

最近、無限ループが流行りらしいので
各プログラミング言語(その辺にあった10個の言語)の
無限ループを比べてみます。
果たしてどの言語が1位に輝くのか!?

※改行は1文字としてカウント。
(一応、全て実行してチェックしています)

C (24文字)

int main(void){for(;;);}

C⋕ (37文字)

class a{static void Main(){for(;;);}}

C++ (20文字)

int main(){for(;;);}

D (22文字)

void main(){for(;;){}}

Go (31文字)

package main
func main(){for{}}

Java (54文字)

class a{public static void main(String[] a){for(;;);}}

JavaScript (8文字)

for(;;);

PHP (14文字)

<?php
for(;;);

Python (9文字)

while 1:0

Ruby (11文字)

while 1
end

【優勝】JavaScript

チャンピオンは"JavaScript"でした。さすが"JS"
"JavaScript"は無限ループ界において最有力候補であると考えられますね。

???「JSが優勝だと思っていたのか。」

【真の優勝】Ruby ※追記

Ruby(6文字)
loop{}

Ruby、6文字で無限ループが出来るとは……。
恐るびー

最後に

もっと文字数減らせるよ!とか
もっと文字数が少ない言語あるぜ!最強だぜ!

などなどありましたらコメントまたは編集リクエストでお願い致します。

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

?‍♀️無限ループ∞最短選手権?‍♂️

さぁみんな無限ループしよう。

最近、無限ループが流行りらしいので
各プログラミング言語(その辺にあった10個の言語)の
無限ループを比べてみます。
果たしてどの言語が1位に輝くのか!?

※改行は1文字としてカウント。
(一応、全て実行してチェックしています)

C (24文字)

int main(void){for(;;);}

C⋕ (37文字)

class a{static void Main(){for(;;);}}

C++ (20文字)

int main(){for(;;);}

D (22文字)

void main(){for(;;){}}

Go (31文字)

package main
func main(){for{}}

Java (53文字)

class a{public static void main(String[]a){for(;;);}}

JavaScript (8文字)

for(;;);

PHP (14文字)

<?php
for(;;);

Python (9文字)

while 1:0

Ruby (11文字)

while 1
end

【優勝】JavaScript

チャンピオンは"JavaScript"でした。さすが"JS"
"JavaScript"は無限ループ界において最有力候補であると考えられますね。

???「JSが優勝だと思っていたのか。」

【真の優勝】Ruby ※追記

Ruby(6文字)
loop{}

Ruby、6文字で無限ループが出来るとは……。
恐るびー

???「6文字ごときが優勝だと思っていたのか。」

【本当の真の優勝】L00P ※追記(番外編)

L00P(0文字)

0文字……圧巻です。
言語名からして、無限ループ界の頂点に君臨していると思われる風貌をしてますね……。

このように上記で比べていた10個の言語以外の言語では、もっと文字数が少ないものがありました。
無限ループは奥が深い。

最後に

もっと文字数減らせるよ!とか
もっと文字数が少ない言語あるぜ!最強だぜ!

などなどありましたらコメントまたは編集リクエストでお願い致します。

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

無限アラートはブラクラではない

まずこの記事はChrome最新版に基づくものです。その他のブラウザの情報や検証結果などありましたらコメントいただけるとありがたいです。

今回の無限アラートがブラクラだとして、自社のセキュリティソフトを宣伝する悪質な会社などがあり、それの影響なのかブラクラだと勘違いする人が自分の観測範囲に多くいたので、もちろんエンジニアなら簡単にわかることですが、なぜブラクラではないのか技術的な観点から検証していきたいと思います。

まずブラクラ(ブラウザクラッシャー)は広い意味で使われているので、齟齬が無いようにこの記事内での言葉通りブラクラはブラウザをクラッシュさせるものだと定義します。実際にブラウザがクラッシュする他に著しく不安定になったり、正規の方法で閉じれなくなるようなこともクラッシュに含めブラウザのバグに起因するものは除外するとします。

無限アラートがブラクラではないことを検証する前に、ブラクラがどんな物なのか理解しやすいようにブラクラとされてきたものをいくつか見ていきましょう。比較するためにソースを載せますが、実行する際は必ず自己責任でお願いします。

実際のブラクラ

無限ループ

何もしないただの無限ループです。しかし何もしないというのは1回のループに時間がかからないことになり、短時間で大量の繰り返しが発生し無限に繰り返されます。あっという間にChromeが管理するCPU使用率が100%近くになりました。Chromeが不安定になりタブは閉じられませんしページの反応もありません。Chromeのタスクマネージャで実行したタブを強制終了することで終了できました。

while (true);

JavaScriptでは非同期性を実現するために、1つの処理が終わってからキューにある次の処理を実行していくイベントループというシステムというものがあり、スクリプトの実行以外にも描画を含めたページ全体の処理がイベントループ上に実装されています。しかし無限ループは処理が永遠に終わらないので、イベントループ自体をブロックしてしまいます。

※ちなみに一時期はGIFアニメーションですら、無限ループによってブロックされていました。(今は確認できていません)

無限に新しいページを開く

無限に新しいページを開く処理です。上記の無限ループの問題点に加えて新しいページが開かれるというコストがあるかと思いきや、2回目以降のwindow.open()はChromeによってブロックされるので1つしか新しいページは開かれません。しかし無限ループが止まるわけではないので上記の問題は顕在です。

while (true) window.open()

※ウィンドウが開かれていく状況を重視してあえて時間を開けているものもありましたね(今Chromeではどれも動きませんが...)

無限アラート

実際のブラクラを軽く見たところで、今回の無限アラートを見てみましょう。

while (true)と無限ループが入っていて、上記の無限ループの問題点を含むように一瞬思いますが、実際はalert()アラートが閉じられるまで処理がブロックされるので、一回閉じるごとに1回しかループが発生しません。閉じたときに一回だけループされますが、数十ミリ秒で連続して何百個も閉じれば変わってきますが、とても現実的ではないので負荷という観点からしたら問題はなさそうです。

while (true) alert('!')

本題とは少しそれますが、ブラクラに続いて言われる問題として、アラートが絶え間なく表示されることが上げられるので、それについても見てみましょう。

確かに上に書いた通りアラートはページ全体の処理を止めてしまうのでページの操作ができなくなります。しかしChromeではアラートが表示されていても、ウィンドウや、URLバー、メニュー、戻るボタン、タブの×ボタンや、ショートカットまで、ページ内以外は通常通り操作できタブの×ボタンから通常通りタブを閉じれます

終わりに

ということで、無限アラートはブラウザをクラッシュさせませんし、操作に支障もきたしませんつまりブラクラではないことがわかりました。

最後に僕はなにかある度に便乗してプログラムを有害だと決めつけて不安を煽り、自社のセキュリティソフトを宣伝する悪質な会社に反対します!そしてそんな会社によってCoinhive事件の被害者にされたモロさんには健闘を祈ります。

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

alert無限ループはブラクラではない

まずこの記事はChrome最新版に基づくものです。その他のブラウザの情報や検証結果などありましたらコメントいただけるとありがたいです。

今回の無限アラートがブラクラだとして、自社のセキュリティソフトを宣伝する悪質な会社などがあり、もちろんエンジニアなら簡単にわかることですが、その会社の影響なのかブラクラだと勘違いする人が自分の観測範囲に多くいたので、なぜブラクラではないのか技術的な観点から検証していきたいと思います。

まずブラクラ(ブラウザクラッシャー)は広い意味で使われているので、齟齬が無いようにこの記事内での言葉通りブラクラはブラウザをクラッシュさせるものだと定義します。実際にブラウザがクラッシュする他に著しく不安定になったり、正規の方法で閉じれなくなるようなこともクラッシュに含めブラウザのバグに起因するものは除外するとします。

無限アラートがブラクラではないことを検証する前に、ブラクラがどんな物なのか理解しやすいようにブラクラとされてきたものをいくつか見ていきましょう。比較するためにソースを載せますが、実行する際は必ず自己責任でお願いします。

実際のブラクラ

無限ループ

何もしないただの無限ループです。しかし何もしないというのは1回のループに時間がかからないことになり、短時間で大量の繰り返しが発生し無限に繰り返されます。あっという間にChromeが管理するCPU使用率が100%近くになりました。Chromeが不安定になりタブは閉じられませんしページの反応もありません。Chromeのタスクマネージャで実行したタブを強制終了することで終了できました。

while (true);

JavaScriptでは非同期性を実現するために、1つの処理が終わってからキューにある次の処理を実行していくイベントループというシステムというものがあり、スクリプトの実行以外にも描画を含めたページ全体の処理がイベントループ上に実装されています。しかし無限ループは処理が永遠に終わらないので、イベントループ自体をブロックしてしまいます。

※ちなみに一時期はGIFアニメーションですら、無限ループによってブロックされていました。(今は確認できていません)

無限に新しいページを開く

無限に新しいページを開く処理です。上記の無限ループの問題点に加えて新しいページが開かれるというコストがあるかと思いきや、2回目以降のwindow.open()はChromeによってブロックされるので1つしか新しいページは開かれません。しかし無限ループが止まるわけではないので上記の問題は顕在です。

while (true) window.open()

※ウィンドウが開かれていく状況を重視してあえて時間を開けているものもありましたね(今Chromeではどれも動きませんが...)

無限アラート

実際のブラクラを軽く見たところで、今回の無限アラートを見てみましょう。

while (true)と無限ループが入っていて、上記の無限ループの問題点を含むように一瞬思いますが、実際はalert()アラートが閉じられるまで処理がブロックされるので、一回閉じるごとに1回しかループが発生しません。閉じたときに一回だけループされますが、数十ミリ秒で連続して何百個も閉じれば変わってきますが、とても現実的ではないので負荷という観点からしたら問題はなさそうです。

while (true) alert('!')

本題とは少しそれますが、ブラクラに続いて言われる問題として、アラートが絶え間なく表示されることが上げられるので、それについても見てみましょう。

確かに上に書いた通りアラートはページ全体の処理を止めてしまうのでページの操作ができなくなります。しかしChromeではアラートが表示されていても、ウィンドウや、URLバー、メニュー、戻るボタン、タブの×ボタンや、ショートカットまで、ページ内以外は通常通り操作できタブの×ボタンから通常通りタブを閉じれます

終わりに

ということで、無限アラートはブラウザをクラッシュさせませんし、操作に支障もきたしませんつまりブラクラではないことがわかりました。

最後に僕はなにかある度に便乗してプログラムを有害だと決めつけて不安を煽り、自社のセキュリティソフトを宣伝する悪質な会社に反対します!そしてそんな会社によってCoinhive事件の被害者にされたモロさんには健闘を祈ります。

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

ざっくり入門TypeScript

概要

今流行りのTypeScriptに関して以下の流れでざっくりとまとめました。

TypeScriptとは?

TypeScriptとはJavaScriptを拡張したプログラミング言語で、「静的型付け」・「クラスベースオブジェクト指向」等の特徴を持ちます。

TypeScriptの利点

TypeScriptを使用する事には以下の様な利点があります。

  • 静的型付けで明らかなエラーを防ぐことが可能で、ディバグもより簡単。
  • クラスベースオブジェクト指向のため、大規模開発においての開発効率コストが削減ができる。

インストール

npmを使用する事で簡単にインストールできます。

$ npm install -g typescript

以下のコマンドでインストールが出来た事を確認しましょう。

$ tsc -v
$ Version 3.3.3333

コンパイル

TypeScriptのソースコード(.ts)は、そのままでは実行できず、JavaScriptにコンパイルしてから実行します。

以下の様にtscを使用する事でコンパイルが可能です。

$ tsc main.ts

複数のファイルを指定したり、全てのファイルを指定する事もできます。

# 複数ファイルのコンパイル
$ tsc main.ts workder.ts

# 全ての.tsファイルをコンパイル
$ tsc *.ts

--watchオプションを使用する事で、指定したファイルに変更がなされたら自動でコンパイルさせる事も可能です。

$ tsc main.ts --watch

静的型付け

TypeScriptの大きな特徴の1つが静的型付けです。
簡単に言うと、宣言する変数に格納できる型を指定できる機能です。

下の例を見ると、変数関数の仮引数関数の返す値 の全てに型を定義している事がわかります。

mail.ts
// 変数に型を与えて宣言。
var onigiri: string = 'onigiri', // String型
    calories: number = 200;  // Number型

// 関数の仮引数にも受け入れる型を定義。
// {}の前にあるstringは関数の返す値の型を定義している。
function todaysMeal(food: string, energy: number): string {
  return `My ${food} has ${energy} calories`
}

todaysMeal(onigiri, calories)

上記のTypeScriptファイルをコンパイルすると以下の様なJavascriptファイルになります。
Javascriptは型など知らないので、型付け部分は完全に無視されています。

var onigiri = 'onigiri', // String型
calories = 200; // Number型
function todaysMeal(food, energy) {
    return "My " + food + " has " + energy + " calories";
}
todaysMeal(onigiri, calories);

もし以下の様な不正なTypeScriptファイルをコンパイルしようとすると、エラーを発生させます。

// 変数に型を与えて宣言。
var calories: number = 'Calories';  // Number型
$ tsc main.ts
main.ts:3:5 - error TS2322: Type '"Calories"' is not assignable to type 'number'..

関数に誤まった型の値を渡した際にもコンパイル時にエラーを発生してくれます。

function todaysMeal(food: string, energy: number): string {
  return `My ${food} has ${energy} calories`
}

// Number型のenergyに文字列を渡している。
todaysMeal('FOOD', 'A LOT OF')
$ tsc main.ts
Argument of type '"A LOT OF"' is not assignable to parameter of type 'number'.

代表的な型

静的型付けを学んだ所で、TypeScriptで使用される代表的な型を学びましょう。

  • Number: 全てのNumericの値。
  • String: 文字列。
  • Boolean: trueかfalse
  • Any: この型をもつ変数は全ての型の値を持つ事ができます。
  • Void: 関数が何も値を返さない時にセットされる型です。

インターフェース

インターフェースはオブジェクトがある構造に合っているかどうかの型チェックに使用されます。
インターフェースを定義することによって、変数の特定の組み合わせに名前を付けることができ、それらが常に一緒になることを確実にします。

interface.ts
// Personインターフェースをとその要素を型と共に定義。
interface Person {
  name: string;
  age: number;
}

// 関数はPersonインターフェースの構造を満たすオブジェクトを受け取る。
function intro(person:Person): string {
  return `My name is ${person.name}. I am ${person.age}!`
}

// Personインターフェースの構造に従う変数を定義します。
var ken = {
  name: 'Ken',
  age: 20
}

// 関数を実行します。
intro(ken) //=> My name is Ken. I am 20!

上記のTypeScriptファイルをJavascriptにコンパイルすると以下の様になります。

function intro(person) {
    return "My name is " + person.name + ". I am " + person.age + "!";
}

var ken = {
    name: 'Ken',
    age: 20
};

intro(ken); //=> My name is Ken. I am 20!

クラスベースオブジェクト指向

JavascriptでもES6からclassを用いた記法が出来るようになりましたが、TypeScriptのクラスベースオブジェクト指向とはやや書き方が異なります。

class Menu {
  items: Array<string>; //文字列の配列を表します。
  pages: number

  constructor(item_list: Array<string>, total_pages: number) {
    this.items = item_list;
    this.pages = total_pages;
  }

  // Method
   list(): void {
     console.log("Our menu for today:");
     for(var i=0; i<this.items.length; i++) {
       console.log(this.items[i]);
     }
   }
}

// Menuクラスのインスタンスを作成します。
var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);

// メソッドを呼びます。
sundayMenu.list();

また、以下の様にしてクラスの継承が可能です。

// Menuクラスのインスタンスを作成します。
var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);

// メソッドを呼びます。
sundayMenu.list();


class HappyMeal extends Menu {
  // Properties は省略可能だがconstructorは定義する必要あり。
  constructor(item_list: Array<string>, total_pages: number) {
    super(item_list, total_pages);
  }
}

// HappyMealクラスのインスタンスを作成
var menu_for_children = new HappyMeal(["candy","drink","toy"], 1);

// 継承されたメソッドを実行。
menu_for_children.list();

参照

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

Reactで実装する虫眼鏡レンズ

はじめに

いいライブラリがなかったので、Reactで虫眼鏡のような拡大鏡をパワーで実装してみましたというパワー系の記事です

ポイント

ポイントはhoverした時点で、imageタグのopacityを0にし、background-imageを拡大していくことで拡大鏡を実装しています。
マウスカーソルの位置を取得して、その値に応じてtransform-originbackground-positionを変えていきます。

デモンストレーション

magnifying_glass.mov.gif

ソースコード

JS
const imageUrl = 'http://img.kb-cdn.com/imgviewer/NVpIM2ptOHhYRzVmUk5rM1NrNlFxYVV6enV4aGk2UFRJMmxPckdDUUVNYmF1RnpSNUZYSGVySnNpclp0dGpMT2xQcndrSmo1U0dxdHR4WmNjZHZoM2RKUmpwbktBZ0E5eDFOd0I0RFdsNE1XbS9NbE1QWFMxa2JCaVVDRzdNMUtNWHcrWGkxdjdUQ2Jya25ZZ2t4Z2M5MEM3MGdZUGwvTEx3RHRScVhBdzM2QjFYNVNHQ2trNDhnWUFlelBZU2Jr?square=0'

class ZoomImage extends React.Component {
  state = {
    backgroundImage: `url(${imageUrl})`,
    backgroundPosition: '0% 0%',
    transformOrigin: '50% 50%',
    transform: 'scale(1)',
  };

  handleMouseMove = event => {
    const {
      left,
      top,
      width,
      height,
    } = event.currentTarget.getBoundingClientRect();
    const x = ((event.pageX - left) / width) * 100;
    const y = ((event.pageY - top) / height) * 100;

    this.setState({
      ...this.state,
      transformOrigin: `${x}% ${y}%`,
      backgroundPosition: `${x}% ${y}%`,
    });
  };

  handleMouseOver = () => {
    this.setState({ ...this.state, transform: 'scale(2.5)' });
  };

  handleMouseLeave = () => {
    this.setState({ ...this.state, transform: 'scale(1)' });
  };

  render = () => {
    return (
      <div className="zoomImgContainer">
        <div
          onMouseMove={this.handleMouseMove}
          onMouseOver={this.handleMouseOver}
          onMouseLeave={this.handleMouseLeave}
          style={this.state}
          className="zoomImgSection"
        >
          <img
            className="zoomImg"
            src={`${imageUrl}`}
            alt="画像がありません"
          />
        </div>
      </div>
    );
  };
}

React.render(<ZoomImage />, document.getElementById('app'));
CSS(Stylus)
.zoomImgContainer
  overflow hidden
  width 416px
  height 416px
  margin 0 auto

.zoomImgSection
  background-repeat no-repeat

.zoomImgContainer:hover
  .zoomImg
    opacity 0

.zoomImg
  display block
  width 100%
  pointer-events none
HTML
<div id="app"></app>

CodePen

See the Pen React at CodePen by kazukiii (@kazukiii) on CodePen.

以上です。

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

GAS初心者が抑えるべきポイント徹底解説!

今回作成したのはタイトルにある通り、「GAS初心者が抑えるべきポイント徹底解説!」です!
今までいくつかに分けて書いていた記事をまとめる時間を作ったので、改めてまとめてみました。
https://bzbot.work/

紹介記事

https://bzbot.work/2019/03/07/gas-firststep/

image.png

1.GASとは

Googleが提供してるスクリプト『Google Apps Script』の略です。
このGASというスクリプトはGoogleが提供しているサービス(スプレッドシート等)をクラウド上で実行することで操作できるサービスを指します。

利用にお金は一切かからずGoogleアカウントがあれば無料で利用できるため、プログラマーを目指す初心者はまず学んで損はないです。
※GASとJavaScriptはイコールではありません!

2.GASのルール

GASは処理の時間が6分を超える事ができません。理由は単純にGoogle利用サービスを共通して使うクラウド上のリソースでで動くスクリプトのため、一人特定の重たい処理が実行されないように、6分で処理が終了するようになってます。

3.記述エディタ表示

GASを記述するスクリプトエディタを表示する方法です。エディタを表示する方法はスプレッドシートやGoogleFormサービスの様々なところにあるので、用途ごとに使い分ければOKです。

まず、GoogleDriveの画面から『新規』をクリックします。
すると、新規追加するべき項目の候補(フォルダやアップロード)が表示されるので、『その他』の中の『アプリ通知』をクリックします。

image.png
ドライブにアプリを追加が表示されるので、『+ 接続』をクリックして、Drive上に追加します。
image.png

image.png

4.コードの実行
これから実際にコードを書いて進めますが、書いた処理を実行する方法が記載されていなかったので、記載します。
虫のマークの横に『function xxxxx』に記載されている『xxxxx』がプルダウンで表示されるので、実行したい処理を選択します。

それ以外にも『 https://bzbot.work/ 』紹介しているので、ぜひみてみてください?

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

「StackOverflow から突然 Qiita に移動」を、App Extensions と React Native で簡単プロトタイプ

なんか実装でハマってしまい、うまくいかねーなって思いながら、StackOverflow を見ていたら、なんだか似たような質問 Qiita で見たし、やっぱり日本語で読みたいから Qiita に行こうかな、と思うことありませんか?
ありますよね!

それ、Action Extension でさくっと実装できます!
しかも、ほんの少しの Swift と Objective-C を書けば、あとは React Native で書けます。

ということでやってみました。

0. 本稿について

  • 対象者: iOS プログラミング初心者. Xcode といえば、xcode-select --installだと思っている人
  • 書いている人: iOS プログラミング初心者. Swift はprint("Hello, World")だけで卒業した
  • 必要な知識: JavaScript, React, React Native を触ったことがある
  • 動作環境: macOS X mojave. React Native 0.58.x. Xcode や Swift のバージョンはしらない
  • 内容: チュートリアル形式で Action Extension を React Native から使えるまでの実装方法を紹介する
  • 初公開日: 2019/03/06 更新日は上に書いてあるよ

1. React Native CLI のインストールとアプリのひな形の作成

では、さっそく元気に新プロジェクトつくってやっていきましょう。

$ yarn global add react-native-cli
$ cd /path/to/somewhere
$ react-native init So2Qiita

もし CamelCase なフォルダ名が気持ち悪ければ、ひな形作成後に、

$ mv SO2Qiita so2qiita

リネームしても大丈夫です。また、git にもコミットしておきます。

$ git init
$ git add -A
$ git ci -m "Create project via react native cli"

ここで一度実行してみる。
初めて起動する場合や、metro という React Native 用に Javascript の Bundle を行うプロセスが起動していない場合は cli から、

$ react-native run-ios

で起動。

ひとたび React-Native の画面のレンダリングがうまく実行できるようになれば、Objective-C や Swift を変更したとき、 Xcode からビルドすることも可能といえば可能です。
しかし、簡便のため、本記事で「実行する」と書いた場合は、CLI で、react-native run-iosしていると考えておいてください。

ひとたびビルドタスクがはじまると、マシンが熱をはらみ、CPU のほとんどを持っていかれるので、人間は大人しく wait 状態に入りましょう。

Simulator Screen Shot - iPhone 7 - 2019-03-04 at 19.47.12.png Simulator Screen Shot - iPhone X - 2019-03-04 at 19.54.00.png

さらに、App.jsxを編集して、Cmd+R すると、画面が変わることも確認できたでしょうか。

Simulator Screen Shot - iPhone X - 2019-03-04 at 19.55.09.png

2. Xcode を開いて、Action Extension を追加。

まずは言葉の定義から。

App Exntesions
  |- Share Extension
  |- Action Extension

App Extensions は総称。具体的な実装として、Share Extension と Action Extension がある。他にもあるかもしれないが知らない。

Share Extension は現在閲覧中の情報を他のアプリや SNS・サービスで共有するための拡張で、ActionExtension は現在閲覧中の情報に関連したタスクを実行するための拡張です。

このあたりの仕分けはけして自明ではなく、URL をブックマークするにしてもはてブを使うなら、Share Extension っぽいし、Safari のブックマークに残すなら、Action Extension っぽい。

実装としてはほぼ同じものになるので、Apple のガイドラインを読んだ上で、より自分の作る拡張に適した方を選んでください。

また、これらの拡張機能の設計指針についても、ガイドラインでしっかり触れられているので、軽く目を通しておいて損はない。
煩雑な UI にするなとか、単一の機能を持つように設計しろとか、まぁそういった内容。

今回は閲覧中の Stack Overflow のページからタグ情報を抜き出し、Qiita のページを開く、という動作で、Share じゃない感が強いし、Action Extension を選択します。

さて、Action Extension を作成すると決まったところで、いよいよ Xcode を開きます。
普段のお仕事がサーバーサイドだったら、Xcode を開くのは、年に数回あるかないかというところで、不慣れではありますが、怖がらないで、やっていきましょう。

open ios/So2Qiita.xcodeproj/

Xcode がおもむろにたちあがります。以下のメニューを選択して、

File > New > Target ...

Screen Shot 2019-03-04 at 19.33.06.png

ウィザードから、Action Extensionを選択。

Screen Shot 2019-03-04 at 19.33.26.png

Swift で書きたいんだけど、ここはいったん Objective-C を選んでおいてください。
名前は、So2QiitaExtとした。

Screen Shot 2019-03-04 at 19.34.06.png

Activateするか聞かれるので、当然Yes, "Activate"。

Screen Shot 2019-03-04 at 19.34.20.png

これで、空のActionExtensionができたことになる。

React Native を使えるようにするために、So2QiitaExtに依存関係として、JavaScriptCore.frameworklibRCTxxx.aが10個, それにlibReact.a というライブラリを追加します (tvOS 向けと間違いないようにしてください)。
あと、Qiita を WebView で開くので、libRNCWebView.aも追加しておいてください。

結果、こんな感じになる。

Screen Shot 2019-03-06 at 23.23.13.png

また、build phrasesの、Other Linker flags に、-ObjC -lc++を追加します。

Screen Shot 2019-03-06 at 23.24.57.png

謎めいた作業のように思われるかと思いますが、ここをきちんとしておかないと、後で詰むので、今がんばってください。

また、Deployment Info > Deployment Target が最新のものになっていると、Readt Native から run-ios したときの simulator から実行したときには deploy されないので、バージョンを合わせておきましょう。2019 年 3 月時点では、11.4 にする必要がありました。

Screen Shot 2019-03-04 at 19.59.07.png

ちなみに、asset 由来の color などが理由でビルドが失敗する場合、.xcassetsファイルは削除しても問題ありません。今回のチュートリアルでは使用しないです。

さて、ここでまた実行してみましょう。
実行後、Safari を起動して、Share ボタンをクリック。So2QiitaExt が無事に表示されていれば成功。

Simulator Screen Shot - iPhone 7 - 2019-03-04 at 20.00.48.png
Simulator Screen Shot - iPhone 7 - 2019-03-04 at 20.00.43.png

これからは毎回書かないけれど、section 毎に、git commitしておくと便利です。

3. Action Extension から React Native を呼び出す。

Xcode での作業がもう少し続きます。

いまSo2QiitaExtを開いて実行すると、画像を受け取り、そのまま表示するというデフォルトの動作になっています。
(ぜひ、Photos アプリから写真を選択して試してみてください)

Safari から実行しても画像が渡されないためなにも表示されず、空白のモーダルに、Done ボタンがあるだけです。
実はこの動作は、So2QiitaExt/ActionViewController.m内のviewDidLoadメソッドで定義されています。

このメソッドをごっそり削除して、代わりに

//  ActionViewController.m

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

をヘッダに追加してから、

//  ActionViewController.m

- (void)loadView {
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"So2Qiita"
                                               initialProperties:nil
                                                   launchOptions:nil];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  self.view = rootView;
}

をというメソッドを追加してください。
viewDidLoadloadViewはなにが違うのか、気になる方は、

UIViewController のライフサイクル - Qiita

などを参照のこと。

さて、これで実行し、Safari から Share ボタン経由で、So2QiitaExtを立ち上げると空のモーダルが出現します。
ふむ、これはなにかおかしいですね。
本来なら、コンテナアプリの起動画面と同様に"Welcome to React Native"が出て欲しいところです。

実はセキュリティ保護の観点からデフォルトでは localhost も含めて http 通信はブロックされており、かつ React Native は開発環境下では Hot Reload やデバッグを有効にするために、http で js.bundle をダウンロードして実行しているのです。

ということで、So2QiitaExtInfo.plist に以下の項目を追加します。

Screen Shot 2019-03-06 at 23.30.13.png

このXMLをコピペしてもいいよ。

<key>NSExceptionDomains</key>
<dict>
  <key>localhost</key>
  <dict>
    <key>NSExceptionAllowsInsecureHTTPLoads</key>
    <true/>
  </dict>
</dict>

そして、再実行。
今回はウェルカムメッセージがモーダル内に表示されたのではないでしょうか。

Simulator Screen Shot - iPhone X - 2019-03-04 at 21.53.40.png

しかし、なんと、モーダルを消すすべがありません。
いまのところ、「上へスワイプ」から Safari を kill することでなんとか終了してください。

4. Action Extension から呼び出されたとき用の画面を作る

モーダルを消す方法は少し先延ばしにして、ここからは Action Extension 用の画面を作っていきましょう。
方策としては、

  1. initialPropertiesに Action Extension からのリクエストの場合のみ True になるフラグを設定して分岐する
  2. エントリーポイントとなるファイルをindex.jsではないなにかに変える

が考えられます。正直、どちらでもかまわないのですが、今回は特にファイルを分けるほどの理由もないので、方策 1 を採用しましょう。

loadView メソッドに

//  ActionViewController.m

NSDictionary *initialProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool: TRUE] forKey:@"isExtension"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                    moduleName:@"So2Qiita"
                                             initialProperties:initialProps
                                                 launchOptions:nil];

を追加。
RCTRootView のイニシャライザに、 initialProperties として、{isExtension: true}を渡すようにします。

そして、React Native 側では、

// App.js

export default class App extends Component<Props> {
  render() {
    const { isExtension } = this.props;
    let message;
    if (isExtension) {
      message = "Welcome to So2QiitaExt on React Native!";
    } else {
      message = "Welcome to So2Qiita on React Native!";
    }

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>{message}</Text>
      </View>
    );
  }
}

isExtensionによって、表示するメッセージを変えてみます。実行。

Simulator Screen Shot - iPhone X - 2019-03-04 at 22.07.25.png

やりましたね。

5. モーダルを終了するためのボタンを作る

では、次に、モーダルを閉じるためのボタンを作りましょう。
今までは iOS からのアクションやメッセージを受け取るだけでしたが、今回はじめて React Native 側から、iOS へメッセージを送ることになります。
これを実現するためには、iOS 側でメッセージを受け取るためのインタフェースを用意する必要があります。

せっかくですので、Objective-C ではなくて、Swift でそのブリッジ部分を作ってみましょう。
ここがたぶん、このチュートリアルの一番難しいところ。
みなさん、無事に乗り切ってください。

まずは、Swift ファイルをSo2QiitaExtに追加します。New Fileから、Swift を選び、ActionExtensionという名前でファイルを作ります。

Screen Shot 2019-03-04 at 22.45.21.png

ブリッジファイルを作るかと聞かれるので、Create Bridging Header を選んで、作成してください。

Screen Shot 2019-03-04 at 22.45.32.png

まだファイルの中は変えなくていいです。
同名の Objective-C ファイル、ActionExtensionも作りましょう。

こういったファイルがSo2QiitaExt以下に追加されているはずです。

Screen Shot 2019-03-04 at 22.57.56.png

ここでブリッジ部分の実装を行いますが、Swift だけでは完結せず、Objective-C から、モジュールやメソッドを Extern 宣言、すなわち外部公開する必要があります。

ActionExtensionというクラスと、doneというメソッドを定義して、Javascript からアクセスできるようにしてみましょう。

まずはブリッジヘッダに必要なヘッダを追加

// So2QiitaExt-Bridging-Header.h
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "React/RCTBridgeModule.h"
#import "ActionViewController.h"

外部公開用の宣言を追加

//  ActionExtension.m
#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(ActionExtension, NSObject)

RCT_EXTERN_METHOD(done)

@end

そして、Swiftで実装

// ActionExtension.swift

import Foundation
import os.log

let log = OSLog(subsystem: "com.o3c9.so2qiita", category: "ActionExtension")

@objc(ActionExtension)
class ActionExtension: NSObject {
  @objc
  func done() {
    os_log("done", log: log, type: .default)
  }
}

os_logを仕込むと、Console.appにログを吐き出すことができるようになります。
これが今後君の命綱となる。

そして、App.jsに、doneメソッドを呼び出すコードを追加しましょう。

// App.js

export default class App extends Component {
  _onPress() {
    NativeModules.ActionExtension.done();
  }

  render() {
    ...

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>{message}</Text>
        {isExtension && <Button onPress={this._onPress} title="Done" />}
      </View>
    );
  }
}

そんでもって実行〜。

Donwをタップすると、無事にSwiftのdoneメソッドが呼ばれ、ログに表示されていますね。地味ですが大きな意味を持つ一歩です。

Screen Shot 2019-03-05 at 21.35.12.png

では、doneの実装を行い、本当にモーダルを閉じることができるようにします。

まずは、ActionViewController.hactionViewControllerインスタンスの外部公開と、doneメソッドの宣言を追加します。

// ActionViewController.h

#import <UIKit/UIKit.h>

@interface ActionViewController : UIViewController

extern ActionViewController * actionViewController;

- (void) done;

@end

実装ファイルでは、actionViewControllerに値をセット。done メソッドは初めに作成したひな形にすでに実装されてあるので、このままこれを流用して Swift から呼ぶという算段です。

//  ActionViewController.m

ActionViewController * actionViewController = nil;

@implementation ActionViewController

- (void)loadView {
  ...

  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:0.0f blue:0.0f alpha:0.3];
  self.view = rootView;
  actionViewController = self;
}

で、ActionExtension.swift。

// ActionExtension.swift

class ActionExtension: NSObject {
  @objc
  func done() {
    os_log("done", log: log, type: .default)
    actionViewController.done()
  }
}

はい、また、実行。

qiita_action.gif

よかったね、無事モーダルが隠れました。

6. App Extensions から現在の URL を受け取る

ずいぶん長くなりましたが、このセクションが本機能の肝心な部分。Action Extension が起動された元のアプリから URL を JavaScript で受け取ります。
今回の場合、Safari から StackOverflow の Question の URL が受け取りたい情報ですね。

まずは、JavaScript 側の実装のイメージ。関数のcallbackを使う場合。

NativeModules.ActionExteion.url( (error, url) => if(!error) this.setState({ url }));

Promiseとして受け取る場合.

NativeModules.ActionExteion.url()
  .then( (url) -> this.setState({ url }))
  .catch(e => console.log(e));

どっちでもいいんだけど、好みで、Promise でやってみましょう。
async awaitを使ってモダンにやるぞ。

まずはurlというメソッドを定義し、Promise を返すメソッドであるという宣言をブリッジ部分に書く。おまじないです。

// ActionExtension.m

RCT_EXTERN_METHOD(url: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject)

そして、Swift ファイルでの実装。

// ActionExtension.swift

@objc(ActionExtension)
class ActionExtension: NSObject {
  ...

  @objc
  func url(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
    guard
      let inputItem = actionViewController.extensionContext?.inputItems.first as? NSExtensionItem,
      let attachments = inputItem.attachments
      else {
        let error = NSError(domain: "", code: 400, userInfo: nil)
        reject("E_URL", "cannot obtain url", error)
        return
    }

    for provider in attachments {
      if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
        provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (target, error) in
          let url = target as! URL
          resolve(url.absoluteString)
        })
        break
      }
    }
  }
}

まぁ、こういうの、正直すべてを正しく理解するのにはたくさん寄り道をせなあかんので、全部は説明しきれないです。
ただ Swift や iOS プログラミングに関心があれば、ぜひ Apple の公式ドキュメントや有志の解説記事を探して読んでみてください。

React Native とのつなぎこみに関しては、React Native の Official のドキュメントか、Medium で人気 TeaBreak の記事、Swift in React Native - The Ultimate Guide Part 1: Modulesが詳しいです。

// App.js

async componentDidMount() {
  try {
    const url = await NativeModules.ActionExtension.url();
    this.setState({ url });
  catch(error) {
    console.log(error);
  }
}

また、実行。

Simulator Screen Shot - iPhone X - 2019-03-05 at 22.39.11.png

やった、URLが表示されてますね。これで、Action Extension から URL を React Native の JavaScript で受け取ることができるようになりました。
めでたし、めでたし。

Action Extension を React Native と組み合わせて使う、という書きたかった内容はここまででほとんどすべて。

  • doneメソッドで、JS から Swift 側へメッセージを送る。
  • urlメソッドで、JS から Swift 側へデータを要求し受け取る。

この 2 つができるようになったわけです。

あとは、機能を完成させるために React Native 側での実装をさくっと紹介して終わることにします。

7. StackOverflow のタグを使って Qiita で検索する JavaScript

Safari から送られてきた URL を使って、StackOverflow のタグを Qiita 検索に使うというロジックの実装に入ります。

まずは、Component での実装のイメージ。
(補足すると、今の時点ではあくまでイメージだったはずが、この後、このイメージに沿って実装が進むため、結局これがそのまま Component の実装となっていく)

async componentDidMount() {
  try {
    // URLをNativeModules経由で、上記のActionExtensionクラスから受け取る & Wait
    const url = await NativeModules.ActionExtension.url();
    // そのURLをStackOverflow APIを実装したクラスに渡して、タグを受け取る & Wait
    const tags = await new StackOverflow(url).getTags();
    // タグからQiitaの検索クエリを構築
    const query = encodeURIComponent(tags.map(t => `tag:${t}`).join(" "));
    const uri = `https://qiita.com/search?utf8=%E2%9C%93&q=${query}`;
    // stateにつっこむ
    this.setState({ isLoading: false, uri });
  } catch (error) {
    this.setState({ isLoading: false, error });
  }
}

StackOverflow.jsというクラスを作って、ダミーの実装をする.

export default class StackOverflow {
  constructor(url) {
    this.url = url;
  }

  getTags() {
    return Promise.resolve(["javascript", "reactjs"]);
  }
}
class Extension extends Component {
  render() {
    if (isLoading) {
      return (
        <SafeAreaView style={styles.container}>
          <ActivityIndicator size="large" color="#0000ff" />
        </SafeAreaView>
      );
    } else if (uri) {
      return (
        <SafeAreaView style={styles.extension}>
          <Text>{uri}</Text>
        </SafeAreaView>
      );
    } else {
      return (
        <SafeAreaView style={styles.container}>
          <Text style={styles.error}>{error}</Text>
        </SafeAreaView>
      );
    }
  }
}

次に、Qiitaの検索結果ページを、WebView で表示させてみる。
WebViewはReact Native本体から分離されて別パッケージになったようなので、yarnで追加。

yarn add react-native-webview
react-native link react-native-webview

Xcode のビルド設定を見て、So2QiitaExtlibRNCWebView.aが含まれているか確認しよう。
入ってないと、"Invariant Violation: requireNativeComponent: "RNCWebView" was not found" が襲いかかってくる。

あと、全面 WebView だとせっかくつくったdoneメソッドを呼べなくなるので、NavigationBarも追加しておこう。

yarn add react-native-navbar

stateuriがあるときの View はこんな感じになる。

<SafeAreaView style={styles.extension}>
  <NavigationBar
    title={{ title: "So2QiitaExt" }}
    leftButton={{ title: "Done", handler: this._onPress }}
  />
  <WebView style={styles.webview} source={{ uri }} />
</SafeAreaView>

うまくいけば、たぶん見慣れたサイトがモーダル上に表示される。

Simulator Screen Shot - iPhone X - 2019-03-06 at 23.44.34.png

最後に、StackOverflow.getTags()をちゃんとした実装にする。
StackAppsというところから、API Keyの登録をしなくちゃいけないと思っていたが、実はこのエンドポイントはpublicなようで、認証なしで呼べる。

APIの詳細は、この辺に転がってます。

constructor(url) {
  this.questionId = this._parseUrl(url);
}

getTags() {
  return new Promise(async (resolve, reject) => {
    if (this.questionId) {
      try {
        const response = await fetch(
          `https://api.stackexchange.com/2.2/questions/${this.questionId}?site=stackoverflow`
        );
        if (response.ok) {
          const json = await response.json();
          const tags = json.items[0].tags;
          return resolve(tags);
        } else {
          return reject(`http error: ${response.status}`);
        }
      } catch (error) {
        return reject(error.message);
      }
    } else {
      return reject("not a valid SO url");
    }
  });
}

URL_REGEX = /^https\:\/\/stackoverflow\.com\/questions\/(\d+)/;

_parseUrl(url) {
  const result = url.match(this.URL_REGEX);
  return result && result[1];
}

はい、これで完成 :sparkles:

so2qiita.gif

どうだ、このセクションでは、Js しか書いてないぞ!
Test だって、Jest で書けるぞ!

ということで、React Native から、Action Extension を使う方法を紹介しました。
ごくろうさまでした。

最終的な成果物は、

https://github.com/o3c9/so2qiita

ここで公開しています。
参考になったと思えば、ぜひStar :star: をよろしくです!

8. FAQ -結びにかえて

それ、全部 Swift で書いた方が早くない?

ほとんどの場合そうかもしれないが、コンテナアプリが React Native で書かれている場合、App Extensions でもその資産を使いたいことはあるはず。そういった場合には、なるべくすべて React Native にしておくほうがいいはずなので、ここで紹介したテクニックは使えると思う。

この機能、微妙じゃね?

タグ検索だけだと微妙だけど、タイトルから重要なキーワードを推測して適切な検索クエリを構築できるようになると、けっこう実用的だと思っている。それなりに自然言語処理がんばってやらないと使い物にならないだろうけどね。

エンジニアは全員英語で読み書きできるべきだし、日本語のQiita見るより英語のStack Overflowを参照するべきでは?

You Qiita2So 作るべき

Share Extension の例もほしい

表示されるカラムなどが違い、UI としては別物に見えるけど、実質ほぼ同じものなので、Action Extension が作れたら、Share Extension も作ることができる #はず #未確認 #誰かやってみて #コメント欲しい

チュートリアル通りやっても動かない

うん、それが現実。

https://github.com/o3c9/so2qiita に完成したコードあるから、これcloneして動かしてみてください。

途中経過の再現については Xcode の設定や、コードをにらめっこしながら、差分を見つけてみましょう。
簡単じゃないけど、難しくもないはず。時間はかかるけど、こういうのがいい勉強になるよ。

Xcode 上での作業が多い、もっとラクにできないの?

そう思ってた頃もあった。

React Native Share Extensionというものがあって、わざわざライブラリになっているにもかかわらず、README に書いてある Setup のプロセスがめっちゃ長くて大変そう。
つまり、ここに書いたくらいのステップがほぼ現在のところ最短だと思う。
一回やってみて要領つかめたら、次からは怖くない

Extension のデバッグつらくない?

うん、つらい。

NativeModules.ActionExtension.xxxが不要な場合には、Extension の画面をコンテナアプリのトップ画面にして開発すると、JS 由来の動作確認がしやすくなる。

Extension として実行しているときは、console.log の代わりに、自前の logger を作って、React Native の画面に出してしまうというのをやっていた。こんな感じ。

debug(message) {
  this.setState( { console: [...this.state.console, message] })
}

render() {
  return (
    <View>
      ...
      {this.state.console.map(m => <Text>{m}</Text>)}
    </View>
  )
}

iOS 側は、os_logを使えば、Console.app から見えるようになる。
他の大量のメッセージにかき消されるという苦難はあるけれど。
Process を限定してみれば、いちおう追えないことはない。

Android は?

また今度。
https://github.com/o3c9/so2qiita のStarの進捗次第かな

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