- 投稿日:2019-03-27T23:39:48+09:00
XX is not a functionエラーがどうしても解決しないときの可能性
こんにちは。俳句や川柳から動くGIF画像を生成できるWebサービス「五七五メーカー」をリリースしたアカネヤ(@ToshioAkaneya)です。
JavaScriptで、
XX is not a functionというエラーがどうしても解決しないことがあったので、参考になれば幸いです。XX is not a functionエラーがどうしても解決しないときの可能性
セミコロンが絡んでいる可能性があります。
そのエラーは、次の様な状況で起きていました。
console.logが関数じゃないだって...!?
これは、一行目にセミコロンが無いからです。
これなら大丈夫です。
ちなみに、Numberリテラルにメソッド呼び出しを行うときは、32..toString()の様にドットを付ける方法もあります。
上の様なセミコロンのエラーが起きないし、簡潔なのでこちらを使う様にしましょう。はてなブックマーク・Pocketはこちらから
- 投稿日:2019-03-27T22:46:28+09:00
ブラウザで動作するJavaScriptのdeepEquals(不完全)を書いたけど、fast-deep-equalを使います
(不完全)と前置きする感じ、弱気である。
雑にdeepEqualsしたい、という要件があったので。
npmにはもちろん上がっているけど、ブラウザでちょっと試しに使いたい感じだったので、ググってすぐ見つからなかったので書いた。https://jsfiddle.net/upcme7ob/
対応しているのは、null,undefined,number,string,object,array。
(boolean同士がない事に気づく。「こんなん書かなくてもあってるでしょ」とタカをくくって書いてないですね・・・よくない・・・)2019-03-26時点のモダンなEdge,Chrome,Firefoxでエラーが出てないことは確認。Safari、モバイルは知らない。
雰囲気で適当なテストを書きながら実装してたので、テストしてないパターンありそう。Functionからインスタンスを作る感じのやつも多分大丈夫だけど、Functionを作ってるようなものは無理かもしれない。
NaNとかSymbolとかは見てない。循環参照も知らない。こちらからすれば、そんな汚いオブジェクトを作る方が悪い。他を見てみましょう
こういう書いた系は、いつもある程度満足いくところまで実装してから他の実装見ています。なぜ逆にしないのか。
Node 実装
最初はnodejs実装を参考にしようとしたけど、
なんかめんどくさそうだったので諦めた。NodeJSの実装は以下。setを持ちまわる感じ、循環参照を考慮してる感はある。Symbolも見てるっぽい。よくわからない。
fast-deep-equal
https://www.npmjs.com/package/fast-deep-equal
code: https://github.com/epoberezkin/fast-deep-equal/blob/master/index.js
こちらは1300万DL/weekな実装。テストか何かの依存なんだろうか
何度もオブジェクトを辿らないようにしたり、varを1回で済ませてたり、何度もvarしないように頑張っている感がある。コンパクトだし読みやすい。
export等を削れば自分が用意したテストケースでブラウザ上でも動いた。本番で使うならこっち使います。node-deep-equal
https://www.npmjs.com/package/deep-equal
code: https://github.com/substack/node-deep-equal/blob/master/index.js
こちら600万DL/weekな実装。名前は一番良い。でもDate型とかも見てるし、なんか読みにくく感じてあまり見てない。
他にもあるみたいだけどどんぐりの背比べのような気がしたので見てない。
真のdeepEqualsを見せてくれる方を募集しております。
- 投稿日:2019-03-27T21:21:56+09:00
新元号はこれに決定
思わせぶりなタイトルですみません。
新元号はリークしたら変更されるから予想出来ないとか、みなさん色々言われていますが、新しい元号はもう昨年にはとっくに決まっていたのです。
よろしいですか?新しい元号は「㋿」です。
・・・これが豆腐にしか見えないみなさんも、いずれ私の言うことが正しかったと分かるはずです。
それでは、せっかくのQiitaですので、新しい元号を出力してクリップボードに入れるプログラムを紹介します。
WindowsでPowershellを開く、もしくはコマンドプロンプトを開いて
powershellを実行し、下記のスクリプトを実行しましょう。"新元号は" + [char]::ConvertFromUtf32([int]"0x32ff") + "。" | clipせっかくなので、JavaScript版も紹介します。
console.log("新元号は「\u32ff」。")というわけで、unicode.orgにもあるように、
32FFがcode point reserved for pending Japanese era nameで新元号に決まっていますので、みなさんお忘れなきよう。各種申請書類にも、"\u32FF元年 5 月 1 日" などと書き込むようにしましょう。
・・・フォントがいつアップデートされるかは、みなさんの環境次第ですが。
最後に
よろしければ、コメントで各言語での新元号表示方法を教えてください。
それから@BotUnicode というBotを運用しています。よろしくお願いします。
- 投稿日:2019-03-27T20:08:39+09:00
状態管理ツールdobについて
dobjs/dob についての解説です。
dobとは
Light and fast ? state management tool using proxyと書かれているように、軽くて速い状態管理ツールです。
特定のフレームワークに依存することなく使えます。
現状フロントエンドの状態管理ツールは、特定のフレームワークに依存しているものが殆どですが、このツールをうまく利用すれば、Viewフレームワークだけを取り替えることが出来ると考えています。もちろんサーバー側でも使えます。dobのバックグラウンド
まずは、dobで利用されているJavaScriptの標準機能Proxyについてざっくり説明します。
(ここは表面的な部分からは触れないので、読み飛ばしても大丈夫です)
Proxy オブジェクトとは、プロパティの検索・代入・削除等の操作をしたときに、独自の動作を定義することが出来るものです。
例として、猫の体重をチェックするコードは以下のようになります。
{ const handler = { set (target, prop, value) { if (value < 10) { console.log(`現在${value}kgです。`) target[prop] = value return true } throw new Error(`食事内容を変更してください。`) } } const cat = new Proxy({ weight: 2 }, handler) cat.weight += 1 // 現在3kgです。 cat.weight += 2 // 現在5kgです。 cat.weight += 10 // Error: 食事内容を変更してください。 }この機能を利用し、値の変更を検知をすることが出来るツールです。
dobのざっくり簡単な使い方
observableでobjectを監視対象とし、observeでpropertyの変更を検知し処理を実行します。{ const { observable, observe, Action } = dob const dog = observable({ name: 'ポチ', gender: 'male', age: 2, condition: { happy: true, hunger: false } }) // observe定義時に、渡した関数が即時実行されます。 observe(() => { console.log(dog.name, dog.gender) if (dog.condition.hungry) { console.log('ご飯を食べさせましょう。') } }) // ポチ male // observe内でアクセスされたpropertyを変更すると検知対象となります。 dog.name = 'ポチ太郎' // ポチ太郎 male // observe内で参照されていないpropertyを変更しても何も起きません。 dog.age += 1 // Actionを使うことで、値を複数回変更しても、コールバックが一度のみ実行されるようになります。 Action(() => { dog.name = 'ポチ美' dog.gender = 'female' }) // ポチ美 female // object を持つ property についても同様に扱えます。 dog.condition.hungry = true // ポチ美 female // ご飯を食べさせましょう。 }上記のように、property値の変更をすることで、コールバックが呼び出されていることが確認できるかと思います。
気を付ける点
自分がはまったことのある点をいくつか紹介します。
配列操作
以下のように、配列自体を監視すると、配列に値を追加してもコールバックが呼ばれません。
{ const { observable, observe, Action } = dob const shop = observable({ fruits: ['?', '?', '?'] }) observe(() => { console.log(shop.fruits, 'フルーツが変更されました') }) // Proxy {}, フルーツが変更されました。 // 以下を実行しても、配列自体は同じものと見なされるため、コールバックが実行されない。 shop.fruits.push('?') }上記のケースの場合は、
shop.fruits.lengthを参照する等、配列操作によって変更される部分を監視する必要があります。外部関数
observeの中で別の関数を呼び出しても、その関数で使われたpropertyは検知対象となります。{ const { observable, observe, Action } = dob const tree = observable({ root: 1, leaf: 100 }) function treeNode () { return tree.root + tree.leaf } observe(() => { console.log(tree.root, treeNode()) }) // 1 101 // treeNodeで参照しているため、以下の処理は検知対象となる。 ++tree.leaf // 1 102 }仕組みを理解すると当然と言えば当然なんですが、外側の関数は割と見落としがちなので、注意が必要です。
どうしても検知対象から外したい場合は、setTimeout(fn, 0)等を呼び出す必要があります。自己検知による無限ループ
observe時に、自分自身が参照している値を変更すると無限ループになります。
{ const { observable, observe, Action } = dob const game = observable({ count: 1 }) observe(() => { ++game.count // InternalError: too much recursion }) }observe時に値を変更したい場合は、以下のように
Actionでくくる必要があります。(参考issue){ const { observable, observe, Action } = dob const game = observable({ count: 1 }) observe(() => { Action(() => { ++game.count // no loop }) }) }検知対象とならないproperty
検知対象となるルールとして、observeに渡したコールバック関数が、最後に実行されたときに読み取られたpropertyが検知対象となります。
以下では、分岐により読み取られなかったpropertyを変更しても実行されない例です。
{ const { observable, observe, Action } = dob const game = observable({ isPlay: false, title: 'マリオ' }) observe(() => { console.log(game.isPlay ? 'ゲームプレイ中' : 'ゲームしてません') if (game.isPlay) { console.log(`「${game.title}」をプレイしています。`) } }) // ゲームしてません // 以下のproperty変更は、最後に呼び出されたコールバックで読み取られていないため、呼び出されません。 game.title = '?の大冒険' game.isPlay = true // ゲームプレイ中 // 「?の大冒険」をプレイしています。 // 以下のproperty変更は、最後に呼び出されたコールバックで読み取られたため、呼び出されます。 game.title = '?クエスト' // ゲームプレイ中 // 「?クエスト」をプレイしています。 }これらを考慮するのは大変なため、分岐で使ったpropertyは変更の有無に関わらず問題が無いような作りを考えたほうが楽です。
自分はコールバックの頭にvoid (game.title, ...)のように、監視したいpropertyを並べて、分岐による処理がされないように施したりしています。試してみる
上記のコードは https://observablehq.com/@ampcpmgp/dob-example に置いてあります。devtoolsのconsole画面を開いて、画面内コードセル上にある右上の実行ボタンを押すことで、実際の動作を確認できます。編集して実行しても大丈夫です。(Observableもとても良いツールなので使ってみてください?)
所感
objectの変更検知が、他ツールに比べて直感的でわかりやすいと思います。
特定のフレームワークへの依存性が小さいという点も、本来のエコシステムにより近づくように思います。ちょっと古い記事ですが、jsのProxyを利用した状態管理ツールを試してみたで他にも軽く試したことがあるので、良かったら参考にしてください。
以上です。
- 投稿日:2019-03-27T19:45:26+09:00
GraphQLの勉強 part1~基本操作~
GraphQLとは
簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。
例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリーこれはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定するGraphQLの利点
1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)3.簡単
一つのエンドポイントしか管理しなくてよいからHello GraphQL
サーバー ⇒ クライアント で既存のデータを取得してみる
1 ) プロジェクトの初期化とBabelのインスト-ル
> npm init > npm install babel-cli babel-preset-env ※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要 ※ babel-preset-env:Babelに何をパースすべきか伝える2 ) package.json / .babelrc / index.js を以下のように作成
3 ) 「npm run start」 でコンパイルしてBabelの動作確認> npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js hello4 ) GraphQL Yogaのセットアップ
GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。
ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。
> npm i graphql-yogaダミーのデータをサーバーに準備しつつセットアップ。
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Type definition (Schema) const typeDefs = ` type Query { hello: String! name: String! } ` // Resolvers const resolvers = { Query : { hello() { return 'Hello GraphQL' }, name() { return 'Hugo' } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- typeDefs でサポートする型を定義する(スキーマの定義)
- resolvers でクエリに対する処理を定義する
- serverのインスタンスを作成する
- serverを立ち上げる
※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)
5 ) 実行
>npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js server is upこの状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。> npm install nodemon --save-devnodemonをインストールしたら package.json の scripts を以下のように書き変える。
package.json"scripts": { "start": "nodemon src/index.js --exec babel-node" }index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。GraphQL Types
【Scalar Type】
5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/falseScalarType// Type definition (Schema) const typeDefs = ` type Query { id: ID! name: String! age: Int! height: Float! isAdult: Boolean! } `【Custom Type】
開発者が定義する type。
CustomType// Type definition (Schema) const typeDefs = ` type Query { me: User! } type User { id: ID! name: String! email: String! age: Int } ` // Resolvers const resolvers = { Query : { me(){ return { id: '12345', name: 'Hugo', email: 'hugo@example.com', age: 27 } } } } `Resolver Operation Arguments
index.js// 引数に '!' が有る場合、その引数は required const typeDefs = ` type Query { greeting(name: String, position: String): String! add(a:Float!, b:Float!): Float! } ` const resolvers = { Query : { greeting(parent, args, ctx, info) { if(args.name && args.position) { return `Hello, ${args.name}! You are my favorite ${args.positino}`; } else { return 'Hello!' } }, add(parent, args){ return args.a + args.b } } }Resolverには4つの引数が存在する
- parent : Type間にリレーションがある場合の親を指す
- args : operation arguments supplied
- ctx : contexutual data (ユーザの情報)
- info : サーバーに送信されたアクションの情報など
Array Type の扱い
【Arrays of Scalar types】
index.jsconst typeDefs = ` type Query { add(numbers: [Float!]!): Float! } ` const resolvers = { Query : { add(parent, args, ctx, info){ if(args.numbers.length === 0 ) { return 0 } return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue ) } }【Arrays of Custom types】
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'mike@ex.com' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! } type User { id: ID! name: String! email: String! age: Int } ` const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query)) } } }Relation
Type の ID を追加することでリレーションが生まれる。
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'aaa', body: 'asdtfhaer', published: true, author: '1' /// 注目 }, { id: '5', title: 'fff', body: 'hrtea', published: true, author: '1' /// 注目 }, { id: '6', title: 'Ahwerike', body: 'aikreshe', published: true, author: '2' /// 注目 }] const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! /// 注目 } type Post { id: ID! title: String! body: String! published: Boolean! author: User! /// 注目 } `そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる
index.jsconst resolvers = { Query : { users(parent, args, ctx, info) { return users // call posts() coded below (userの数だけcallする) }, posts(parent, args, ctx, info) { return posts // call author() coded below (postの数だけcallする) } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost // 自分(Post)のauthor(UserのID)とマッチするUserを返却 } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser // 自分(User)のIDとマッチするauthorをもつPostを返却 } } }まとめ
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Demo data // Arrayをフィールドに持ったりなんかしないですよ const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27, }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'About React', body: 'intriguing', published: true, author: '1' // userのID }, { id: '5', title: 'About GraphQL', body: 'Im now studying', published: true, author: '1' }, { id: '6', title: 'Money', body: 'very little', published: true, author: '2' }] const comments = [{ id: '11', text: 'oh my god!', author: '1', // userのID post: '4' // postのID }, { id: '21', text: 'jesus christ', author: '1', post: '4' }, { id: '31', text: 'goo goo dloo l doo', author: '2', post: '5' }, { id: '41', text: 'ironman', author: '3', post: '6' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! comments: [Comment!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! comments: [Comment!]! } type Post { id: ID! title: String! body: String! published: Boolean! author: User! comments: [Comment!]! } type Comment { id: ID! text: String! author: User! posts: [Post!]! } ` // Resolvers const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase())) } }, posts(parent, args, ctx, info) { if (!args.query){ return posts // call Post() coded below } else { return posts.filter(post => post.title.toLowerCase().includes(args.query)) } }, comments(parent, args, ctx, info){ return comments } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.post === parent.id) } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.author === parent.id) } }, Comment: { author(parent, args, ctx, info){ return users.find(user => user.id === parent.author) // parentはComment }, posts(parent, args, ctx, info){ return posts.filter(post => post.id === parent.post); } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- 投稿日:2019-03-27T19:45:26+09:00
Node.js&GraphQLの勉強 part1~基本操作~
GraphQLとは
簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。
例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリーこれはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定するGraphQLの利点
1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)3.簡単
一つのエンドポイントしか管理しなくてよいからHello GraphQL
サーバー ⇒ クライアント で既存のデータを取得してみる
1 ) プロジェクトの初期化とBabelのインスト-ル
> npm init > npm install babel-cli babel-preset-env ※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要 ※ babel-preset-env:Babelに何をパースすべきか伝える2 ) package.json / .babelrc / index.js を以下のように作成
3 ) 「npm run start」 でコンパイルしてBabelの動作確認> npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js hello4 ) GraphQL Yogaのセットアップ
GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。
ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。
> npm i graphql-yogaダミーのデータをサーバーに準備しつつセットアップ。
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Type definition (Schema) const typeDefs = ` type Query { hello: String! name: String! } ` // Resolvers const resolvers = { Query : { hello() { return 'Hello GraphQL' }, name() { return 'Hugo' } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- typeDefs でサポートする型を定義する(スキーマの定義)
- resolvers でクエリに対する処理を定義する
- serverのインスタンスを作成する
- serverを立ち上げる
※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)
5 ) 実行
>npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js server is upこの状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。> npm install nodemon --save-devnodemonをインストールしたら package.json の scripts を以下のように書き変える。
package.json"scripts": { "start": "nodemon src/index.js --exec babel-node" }index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。GraphQL Types
【Scalar Type】
5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/falseScalarType// Type definition (Schema) const typeDefs = ` type Query { id: ID! name: String! age: Int! height: Float! isAdult: Boolean! } `【Custom Type】
開発者が定義する type。
CustomType// Type definition (Schema) const typeDefs = ` type Query { me: User! } type User { id: ID! name: String! email: String! age: Int } ` // Resolvers const resolvers = { Query : { me(){ return { id: '12345', name: 'Hugo', email: 'hugo@example.com', age: 27 } } } } `Resolver Operation Arguments
index.js// 引数に '!' が有る場合、その引数は required const typeDefs = ` type Query { greeting(name: String, position: String): String! add(a:Float!, b:Float!): Float! } ` const resolvers = { Query : { greeting(parent, args, ctx, info) { if(args.name && args.position) { return `Hello, ${args.name}! You are my favorite ${args.positino}`; } else { return 'Hello!' } }, add(parent, args){ return args.a + args.b } } }Resolverには4つの引数が存在する
- parent : Type間にリレーションがある場合の親を指す
- args : operation arguments supplied
- ctx : contexutual data (ユーザの情報)
- info : サーバーに送信されたアクションの情報など
Array Type の扱い
【Arrays of Scalar types】
index.jsconst typeDefs = ` type Query { add(numbers: [Float!]!): Float! } ` const resolvers = { Query : { add(parent, args, ctx, info){ if(args.numbers.length === 0 ) { return 0 } return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue ) } }【Arrays of Custom types】
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'mike@ex.com' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! } type User { id: ID! name: String! email: String! age: Int } ` const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query)) } } }Relation
Type の ID を追加することでリレーションが生まれる。
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'aaa', body: 'asdtfhaer', published: true, author: '1' /// 注目 }, { id: '5', title: 'fff', body: 'hrtea', published: true, author: '1' /// 注目 }, { id: '6', title: 'Ahwerike', body: 'aikreshe', published: true, author: '2' /// 注目 }] const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! /// 注目 } type Post { id: ID! title: String! body: String! published: Boolean! author: User! /// 注目 } `そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる
index.jsconst resolvers = { Query : { users(parent, args, ctx, info) { return users // call posts() coded below (userの数だけcallする) }, posts(parent, args, ctx, info) { return posts // call author() coded below (postの数だけcallする) } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost // 自分(Post)のauthor(UserのID)とマッチするUserを返却 } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser // 自分(User)のIDとマッチするauthorをもつPostを返却 } } }まとめ
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Demo data // Arrayをフィールドに持ったりなんかしないですよ const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27, }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'About React', body: 'intriguing', published: true, author: '1' // userのID }, { id: '5', title: 'About GraphQL', body: 'Im now studying', published: true, author: '1' }, { id: '6', title: 'Money', body: 'very little', published: true, author: '2' }] const comments = [{ id: '11', text: 'oh my god!', author: '1', // userのID post: '4' // postのID }, { id: '21', text: 'jesus christ', author: '1', post: '4' }, { id: '31', text: 'goo goo dloo l doo', author: '2', post: '5' }, { id: '41', text: 'ironman', author: '3', post: '6' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! comments: [Comment!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! comments: [Comment!]! } type Post { id: ID! title: String! body: String! published: Boolean! author: User! comments: [Comment!]! } type Comment { id: ID! text: String! author: User! posts: [Post!]! } ` // Resolvers const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase())) } }, posts(parent, args, ctx, info) { if (!args.query){ return posts // call Post() coded below } else { return posts.filter(post => post.title.toLowerCase().includes(args.query)) } }, comments(parent, args, ctx, info){ return comments } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.post === parent.id) } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.author === parent.id) } }, Comment: { author(parent, args, ctx, info){ return users.find(user => user.id === parent.author) // parentはComment }, posts(parent, args, ctx, info){ return posts.filter(post => post.id === parent.post); } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- 投稿日:2019-03-27T19:45:26+09:00
Node.js&GraphQL part1~基本操作~
GraphQLとは
簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。
例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリーこれはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定するGraphQLの利点
1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)3.簡単
一つのエンドポイントしか管理しなくてよいからHello GraphQL
サーバー ⇒ クライアント で既存のデータを取得してみる
1 ) プロジェクトの初期化とBabelのインスト-ル
> npm init > npm install babel-cli babel-preset-env ※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要 ※ babel-preset-env:Babelに何をパースすべきか伝える2 ) package.json / .babelrc / index.js を以下のように作成
3 ) 「npm run start」 でコンパイルしてBabelの動作確認> npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js hello4 ) GraphQL Yogaのセットアップ
GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。
ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。
> npm i graphql-yogaダミーのデータをサーバーに準備しつつセットアップ。
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Type definition (Schema) const typeDefs = ` type Query { hello: String! name: String! } ` // Resolvers const resolvers = { Query : { hello() { return 'Hello GraphQL' }, name() { return 'Hugo' } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- typeDefs でサポートする型を定義する(スキーマの定義)
- resolvers でクエリに対する処理を定義する
- serverのインスタンスを作成する
- serverを立ち上げる
※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)
5 ) 実行
>npm run start > graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql > babel-node src/index.js server is upこの状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。> npm install nodemon --save-devnodemonをインストールしたら package.json の scripts を以下のように書き変える。
package.json"scripts": { "start": "nodemon src/index.js --exec babel-node" }index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。GraphQL Types
【Scalar Type】
5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/falseScalarType// Type definition (Schema) const typeDefs = ` type Query { id: ID! name: String! age: Int! height: Float! isAdult: Boolean! } `【Custom Type】
開発者が定義する type。
CustomType// Type definition (Schema) const typeDefs = ` type Query { me: User! } type User { id: ID! name: String! email: String! age: Int } ` // Resolvers const resolvers = { Query : { me(){ return { id: '12345', name: 'Hugo', email: 'hugo@example.com', age: 27 } } } } `Resolver Operation Arguments
index.js// 引数に '!' が有る場合、その引数は required const typeDefs = ` type Query { greeting(name: String, position: String): String! add(a:Float!, b:Float!): Float! } ` const resolvers = { Query : { greeting(parent, args, ctx, info) { if(args.name && args.position) { return `Hello, ${args.name}! You are my favorite ${args.positino}`; } else { return 'Hello!' } }, add(parent, args){ return args.a + args.b } } }Resolverには4つの引数が存在する
- parent : Type間にリレーションがある場合の親を指す
- args : operation arguments supplied
- ctx : contexutual data (ユーザの情報)
- info : サーバーに送信されたアクションの情報など
Array Type の扱い
【Arrays of Scalar types】
index.jsconst typeDefs = ` type Query { add(numbers: [Float!]!): Float! } ` const resolvers = { Query : { add(parent, args, ctx, info){ if(args.numbers.length === 0 ) { return 0 } return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue ) } }【Arrays of Custom types】
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'mike@ex.com' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! } type User { id: ID! name: String! email: String! age: Int } ` const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query)) } } }Relation
Type の ID を追加することでリレーションが生まれる。
index.js// Demo data const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27 }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'aaa', body: 'asdtfhaer', published: true, author: '1' /// 注目 }, { id: '5', title: 'fff', body: 'hrtea', published: true, author: '1' /// 注目 }, { id: '6', title: 'Ahwerike', body: 'aikreshe', published: true, author: '2' /// 注目 }] const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! /// 注目 } type Post { id: ID! title: String! body: String! published: Boolean! author: User! /// 注目 } `そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる
index.jsconst resolvers = { Query : { users(parent, args, ctx, info) { return users // call posts() coded below (userの数だけcallする) }, posts(parent, args, ctx, info) { return posts // call author() coded below (postの数だけcallする) } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost // 自分(Post)のauthor(UserのID)とマッチするUserを返却 } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser // 自分(User)のIDとマッチするauthorをもつPostを返却 } } }まとめ
index.jsimport { GraphQLServer } from 'graphql-yoga'; // Demo data // Arrayをフィールドに持ったりなんかしないですよ const users = [{ id: '1', name: 'Hugo', email: 'hugo@ex.com', age: 27, }, { id: '2', name: 'Taro', email: 'taro@ex.com' }, { id: '3', name: 'Aike', email: 'aike@ex.com' }] const posts = [{ id: '4', title: 'About React', body: 'intriguing', published: true, author: '1' // userのID }, { id: '5', title: 'About GraphQL', body: 'Im now studying', published: true, author: '1' }, { id: '6', title: 'Money', body: 'very little', published: true, author: '2' }] const comments = [{ id: '11', text: 'oh my god!', author: '1', // userのID post: '4' // postのID }, { id: '21', text: 'jesus christ', author: '1', post: '4' }, { id: '31', text: 'goo goo dloo l doo', author: '2', post: '5' }, { id: '41', text: 'ironman', author: '3', post: '6' }] // Type definition (Schema) const typeDefs = ` type Query { users(query: String): [User!]! posts(query: String): [Post!]! comments: [Comment!]! } type User { id: ID! name: String! email: String! age: Int posts : [Post!]! comments: [Comment!]! } type Post { id: ID! title: String! body: String! published: Boolean! author: User! comments: [Comment!]! } type Comment { id: ID! text: String! author: User! posts: Post! } ` // Resolvers const resolvers = { Query : { users(parent, args, ctx, info) { if (!args.query){ return users } else { return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase())) } }, posts(parent, args, ctx, info) { if (!args.query){ return posts // call Post() coded below } else { return posts.filter(post => post.title.toLowerCase().includes(args.query)) } }, comments(parent, args, ctx, info){ return comments } }, Post: { author(parent, args, ctx, info) { return users.find(user => user.id === parent.author) // parentはPost }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.post === parent.id) } }, User: { posts(parent, args, ctx, info) { return posts.filter(post => post.author === parent.id) // parentはUser }, comments(parent, args, ctx, info){ return comments.filter(comment => comment.author === parent.id) } }, Comment: { author(parent, args, ctx, info){ return users.find(user => user.id === parent.author) // parentはComment }, posts(parent, args, ctx, info){ return posts.filter(post => post.id === parent.post); } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('server is up'); })
- 投稿日:2019-03-27T18:25:24+09:00
[JavaScript] Ajaxのテスト時にFormからのSubmit送信に切り替えてデバッグする方法
こんにちわ。
最近、Ajaxで処理することが多いです。
やはり、UXを考えると、Ajax使いまくらないと。。。ですよね。しかし、Ajaxの開発をしていると、サーバーサイドの連携部分でエラーが発生することがありますよね。
この場合、皆さんはどのようにデバッグをされているのでしょうか?色々検索してみたのですが、これといった方法が見つからず、、、でした。
そこで、私はいつもは以下の方法でデバッグをしています。問題がないとは言い切れませんが、デバッグするには十分かな、と考えています。
①まずは、エラーをResponseから取得し、Consoleに吐き出す
JQueryで、以下のように書きます。
$.ajax({ type : 'POST',data: data,url : url,dataType : "json", success : function(json) { fn_callback(json);}, error : function(XMLHttpRequest, textStatus, errorThrown) { console.log("XMLHttpRequest : " + XMLHttpRequest.status); console.log("textStatus : " + textStatus); console.log("errorThrown : " + errorThrown.message); //↓ここがキモ。本番では出力しない。 console.log("responseText : " + XMLHttpRequest.responseText); return false; } });最後のresponseTextというところを出すと、エラーログが見れるので、ここで解決することが多いです。
この辺は、検索すると出てくる方法ですね。②GETに切り替えて、URLを取得し、直接ブラウザから叩く
でも、上記でも、原因がよくわからない場合ってありますよね。
この場合に、本当にもっといいやり方ないのかな、とは思いつつ、以下のようなやり方をしてます。
まず、送信typeをPOST => GETに変更します。
そして、サーバーサイドで、わざとエラーを起こします。(存在しない関数を呼んだりして)そうすると、GETでアクセスしたパラメータ付きURLが取得できるので、
そのURLをコピーして、ブラウザで叩く、という方法。そして、サーバーサイドのエラー箇所を特定していきます。
ここまでで大体潰せます。③POSTデータをFormに埋め込みサブミットする
しかし、GETは送信できるデータに上限がありますよね。
そういう時は稀ですが、これが必要な場合は、formを作ってサブミットします。でも、Ajaxで大量データを送る時って大抵、配列やオブジェクトで送ることが多いです。
そうすると結構面倒ですよね。例えば、こんなデータを送る時、
{'a':123,'b':{'b1':'bbb1','b2':'bbb2'},'c':['x','y','z']};いちいち手作業でformを作るのは不便ですよね?
(その結果、全然大したことないバグだったりすると・・・。)たまたま、今回送信していたデータがこれ以上に複雑なデータだったので、
汎用的に送信する関数を作りました。やってることはシンプルで、配列かオブジェクトなら、再帰的に階層を辿り、
hiddenタイプのinputに納めて送信する、という方法(ただの力技)です。function makeForm(object,GetPost,url,id){ var keylist = {}; function extendObject(txt, key){ if(typeof txt == 'object' && txt !== null){ for(var i in txt){ var tmpkey = (!key)?i:key + '['+i+']'; extendObject(txt[i], tmpkey); } }else{ keylist[key] = txt; } } extendObject(object, ''); var form = document.createElement("form"); form.id = id; form.action = url; form.method = GetPost; for(var name in keylist){ var ip = document.createElement("input"); ip.type = 'hidden'; ip.name = name; ip.value = keylist[name]; form.appendChild(ip); } document.body.appendChild(form); };この関数を↓の感じで呼んでください。
var object = {'a':123,'b':{'b1':'bbb1','b2':'bbb2'},'c':['x','y','z']}; object['_token'] = $('meta[name="csrf-token"]').attr('content'); //CSRFトークンをセット makeForm(object,'POST','dummyurl/test','submit_form'); $('#submit_form').submit()これで、デバッグに困ることはあまりないかな、と。
ただ、こんな力技しかないのでしょうか?
もっと良いデバッグ方法があれば知りたいのですが。。。
これで十分間に合っている感はあります。
- 投稿日:2019-03-27T18:13:47+09:00
JWTでセッション管理してはいけない
世の中にはJWT(JOSE/JWS/JWE)でセッション管理をしてはいけないという記事が2017年から山ほどあるのに、なぜかJWTでセッション管理をしようとする人がいる。翻訳記事だったり暗号の説明が長すぎたりして、JWTをセッションに使ってしまうような人の心に刺さってないんじゃないだろうか。
前提
JWTでセッション管理というのは、暗号化したトークンをブラウザのCookieに持たせて、サーバー側ではトークンを復号化してユーザー判定などのセッション管理を行うことである。サーバー側で[sessionId: userId]のペアを管理する必要がないのでステートレスに扱えてスケールしやすいというメリットがある。
問題
すごく便利な図があったのでまずこれを読んで欲しい。セッション方式の策定/設計をする職位の人ならすんなり読めると思う。
左から4番目Local Storageは読んでそのままこれ以上補足することもない。残りの部分は下記の2点に集約される。
明示的にログアウトするにはサーバー側の秘密鍵の変更が必要
このため次の二択を迫られる。
- 誰か一人でもログアウトしたいときは全員ログアウトになる(秘密鍵を更新した場合)
- なりすましログインが発覚しても、トークンの有効期限が来るまではそのセッションは無効にできない(秘密鍵を更新せずに耐える場合)
これが許されるのは金銭の絡まないサービスか、ユーザーに損害があっても運営がポイントを配れば済む(詫び石対応)類のサービスなどに限られるだろう。でもセキュリティは固いにこしたことはない。仮想通貨取引サイトで後者を選択したら、犯人が外部に送金していくログを指を咥えて見ているしかない。
従来のセッション管理の方がマシ
前項の不便さを回避するために、次のような回避策を考えるかもしれない。
- 明示的にログアウトしたユーザーを無効なトークンとして管理する
- ユーザーがパスワードを変えたらトークンも再発行の必要があるようにする
結局のところサーバーサイドでのステートフルなセッション管理であり、JWTのメリットであるステートレスを捨てている。新規に実装するよりも、従来の枯れているセッション管理実装を使ったほうがわかりやすくバグの見落としも少ないだろう。
認証
セッション管理ではなくOAuth認証などに使うのは問題ない。一回認証したら終わりなので、有効期限を短くしておけば済む。「一人のために全員ログアウト」のような不便は発生しない。
所感
一部実装がalgヘッダにnone(暗号化なし)を許可していただとか、どの暗号化方式にどんな弱点がとか、話題が分散するとどこが重要なのかわかりにくくなる。RFCを読めなどと言えば拒否反応を示す人もいるのである。正しく手短に伝えるのは難しいですね。
参考URL
- No Way, JOSE! Javascript Object Signing and Encryption is a Bad Standard That Everyone Should Avoid (PARAGON INITIATIVE, 2017-03-14)
- JOSE(JavaScriptオブジェクトへの署名と暗号化)は、絶対に避けるべき悪い標準規格である (POSTD, 上記PARAGON記事の翻訳, 2017-04-13)
- どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ? (co3k.org, 2018-09-20)
- JWTをセッション管理に転用するのはあまり良いアイデアではない(認証だけならいいよ) (anatooのブログ, 2018-10-03)
- 投稿日:2019-03-27T17:37:22+09:00
初心者めも
JavaScriptでは関数は変数と変わらない。
Javaでオブジェクトを変数に入れられるのと同じように、JavaScriptでは関数を変数に入れられる。よくある書き方
function hogefunc() { document.write("よくある書き方"); } hogefunc(); /* 『よくある書き方』と表示される */変数に代入する書式
var hogefunc2 = function() { document.write("変数に代入する場合と同じ書式でかく"); } hogefunc2();/* 『変数に代入する場合と同じ書式でかく』と表示される */unction hogeという宣言はhogeという関数入りの変数を作ったということ。
関数を戻り値で返す関数
var sotofunc = function() { return function() { document.write("関数を返す関数"); } } var func = sotofunc(); /* return の横にある関数が、func変数に代入される */ func(); /* 代入された関数が実行され『関数を返す関数』が表示される */関数が変数と同じならば、他の数値や文字列と同じように扱うことができる。
たとえば関数の戻り値を関数にする方法などが可能。関数を引数で関数に渡す
var hogefunc3 = function(func2) { func2();/* 引数で渡された何かしらの変数を関数として実行する */ } /* 関数を引数で渡す。『関数を引数で受け取る関数』と表示される */ hogefunc3(function(){ document.write("関数を引数で受け取る関数"); });関数も変数だから、引数として関数を渡すことができる。
これの使いドコロは、あらかじめ共通部分はつくっておいて、処理部分を後から作るとかができる。関数内で生き残る変数(クロージャ)
var hogefunc4 = function() { var num = 0; /* 0を持つ変数を宣言 */ return function() { document.write(num); num++; var clfunc = hogefunc4(); /* returnの横にある無名関数を変数に代入する */ /* numはスコープから外れているので、clfuncに代入された関数が実行後も変数は生きている */ clfunc(); /* 0と表示される */ clfunc(); /* 1と表示される */ clfunc(); /* 2と表示される */JavaScriptでは関数を関数内に定義することができる。
ある関数が関数の外にある変数にアクセスできるが、関数内にネストされた関数でもそれはできる。関数を返す関数hogefunc4内にnumという変数があるが、
通常numの寿命はhogefunc4が実行されている間だが、
戻り値で返される関数がnumを使っている場合numの寿命が伸びる。戻り値で返される関数が生きている間はnumは生き続けるし、またnumは戻り値関数のスコープの外にあるから
状態が保持される。この場合、関数を実行するたびインクリメントされる。
状態を持った関数を作ることができる。こういうものをクロージャと呼ぶ。
- 投稿日:2019-03-27T16:44:22+09:00
javascriptから別のjavascriptの処理を呼出し実行する
経緯、目的
会社で運用しているWebシステムのつくりが悪く、
ほとんど同じ処理にも関わらず、画面ごとに別々のjsを作って呼び出しているため、
改修案件が発生すると何本も同じような修正が必要になり、開発コストも障害リスクも高い。
そのため、共通した部分の処理を切り出して別のjsファイルを作成し、
それぞれのファイルから必要に応じて呼び出すようにできないかと考えた。試したこと
共通関数func_a.jsの関数の処理結果を個別処理func_b.js経由で取得し、画面main.htmlに表示する。
というごくごく簡単なモノですが・・・
※共通関数func_a.jsの処理(関数)は、windowオブジェクトのグローバル変数としてセットする。ソース
main.html<!doctype html> <html lang="ja"> <head> <meta charset="Shift_JIS"> <title>JS共通化サンプル</title> <meta name="description" content=""> <meta name="keywords" content=",,,,"> <meta name="viewport" content="width=device-width"> <script src="js/func_a.js"></script> <script src="js/func_b.js"></script> </head> <body> <h2>JS共通化サンプル</h2> <div>func_a.jsより、func_b.jsの処理を呼び出し実行する</div><br> <input type="button" value="テスト" onClick="pushBtn()"/><br> </body> </html>func_a.js(function() { function hoge() { fuga = 'fuga'; return fuga; } window.hogeLib= window.hogeLib|| {}; window.hogeLib.hoge = hoge; })();func_b.jsfunction pushBtn() { // func_a.jsのhogeを呼び出す処理 var hoge = window.hogeLib.hoge(); //文字列がアラートに表示される。 alert(hoge); };画面イメージ
結果
ごくごく簡単なサンプルですが、あるjsから別のjsの処理を呼び出すことができることが確認できました。
(そんなコトをするより、各jsの差異を取り込んだ1本のjsに集約した方がむしろ早いし分かり易い、
という意見が社内的には強いですが。)
- 投稿日:2019-03-27T15:57:53+09:00
POST.body を Json形式で渡す方法[python-by.bottle]
form内のinputタグ全てをjsonにして...
POSTは、formデータをサーバへ送信する際に活用するタグで、その中のinputタグに含まれるデータをサーバ側で受け取るのは、特に面倒だったりもする...
例えば、<input type="text" name="id" id="id">とか、<input type="text" name="id" id="name">とか...紛らわしい上に扱いづらい、そしてサーバ側の言語には予約されていたりするので、さらに変数名を安易に考えられたものについては面倒...
そこで単純に、jsonデータにして受け渡し、サーバ側でも受け取りをjson形式にする事で、加工や挿入、代入などが可能になり、処理もフロント側だけでほぼ完結したりもできる。準備するもの
サーバサイド
- Python3.6<=x
- Tinydb
- bottle
フロントエンド
- javascript
- html
- Chrome
実際のコード
実際と言いつつも、実行はしていないwけど、多分、動く...
※コメント頂ければ、修正します。これらは、ただ抜粋しただけだから動かない可能性もあるが、
ポイントを押さえておけば問題ないはず...
まずは、js側から...その次に、サーバ側の処理について...説明したいと思う。app.pyimport bottle from bottle import request from tinydb import TinyDB as tydb @route("/") def index(): index = """ <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Post 2 Json</title> </head> <body> <form action="/db" method="POST" id="form_cnts" name="form_cnts" enctype="multipart/form-data"> USER: <input type="text" name="user" id="user"> <br> PASSWORD: <input type="text" name="paswd" id="paswd"> <input type="hidden" name="secret_key" id="secret_key" value="b42c66d06dc047e191aab859429a0105"> </form> <script> /*---------------------------------------------------- $ ---------------------------------------------------------------*/ /** ID と Classを変換する関数 * $('#id') * @param {String} - let id_name = $('#id') * @return {Object} - id-tag */ if (String.prototype.$ == undefined){ $ = (x) => { const idn = /^#/g if (typeof x == "string") { if(x.match(idn)) return document.getElementById(x.replace(idn, "")) } return x } } let PostDB = (e) => { e.preventDefault() let JD = {} let FORMCNTS = $("#form_cnts") let INPUT_DATA = document.getElementsByTagName('input') for(let d of Object.values(INPUT_DATA)){JD[d.name]=d.value} let JSON_DATA = '['+JSON.stringify(JD).toString()+']' let CUSTOM_HEADER = "CUSTOM-HEADER-NAME" let customHeaders = new Headers({'Content-Type': 'application/json','X- Custom-Header': CUSTOM_HEADER }) let customInit = { method: 'POST', headers: customHeaders, Accept: 'application/json', mode: 'cors', cache: 'default', credentials: 'same-origin', body: JSON.stringify(JSON_DATA)} let url = FORMCNTS.action let customRequest = new Request(url, customInit) fetch(customRequest) .then((response) => { return response.json() }) .then((resp) => { if(resp.status == "OK") { for(let d of Object.values(INPUT_DATA)){d.value=""} let fade_div = $("#fade_div") fade_div.innerText = resp.status fadeIn(fade_div,1500,function(){ fadeOut(fade_div,1500,function(){ }) }) } }) .catch(function(error) { console.log(error) }) } </script> </body> </html> """ return index @post('/db') def do_db(): TYDB = f'{TMP_PATH}/{DATABASE["NAME"]}.json' _status = "NG" json_data = {"status": _status} if request.json != "": with tydb(TYDB) as db: json_data = json.loads(request.json) db.insert(json_data[0]) _status = "OK" try: reurl = json_data request_data = { "headers":request.headers['Content-Type'], "json": json_data, "MEMFILE_MAX":request.MEMFILE_MAX, "remote_route":request.remote_route, "is_xhr":request.is_xhr, "remote_addr":request.remote_addr, "url":request.url, "method":request.method, "status":_status, "User_Agent":request.get_header('User-Agent')} response.content_type = 'application/json' r = HTTPResponse(status=302) r.set_header('Location', reurl) return dumps(request_data, ensure_ascii=False, indent=2) except: abort(500) if __name__ == "__main__": bottle.run(host='0.0.0.0', port=8080)と、実行されれば、form内のデータがデータベースに登録されるだろう...
まずは、jsの説明から...
ここが重要で、TagName=inputを全て、INPUT_DATAへ格納する。
その後、for文で全て抜き出す。その際、name(key)とvalueで紐付けしながら格納する。
もしかすると、もっといい方法があるだろう...... let INPUT_DATA = document.getElementsByTagName('input') for(let d of Object.values(INPUT_DATA)){JD[d.name]=d.value} ...その後、一度連想配列にする。
何故か?は、サーバ側の処理を確認するといいだろう。
日本語などがあると通常通り引き渡せない事があったので、救済処置として、このような形にしてみた。let JSON_DATA = '['+JSON.stringify(JD).toString()+']'あとは、非同期処理の
fetchを使ってPOSTすればいい。
その時重要になるのが、Content-Typeだ。これをjsonにしておかないと、受け渡しの際に、サーバ側で処理が複雑になってしまう。... customHeaders = new Headers({'Content-Type': 'application/json',...最後となるが、サーバ側の処理をみてみよう。
...json.loads(request.json) ...db.insert(json_data[0])と、上記のようにjson形式で呼び出した上で、データベースへ格納する際には、連想配列にて、
[0]とし引き渡す事で、str処理などを気にせず、格納できる。
色々試したのだが、連想配列以外での受け渡しだとうまく受け取れないdictだったり、strだったり...typeが異なり、受け取りの処理が複雑かつ、面倒になりがちだった...ひとまず、これで落ち着くと思う。
以上。
- 投稿日:2019-03-27T15:26:20+09:00
Electronで半透明なウィンドウを作る方法 2019年版
Electronで半透明なウィンドウを作る方法を調べても古い方法しかなかなか見つからなかったので、最新のやり方をメモしておきます。
2019/03 現在のバージョン(4.1系)ではウィンドウを半透明にするためにCSSを使う必要はありません。2つ方法があります。
方法1
win = new BrowserWindow({ width: 800, height: 600, opacity: 0.5 // これだけ! })これだけでウィンドウが半透明になります。1で不透明、0で完全に透明です。
方法2
win = new BrowserWindow({ width: 800, height: 600, transparent: true, // これと backgroundColor: '80FFFFFF' // これだけ! })こちらの方法の場合、背景色だけが半透明になるので、その上の要素(文字など)は半透明になりません。
backgroundColorの値はCSSと違いARGB(Alphaが最初)な点に注意しましょう。参考
- 投稿日:2019-03-27T12:45:10+09:00
ES6のimportとexport
myModule.jsconst message = 'some message' const name = 'Hugo' const location = 'Tokyo' const getGreeting = name => { return `welcome ${name}` } export { message, name, getGreeting, location as default }index.jsimport myCurrentLocation, { message, name, getGreeting } from './myModule'; console.log(myCurrentLocation); // Tokyo console.log(message, name); // some message Hugo console.log(getGreeting(name)); // welcome Hugo・Named export ⇒ import先で名前を変えられない
・Default export ⇒ import先で名前を変えらる
- 投稿日:2019-03-27T11:49:51+09:00
document onloadをjQueryなしで書く
jQueryでおなじみの
$(function(){ ... });をjQueryなしで書くにはどうしたらよいのか迷ってしまいましたので投稿します。
結論、
document.addEventListener('DOMContentLoaded', function(){ ... });のように書きます。
- 投稿日:2019-03-27T11:37:48+09:00
JavaScript基礎文法まとめ
0.背景
Webアプリが作るために、今更ながらJavaScriptの文法をお勉強。
C Sharperなので、違和感ほぼ無し。1.基本的なJavaScript実行方法
index.html:JavaScriptを適用するHTML
index.html<!DOCTYPE html> <html lang="ja"> ~ headタグ中略 ~ <body> <script src="main.js"></script> ← ファイル指定 </body> </html>main.js:JavaScriptファイル
main.js"use strict" ← エラーチェック有効 console.log("Hello JavaScript")[実行結果の確認 @ Chrome]
① Chromeでデベロッパーツールを起動する。
② Consoleで実行結果を確認する。
![]()
2.基本的なJavaScript文法
① コメント記載方法
// 一行コメント /* 複数行コメント */② 正規表現
. // 改行以外の任意の文字 \n // 改行文字 \r // キャリッジリターン文字 \t // タブ文字 \b // 単語の区切り(単語の開始または終了) \B // 単語の区切り以外 \d // 任意の数字 ( [0-9]と同じ) \D // 任意の数字のもの( [^0-9]と同じ) \s // シングル空白文字(スペース、タブ、改行など) \S // シングル空白文字以外の文字 \w // 単語文字( [A-Za-z0-9_]と同じ) \W // 単語文字以外の文字( [^A-Za-z0-9_]と同じ)③ 関数
main.js"use strict" function sum(a, b, c) ← 3つの引数の合計を返す関数を定義 { return a+b+c; } for (let i = 1; i <= 5; i++) ← Forループ { console.log(`${i} + ${i+1} + ${i+2} = ` + sum(i, i+1, i+2)); }[実行結果]
情報ソース
- 投稿日:2019-03-27T08:50:55+09:00
Portalsをさわってみた
背景
- 話題の Portals を使った画面遷移 UX の未来という記事を読んでPortalsを知りました
- 詳しいことは上記の記事に書いてありますが自分なりにさわってみた記録を書きます
Portalsとは
<portal>という新しいHTMLタグのことです<iframe>のように別のページを埋め込むことができます<iframe>と違うところは表示したページに遷移させることができる点です- すでにページ上に埋め込まれているページに遷移するため高速に遷移することができます
- ただし
<iframe>のように埋め込んだ中のページを操作することはできずプレビューのみといったイメージです作ったもの
- 言葉だけだとよくわからないと思うのでデモアプリを載せます
mouseEnter/mouseLeaveでPortalsの表示/非表示を切り替えてクリック時に遷移するように実装してあります- 普段使い慣れているのでReactで書いています
- コードあまりきれいではないですがリポジトリはこちらです
- デモアプリはこちらです
※ChromeCanaryを引数付きで起動しないと動作しません
- 上記ブログ記事より引用
現状 Chrome Canary で Portal は試せます。起動フラグを付けて Canary を立ち上げてください。
Mac: open -a Google\ Chrome\ Canary --args --enable-features=Portals
Windows: ショートカットを右クリック、リンク先に --args -enable-features=Portals のオプションを付けて起動。
Linux: Canary は Linux ではサポートされていません。代替として Chromium をご利用ください。Portalsの使い方
- デモアプリではややこしいことをしていますが単に使うだけであればとても簡単です
- 以下のファイルを作成してブラウザで開いてみて下さい
- ※ChromeCanaryを引数付きで起動するのを忘れずに
- ①ページを表示するとQiitaのトップページが表示されます
- ②画面をクリックするとQiitaへ遷移します
index.html<portal src="https://qiita.com" style="height: 1000px; width: 1000px"></portal> <script> const portal = document.querySelector('portal'); portal.addEventListener('click', () => portal.activate()); </script>①ページを表示するとQiitaのトップページが表示される
<portal>タグのsrc属性に表示したいURLを設定するだけで表示することができます- 上のデモで表示/非表示を切り替えていたのはタグ自体を挿入したり削除したりしていただけです
②画面をクリックするとQiitaへ遷移する
- Portalsでページ遷移させるためにはJavaScriptによる操作が必須となります
<portal>のDOMを取得し.activate()を実行するとPortal内に表示しているページに遷移することができますまとめ
- Portalsはiframeのようなものと冒頭で紹介しましたが、単に別のページを埋め込むだけでなく新たなUXの実現ができるような面白いタグだということがわかりました
- まだ一般的なブラウザでは動作しませんが実装はとても簡単なので今後が楽しみです
- 投稿日:2019-03-27T02:52:00+09:00
npmライブラリの開発環境
概要
最近、Three.jsなどでよく使うオブジェクトはなんとなくモジュール化してコピペで複数のプロジェクトで使用していたりするのですが、コピペだとやっぱめんどくさいしせっかくならnpmで簡単にダウンロードできるようにして起きたいよね!ということで試してみました。
同時に、npmパッケージの開発環境ってどうするんだろうか...と試してみた記録です。とりあえずnpmにあげてみよう
開発環境のことは考えずに、適当に作ったモジュールをnpmにあげて見たいと思います。
試しに、three.jsで回転する立方体(を持つモジュール)のパッケージを作ります。1.npmにサインアップ&ログイン
下記からサインアップをします。
https://www.npmjs.com/signupログインします。
$ npm login2.npmを初期化
初期化する前に、パッケージの名前が他の公開されているパッケージと被らないよう、先に検索をかけて確認します。
プロジェクトのディレクトリに移動し、npmを初期化します。
$ npm init初期設定を行います。
package name: (threerotatecube) three-rotate-cube version: (1.0.0) description: entry point: (index.js) test command: git repository: (https://github.com/Obshi/ThreeRotateCube.git) keywords: author: license: (ISC)依存パッケージをインストール
今回はThree.jsを使っているのでthreeをインストールします。
pakage.jsonに書き込むだけでも良いのかな...$ npm install --save three公開
公開します。
$ npm publish確かめた
import * as THREE from 'three'; import RotateCube from 'three-rotate-cube' window.THREE = THREE; export default class ThreeGraphic{ constructor(){ this.canvas = document.querySelector('#canvas'); this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas, }); this.renderer.setSize(window.innerWidth,window.innerHeight); this.renderer.setPixelRatio(1); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 1000); this.camera.position.set(0,2,5); this.camera.lookAt(0,0,0); this.rotCube = new RotateCube(); this.scene.add(this.rotCube.obj); this.animate(); } animate(){ if(this.rotCube){ this.rotCube.update(); } this.renderer.render(this.scene,this.camera); requestAnimationFrame(this.animate.bind(this)); } }パッケージ開発環境
さて、公開自体はとても簡単にすることができましたが、パッケージ自体の開発はどうすれば良いのでしょうか...
テストしながらそのまますぐ公開できる環境欲しい...npm IN npm
テスト用の大枠のプロジェクトの中にパッケージプロジェクトをそのまま突っ込んでしまいます。
これで...いいのか...?
プロジェクトはこちら. ├── gulpfile.babel.js ├── package-lock.json ├── package.json ├── public ├── src │ ├── assets │ ├── html │ │ └── index.html │ ├── js │ │ ├── MainScene.js │ │ ├── ThreeController.js │ │ ├── main.js │ │ └── utils │ │ └── ThreeRotateCube (パッケージ) │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ └── scss │ └── style.scss └── webpack.config.jsこんな感じのディレクトリ構造です。
開発環境中でのインポートはディレクトリをそのまま読ませます。main.jsimport RotateCube from './utils/ThreeRotateCube'24時間立たないともう一度公開できませんが、おそらく正常に公開できるかと思います。
ukMac:ThreeRotateCube obshi$ npm publish npm notice npm notice ? three-rotate-cube@1.0.0 npm notice === Tarball Contents === npm notice 444B package.json npm notice 329B index.js npm notice === Tarball Details === npm notice name: three-rotate-cube npm notice version: 1.0.0 npm notice package size: 515 B npm notice unpacked size: 773 B npm notice shasum: 3b267b75c042430bfa59dd49d01b4225c394cc09 npm notice integrity: sha512-OIV5VXfN+yt4a[...]ZkufvWVWEG0Lw== npm notice total files: 2 npm notice npm ERR! publish Failed PUT 403 npm ERR! code E403 npm ERR! three-rotate-cube cannot be republished until 24 hours have passed. : three-rotate-cube npm ERR! A complete log of this run can be found in: npm ERR! /Users/obshi/.npm/_logs/2019-03-26T17_49_12_695Z-debug.logしばらくはこんな感じでパッケージ、作っていこうと思います。
- 投稿日:2019-03-27T01:14:47+09:00
Googleの力を借りて ”もしかして” する
はじめに
誤字った単語から本来の単語を推測したいときがあるかもしれません。
しかしそれを実装するのは大変でしょう。
そんなときは偉大なGoogleの力を借りましょう。コード
index.html<html> <head> </head> <body> <form id="form"> <input type="text" id="query" /> <div id="result"></div> <button type="submit">検索</button> </form> <script type="text/javascript"> document.getElementById("form").onsubmit = (e) => { e.preventDefault(); (async () => { let result = document.getElementById('result'); let query = document.getElementById("query"); result.innerText = "..."; let res = await fetch(`https://cors-anywhere.herokuapp.com/www.google.com/search?q=${query.value}`); let html = await res.text(); let parser = new DOMParser(); let doc = parser.parseFromString(html, "text/html"); result.innerText = (() => { let fprsl = doc.getElementById('fprsl'); if (fprsl) { return "もしかして :" + fprsl.innerText; } for(let e of doc.getElementsByClassName('gL9Hy')){ let href = e.getAttribute("href"); if(href && !href.indexOf("/search?q=")){ return "もしかして :" + e.innerText; } } return ""; })(); })() } </script> </body> </html>実行してみるとこんな感じ↓
See the Pen ywrmPw by gyojir (@gyojir) on CodePen.
説明
Google検索の"もしかして"機能は2パターンある。
・推測結果で検索までしてくれる
それぞれのidとclassのパターンから適当にとってくればよい。
ただCORS制約によりGoogleに直接フェッチすることはできない。
そのためcors-anywhereというサービスを介してフェッチしている。おしまい
どうせすぐに使えなくなると思う。























