- 投稿日:2020-08-03T23:33:33+09:00
【javascript】テトリスの作成に向けて
javascriptでのテトリス作成において必要になった知識をまとめていきます。
ランダムな整数値の生成
これでmin以上max以下の整数を得られる
function getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive }多重ループを抜け出す
ラベルをつけることでループを抜け出せる
var grid = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; function validate(value) { return value !== 5; } grid_loop: for (var row = 0; row < grid.length; row++) { for (var col = 0; col < grid[row].length; col++) { var value = grid[row][col]; console.log(value); if (!validate(value)) { break grid_loop; } } }配列の中で最大値を見つける
var arr = [1,2,3]; var max = arr.reduce(function(a, b) { return Math.max(a, b); });出典:Math.max()
スプレッド構文
var arr = [1, 2, 3]; var max = Math.max(...arr);出典:Math.max()
スプレッド構文により、イテラブルである配列を展開している
配列内を検索する
Array.prototype.includes()
配列内を検索して真偽値を返す
- 投稿日:2020-08-03T22:44:41+09:00
addEventListenerを使用する際に注意するべきこと
jsの実装中にエラーが起きたのでまとめます。
addEventListnerとは
addEventListenerとは、イベント発火の際に実行する関数を定義するためのメソッドです。
以下のようにして実行できます。
要素.addEventListener('イベント名', 関数);エラーが起きたときのコード
function calc() { const value = document.getElementsByClassName("price-input"); value.addEventListener('input', (e) => { console.log("") }); } window.addEventListener("load", calc);エラー内容
Uncaught TypeError: value.addEventListener is not a function at calc (calculation.js:5)なぜかvalueが定義されてないことになっています。
valueがちゃんと定義されているか確認します。
function calc() { const value = document.getElementsByClassName("price-input"); console.log("value") value.addEventListener('input', (e) => { console.log("") }); } window.addEventListener("load", calc);コンソールを確認します。
HTMLCollection [input#item_price.price-input, item_price: input#item_price.price-input, item[price]: input#item_price.price-input]なぜかいっぱいある。。。
そうですgetElementsbyClassNameで取得できるのは
HTMLCollectionというオブジェクトになります。HTMLCollectionは複数ノードなので
単一ノードのみ指定できるaddEventListnerでは取得することができなかったのですね。idで取得するように変更してみます。
function calc() { const value = document.getElementById("item_price"); value.addEventListener('input', (e) =>{ console.log("") }); } window.addEventListener("load", calc);これでエラーが解決できました。
- 投稿日:2020-08-03T21:59:46+09:00
簡易SPARQLエンドポイント検索アプリをjQueryからVue.jsに書き直しました
RDFデータベースをSPARQLと呼ばれるクエリ言語で検索できるWeb API「SPARQLエンドポイント」を公開するオープンデータサイトが行政を中心に増えています。例えば、
これらのサイトの多くは、Webブラウザ上でSPARQLクエリを実行して、表形式で検索結果を見られるようになっています。
しかし、Web APIなので本来は仕様に基づいた形式でエスケープ処理をしたSPARQLクエリをGETもしくはPOSTしなけれならないので、上記のような気の利いたページを公開しないSPARQLエンドポイントもあります。そこでだいぶ前にWebブラウザ上でSPARQLエンドポイントへ簡単にSPARQLクエリを実行できて、その結果を見やすい表形式で表示するWebアプリをjQueryで作りました。
JavaScriptによるSPARQL利用サンプル(クエリ検索アプリ)
https://github.com/uedayou/simple-sparqlsearch-jsかなり古いアプリですが、今でも問題なく動きます。ただ、今見るとコードの可読性が悪いなとおもいます。とても短いコードでもあるので、jQueryを使わずに書き直すことにしました。
Vue.js で書き直し
今回は Vue.js を使うことにしました。React, AngularでもWebアプリを書いたことがありますが、今回はできるだけコード量を減らして、Webpack等でのビルドを行わない形にしたかったので、Vue.jsを使いました。
Vue.jsによるSPARQLエンドポイント検索アプリ
https://github.com/uedayou/simple-sparqlsearch-vue機能としては全く同じですが、検索した結果をJSONファイルでダウンロードできるようにしました。
検索結果が表示されているときに、ダウンロードボタンを押すとJSONファイルがダウンロードできます。
Vue.js vs jQuery
Vue.js で書き直してみて、jQueryのコードよりも個人的には格段に可読性が上がったと思います。
jQueryは、DOM操作をコード内で行わないといけなかったのが、Vue.jsだとDOMにデータをバインディングできるので書き直すのも楽にできました。Vue.jsのコードVue.use(VueLoading); Vue.component('loading', VueLoading); var app = new Vue({ el: '#app', data: { query: 'select * where {?s ?p ?o} LIMIT 10', results: { data: null, head: [], body: [], }, }, methods: { doSearch: function() { var loader = this.$loading.show(); var that = this; axios.get( endpoint+"?query="+encodeURIComponent(this.query), { headers: {'Accept': 'application/sparql-results+json'} }) .then(function(res) { that.results.data = res.data; that.results.head = res.data.head.vars; that.results.body = res.data.results.bindings; }) .catch(function(error) { console.log(error); alert("Error!"); }) .then(function() { loader.hide(); }); }, downloadData: function() { var filename = "results.json"; var a = document.createElement('a'); var uriContent = 'data:application/octet-stream,'+encodeURIComponent(JSON.stringify(this.results.data)); a.setAttribute('href', uriContent); a.setAttribute('download', filename); document.body.appendChild(a); a.click(); document.body.removeChild(a); } } })jQueryのコード(function(){ $.fn.modal.defaults.spinner = $.fn.modalmanager.defaults.spinner = '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="progress-bar" style="width: 100%;"></div></div></div>'; $('#find_query').click(function(){ $('body').modalmanager('loading').find('.modal-scrollable').off('click.modalmanager'); qr = sendQuery(endpoint,encodeURIComponent($('#query_area').val().replace(/[\n\r]/g,""))); qr.fail( function (xhr, textStatus, thrownError) { $('body').modalmanager('removeLoading'); alert("Error: A '" + textStatus+ "' occurred."); } ); qr.done( function (d) { $('body').modalmanager('removeLoading'); $('body').removeClass('modal-open'); result_table(d.results.bindings); } ); }); $('#result_div').hide(); }()); var result_table = function(data){ var result_div = $('#result_div'); var table = $('#result_list')[0]; if (table == undefined) { result_div.append($('<table></table>').attr({ 'id' : 'result_list', 'class' : 'table' })); table = $('#result_list')[0]; } while (table.rows.length > 0) { table.deleteRow(0); } if (data instanceof Array) { result_div.show(); var header = table.createTHead(); var headerRow = header.insertRow(0); id = 1; for (var d = 0; d < data.length; d++) { var row1 = table.insertRow(d + 1); if (d == 0) { for ( var key in data[0]) { var th = document.createElement('th'); var label = key; th.innerHTML = key; headerRow.appendChild(th); } } var i = 0; for ( var key in data[d]) { var cell = row1.insertCell(i++); var value = data[d][key]; if (value.value != undefined){value = value.value;} if (value == null) {value = '';} var link = true; if (link) { if (value != null && value.indexOf("http://") == 0) { value = '<a href="'+value+'" target="_blank">'+value+'</a>'; } } cell.innerHTML = value; } } } };
- 投稿日:2020-08-03T21:52:20+09:00
border-radiusでムニュムニュするだけのページ作成
動機
border-radiusってせいぜい使っても50%で球体が書けるねって話で終わってしまっていて、もっといろいろ書きたくなった。
実装
まあ、何も言わず見てくれい
https://emptyset.sakura.ne.jp/munu/
こんな感じのヤツがひたすらムニュムニュする。動作原理
タイマでランダムにborder-radiusを生成してCSSを修正し、transitionさせる。
結論
transitionって面白いな。
何も使い道が思いつかないけど。
- 投稿日:2020-08-03T21:25:00+09:00
オブジェクトのKeyの値を変更する
個人メモ
array: [ { id: 1, text: 'test' }, { id: 2, text: 'test' }, { id: 3, text: 'test' }, { id: 4, text: 'test' }, { id: 5, text: 'test' } ]この配列の
test
をtestText
というKey名に変える。changeKey () { const array = this.array.map(value => { return {id: value.id, testText: value.text} }) return array }
- 投稿日:2020-08-03T20:30:42+09:00
【JS】constは定数を定義できないよ。
constとは
再代入できない変数を定義する。なので、オブジェクトの内容などは変えることができる。
const name = { key: "変更前", }; console.log(name.key); // 値を変えてみる name.key = "変更後"; // 正常に動作する console.log(name.key);このように、中身は変えることができる。とはいえ再代入はできない。
const name = "変更前"; console.log(name); const name = "変更後"; // => Uncaught SyntaxError: Identifier 'name' has already been declaredつまり定数ではない。
オブジェクトの中身も固定化したい
この場合は
freeze
メソッドを使えばOK。const name = Object.freeze({ key: "value", }); name.key = "ここで変更できない"; console.log(name.key); // => valueちなみに配列でも同じことができます。
const ary = Object.freeze([1, 2, 3, 4, 5]); ary[0] = 999; console.log(ary[0]); // => 1
- 投稿日:2020-08-03T19:56:07+09:00
【JS】変数名に使える名前のルールを学ぶ
ルールの概要
- _(アンダーバー),$(ダラー),数字,半角アルファベットをの組み合わせ
- 数字からは開始できない。
- 予約語は使えない。
例
const _ = "test1"; const $ = "test2"; const a = "test3"; const 1aaa = "test4"; const let = "test5"; console.log(_); // => test1 console.log($); // => test2 console.log(a); // => test3 console.log(1); // => Uncaught SyntaxError: Invalid or unexpected token console.log(let); // => let is disallowed as a lexically bound name
- 投稿日:2020-08-03T19:00:14+09:00
TypeScriptのnever型について
never型って?
typeScriptにおける
bottom
型です。bottom型って何よ
型理論、数理論理学において値を持たない型のことである
↑wikiからの抜粋です。
値を持たない型, これだけだと???となりそうですが、戻り値の型がボトム型である関数は、いかなる値も返さない。
こちらの説明だと少しわかりやすいかと思います。
つまり、
const foo = () => { while(true){} }上記のように
while(true)
で戻り値がない場合(= returnが絶対に走らない場合)などは、関数foo
の戻り値の型はnever
と言えます。特徴
never
は値のない型なので、値を入れるとエラーになります。let foo:never foo = 'test' // コンパイルerror実際に戻り値がneverとなる例
上記でwhileの例を上げましたが、他の例とも合わせて再掲します。
// 無限ループする場合 const foo = () => { while(true){} } // errorをthrowする場合 const bar = () => { throw new Error("これはエラーです") }
void
とnever
の違いは?これは実際にコードを見てもらった方がわかりやすいです。
const foo = () => { } const bar = () => { return } const baz = () => { while(true){} } let fooVar:never = foo() // error let barVar:never = bar() // error let bazVar:never = baz() //ok!上記では
baz
のみ戻り値がnever型
で、foo
とbar
の戻り値はvoid型
です。JSに置いて
return文
を省略された場合はundefined
が返る仕様なので、
foo
とbar
は実質的に同じです。voidはreturnでの戻り値なし!の型なのに対して
neverはそもそもreturnしない。
似ているようで全然違っています。実際での使用例
値を入れることができない という部分が生きてきます。
下記のような型がそれぞれあったとします。
interface Neko { type: "neko" } interface Inu { type: "inu" name: number } interface Other { type: "other" } type Animal = Neko | Inu | Other上記Animal型を使う上で、
type
によってそれぞれ別の処理をさせたい場合、
下記のようなコードになるかと思います。function intro(s: Animal) { if (s.type === "neko") { return `吾輩は猫である。名前はまだない` } else if (s.type === "inu") { return `${name}は犬のお廻りです。` } }上記では
Neko
Inu
の型しか想定されていません。
Other
型, またそれ以外でも想定しない型が渡ってきた場合にerrorを返す仕組みがあれば便利です。
そこで、予期しない型が渡ってきた場合はnever
に代入することでコンパイルエラーを出し、
網羅漏れに気づくことができます。
(全て網羅できていれば、_exhaustiveCheck
はnever型となります。)function intro(s: Animal) { if (s.type === "neko") { return `吾輩は猫である。名前はまだない` } else if (s.type === "inu") { return `${name}は犬のお廻りです。` } else { // Otherが渡ってきた場合、neverの特徴によってerrorが出て気づくことができる。 const _exhaustiveCheck: never = s; } }上記の手法は
switch
のdefault文に置いても有効ですね。参考文献
TypeScript Deep Dive 日本語版
Use the never type to avoid code with dead ends using TypeScript
TypeScriptの型入門
- 投稿日:2020-08-03T17:54:57+09:00
【プラグイン導入】SyntaxError Cannot use import statement outside a module【Nuxt.js】
参考対象者
- 外部ライブラリをNuxt.jsに導入したい方
- npmでプラグイン等を管理している方
環境
package.json{ "name": "nuxt-proj", "version": "1.0.0", "private": true, "scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate" }, "dependencies": { "nuxt": "^2.14.0" }, "devDependencies": {} }状況
外部ライブラリの
vue-flag-icon
を導入したいが、SyntaxErrorになってしまう。SyntaxError Cannot use import statement outside a moduleエラーにならない、プラグイン導入方法(node_modulesで管理)
- プラグインをnpmで、インストール
- pluginsディレクトリに、プラグインをimportするファイルを作成
- nuxt.config.jsファイルに、importファイルを読み込む設定をする
- サーバーの再起動
プラグインをnpmで、インストール
まずは、今回のプラグインである
vue-flag-icon
をインストールしていきます。$ npm i vue-flag-icon@latestすると自動でpackage.jsonにも記述されますね。
package.json"dependencies": { "nuxt": "^2.14.0", "vue-flag-icon": "^1.0.6" },pluginsディレクトリに、プラグインをimportするファイルを作成
プラグイン公式(https://github.com/vikkio88/vue-flag-icon)
にもあるように、必要な記述を設定していきます。そしてその設定は、どのファイルからでもライブラリを読み込めるように、pluginディレクトリというVue.jsをインスタンス化する前に読み込んでくれるJSファイルの置き場所に、新規ファイルでしていく。
plugins/flag-icon.jsimport Vue from 'vue'; //Vueを定義するために、Vueをimport import FlagIcon from 'vue-flag-icon'; Vue.use(FlagIcon);nuxt.config.jsファイルに、importファイルを読み込む設定をする
あとは、先ほど設定したimportファイルを、Nuxt.js自体の設定ファイルに読み込む記述をする。
nuxt.config.jsexport default { //省略 plugins: [ '@/plugins/flag-icon', ], //省略 }ただ、これだけだとエラーになってしまう。
原因は、今回のライブラリの管理方法がnpmなので、さらに専用の設定が必要なため。とは言っても、あと1行だけ追記すればいいのだが、
nuxt.config.jsexport default { //省略 build: { transpile: ['vue-flag-icon'] } }これにて、ライブラリが使えるようになる。
サーバーの再起動
と、よく忘れますが、
nuxt.config.js
ファイルに変更を加えた場合、サーバーの再起動をする必要があるので、$ npm run devして実際に使ってみましょう!!
実際に、使ってみる
どのVueファイルでも良いので、
<flag iso="jp" />
を追加してみると、pages/index.vue<template> <div> //省略 <flag iso="jp" /> </div> </template>我らが日本の国旗が、表示されるであろう!
参考
Nuxt.js公式様
https://ja.nuxtjs.org/guide/plugins/@fj_yohei様
https://qiita.com/fj_yohei/items/cddf267a94fa30ecb0b8
- 投稿日:2020-08-03T16:50:25+09:00
storybookでPHPを利用してAPIデータを取得する
別ドメインのAPIデータを取得するような仕組みの場合、phpを使用して同ドメインのデータとして取得したい場合があると思います。
storybookでこれを実現する方法を紹介します。フォルダ構成
npxでインストールしたものとAPI用のphpフォルダを置いた状態で前提として進めます。
/.storybook /stories /php/ /api/ items.php /package.jsonローカル環境でPHPを使う場合
APIを取得するだけであればphpのビルドインサーバー使うのが楽だと思います。
php -S 0.0.0.0:8000 -t ./php0.0.0.0(localhost)
storybookでproxyを使う方法
1. 【http-proxy-middleware】をインストールする
npm i http-proxy-middleware -D2. storybookのconfigに【middleware.js】を置く
configのフォルダ(デフォルトだと.storybook)下に【middleware.js】を置いて以下の様な記述をします。
【例】拡張子がphpになってるものをproxyするパターン
const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = router => router.use( "**/*.php", createProxyMiddleware({ target: "http://localhost:8000/", }) );【例】/api以下をproxyして、本番URLに合わせてリライトするパターン
const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = router => router.use( "/api", createProxyMiddleware({ target: "http://localhost:8000/", pathRewrite: { '^/api/items': '/api/items.php', }, }) );ビルドインサーバーを実行した状態で、storybookの【 http://localhost:6006/api/items 】を叩くとproxyが動作していることを確認できます。
一つのコマンドで実行できるようにする
ビルドインサーバーとstorybook両方のコマンドを実行するのは面倒だと思うので、一つで実行できるようにします。
1. 【npm-run-all】をインストール
npm i npm-run-all -D2. package.jsonにコマンドを追加する
"scripts": { "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook", "proxy": "php -S 0.0.0.0:8000 -t php/", "start": "npm-run-all -p proxy storybook" }storybook実行中にproxyのログが必要ない場合は、proxyのオプションに-qを付けると消せます。
ローカルだけproxyを使いたい場合やパスを変えたい場合
storybookは、.storybookの中身を別のフォルダに用意することで、configを分けることが出来ます。
コマンドのオプション(-c)でフォルダを指定しますstart-storybook -c .storybook/dev build-storybook -c .storybook/buildこのような形でconfigを分けると、dev専用の【middleware.js】を仕込むことができます。
- 投稿日:2020-08-03T16:29:50+09:00
ワイニート 「ヤフージャパンのロゴ変えたろ」
ヤフージャパンのトップを見つめるワイ
ワイ 「ヤフージャパンのロゴ変えたいな」
ワイ 「久々Chrome拡張機能でも書くか」
娘 「ユーザスクリプト書くのね」
娘 「manifest.jsonとJavaScriptのファイルが必要だね」manifest.json{ "name": "ヤフージャパンのロゴを変えてみる", "version": "1.0.0", "manifest_version": 2, "description": "ヤフージャパンのロゴを変えてみる", "content_scripts": [ { "matches": ["https://www.yahoo.co.jp/"], "js": ["index.js"] } ] }index.jsdocument.getElementsByTagName('h1')[0].style.backgroundImage = 'url(data:image/png;base64,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)';ワイ 「ヤフージャパンのロゴの所をスクリプトで書き換えたった」
〜おしまい〜
動画(YouTube)
ワイのGitHubとか
GitHub: https://github.com/yuzuru2
YouTube: https://www.youtube.com/channel/UCuRrjmWcjASMgl5TqHS02AQ
Qiita: https://qiita.com/yuzuru2
LINE: https://line.me/ti/p/-GXpQkyXAm
Twitter: https://twitter.com/yuzuru_program
成果物まとめ: https://qiita.com/yuzuru2/items/b5a34ad07d38ab1e7378
- 投稿日:2020-08-03T16:15:38+09:00
JSのpush()について
JSのpush()について
初心者がJSのpush()についてメモしています。
push() メソッド
配列の末尾に 1 つ以上の要素を追加する
参考const animals = ['pigs', 'goats', 'sheep']; const count = animals.push('cows'); console.log(count); // expected output: 4 console.log(animals); // expected output: Array ["pigs", "goats", "sheep", "cows"] animals.push('chickens', 'cats', 'dogs'); console.log(animals); // expected output: Array ["pigs", "goats", "sheep", "cows", "chickens", "cats", "dogs"]var items = [1,2,3,4]; var result = items.push(); console.log(result);
- 連想配列では「push」を使えない
- 「push」はあくまで配列の組み込みメソッド
- 「push」を使って、配列の中に別の配列を追加するのは特に問題ない
var obj = { name:'太郎', age:30 }; obj.push( ['花子', 28] ); console.log( obj );実行結果
Uncaught TypeError: obj.push is not a function
- 連想配列に要素を追加するには、まだ連想配列にないキー文字列を指定して値を代入します。疑似コードは以下
arr[キー配列] = 値//keyとvalueをセットする
- しかし、配列の中にオブジェクトを追加することは可能 連想配列に配列を pushはだめ、 配列にオブジェクトを pushは OK
var items = [1,2,3,4]; items.push({one:1,two:2,three:3}); console.log(items);「pop」は配列の末尾のデータを削除する
pop.jsvar array = [1,2,3,4,5]; array.pop(); console.log(array);[1,2,3,4]concatメソッドで結合
var array1 = ['aaa','bbb']; var array2 = ['ccc','ddd']; var result = array1.concat(array2); console.log(result);
- 投稿日:2020-08-03T15:22:19+09:00
JSの Promise
JSの Promise
Promise.prototype.then()
JS初心者が苦し紛れにメモを取っています。
- then() メソッドは Promise を返す。最大2つの引数、 Promise が成功した場合と失敗した場合のコールバック関数を取る。
const promise1 = new Promise((resolve, reject) => { resolve('Success!'); }); promise1.then((value) => { console.log(value); // expected output: "Success!" });?よくわからない、、
参考2:JavaScript の Promise: 概要
- JavaScript はシングル スレッド
- JavaScript はペイント、スタイルの更新、ユーザー操作の処理(テキストのハイライト表示やフォーム コントロールの操作など)と同じキューに入っています。 これらいずれかの処理のアクティビティが、他のアクティビティを遅延させる参考3 JavaScriptのPromiseとjQueryのPromise
今のところの理解
- 順番通りに実行させたいときに使うもの
- 昔は順番通りに実行させたいときにコールバックなどでなんとか解決されていたが、ES6から公式になったもの
- 少し疲れたので、また気力があるときに理解を進める
- 投稿日:2020-08-03T14:17:19+09:00
ウェブサイト作成用備忘録・2号:background-image の疑似アニメーション制御その2
日々の学習のアウトプットの為、自主学習の際に工夫した内容を記録していきます。
今回は background-image プロパティの疑似アニメーション制御について
background-image プロパティは transition プロパティや animation プロパティ等のアニメーション制御には対応していません。
そこで、自分なりに試行錯誤することで、疑似的なアニメーション制御に成功したので、その方法などを記録していきます。
方法2:JavaScript の動的制御で2種類の background-image の opacity を同時に操作することで、疑似的なクロスフェードを実装する。
記述例
HTML
<html> <body> <div id="background1" class=""></div> <div id="background2" class="none"></div> <button id="change_screen" class="" type="button">背景変更</button> </body> </html>CSS
body { margin: 0; } #background1, #background2 { min-height: 100%; min-width: 100%; opacity: 1; position: fixed; transition: all 1s; -webkit-transition: all 1s; } #background1 { background-image: url(背景画像1) } #background2 { background-image: url(背景画像2) } #background1.none, #background2.none { opacity: 0; } #change_screen { position: relative; }javascript
jQuery(document).ready(function(){ $("#change_screen").click(function(){ $("#background1").toggleClass("none"); $("#background2").toggleClass("none"); }); });解説
1・背景画像の表示専用の空の div タグを二種類を用意する。
2・予め片方の div タグにはクラス none を設定し、透過処理の初期設定を行う。
3・背景変更ボタンをクリックすると、toggleClass メソッドでそれぞれの div タグの none クラスが切り替わる。
4・それぞれの div タグの透明度が逆転し、transition プロパティによって、1秒間の間にそれぞれの背景がアニメーションすることで、結果的にクロスフェードする。
今回はこれで以上になります。
あくまで自分用の備忘録ですが、他の方の参考になれば幸いです。
- 投稿日:2020-08-03T12:06:09+09:00
JSのprototype_初心者覚書
初心者が記事などを見てメモをしています。
(記事を理解しようとしてメモをしているだけなので、下記の記事を見た方がわかりやすいです。すみません、、)「prototype」とは?
「プロトタイプ」と呼ばれる最小テンプレートがあり、それをコピーして新しいオブジェクトを作るようなイメージ
オブジェクト同士の繋がりを保持する機能もあるため「継承」も簡単に行えるわけです。JavaScriptはプロトタイプベースのオブジェクト指向言語「prototype」の使い方
基本的なコンストラクタの作成方法
var User = function(name,age){ this.name = name; this.age = age; } var taro = new User('太郎','12'); console.log(taro);console.jsUser {name: "太郎", age: "12"} age: "12" name: "太郎" __proto__: Objectprototypeを使ってメソッド定義
- プロパティだけでなく、メソッドも一緒に定義する場合、プロトタイプでメソッドを効率的に定義することができます。
オブジェクト名.prototype.メソッド名=function(){}
- メソッドはどのインスタンス先でも内容は同じなので、一緒にコピーするのは無駄
- 「参照」であれば、大量のメソッドがあってもインスタンス化する度にメモリが圧迫されない
var User = function(name,age){ this.name = name; this.age = age; } User.prototype.getName = function(){ return this.name; }var User = function(name,age){ this.name = name; this.age = age; this.getName = function(){ return this.name; } }
- 参照されないので、毎回メソッドがコピーされてメモリを無駄遣い
複雑な書き方.jsvar User = function(name, age) { this.name = name; this.age = age; } User.prototype.getName = function() { return this.name; } User.prototype.getAge = function() { return this.age; }
- オブジェクト形式でメソッドを定義すると簡潔に書ける
完結な書き方.jsvar User = function(name, age) { this.name = name; this.age = age; } User.protptype = { getName: function(){ return this.name; }, getAge: function(){ return this.age; } }プロトタイプチェーンの仕組み
var User = function() {}; var MemberA = function() {}; var MemberB = function() {};MembarA.prototype = new User():
- 「User」と「MemberA」のプロトタイプはそれぞれ参照できる状態に
MemberB.prototype = new MemberA();
- 「MemberB」と「User」も同じように参照関係
var User = function() {}; var Member = function() {}; User.prototype.hello = function() { return 'こんにちは!'; } Member.prototype = new User();Member.prototype.hello(); "こんにちは!"
- 投稿日:2020-08-03T11:36:17+09:00
JavaScript: Promise.raceのどれかがエラーになったらどうなるか?
JavaScriptの
Promise.race
は2つ以上のPromiseに対して、早く完了したものを戻り値として得られる関数です。もし、
Promise.race
で競争するPromiseのうち、どれかがエラー(reject)になったら、どうなるのでしょうか? これについて検証してみます。早いほうがエラーになった場合
下のサンプルコードでは、2つのPromiseが競争します。
ひとつは、100ミリ秒後にエラーになるPromise。もうひとつは、200ミリ秒後に成功裏に完了するPromiseです。
この2つのPromiseを
Promise.race
で処理すると、前者のエラーのほうが勝ち、new Error('100ミリ後にエラーになった処理')
のほうが戻り値になります。async function test1() { // 100ミリ後にエラーになる処理 const willError = new Promise((_, reject) => setTimeout(() => reject(new Error('100ミリ後にエラーになった処理')), 100), ) // 200ミリ秒後に完了する処理 const willSuccess = new Promise(resolve => setTimeout(() => resolve('200ミリ後に完了した処理'), 200), ) try { await Promise.race([willError, willSuccess]) } catch (error) { console.log(error.message) //=> "100ミリ後にエラーになった処理" } } test1()遅いほうがエラーになる場合
今度は、エラーのほうが遅い場合です。
ひとつめのPromiseは、100ミリ秒後に成功裏に完了します。もうひとつは、200ミリ秒後にエラーになるPromiseです。
この2つのPromiseを
Promise.race
で処理すると、前者のほうが勝ち、戻り値としては文字列の'100ミリ後に完了した処理'
になります:async function test2() { // 100ミリ秒後に完了する処理 const willSuccess = new Promise(resolve => setTimeout(() => resolve('100ミリ後に完了した処理'), 100), ) // 200ミリ後にエラーになる処理 const willError = new Promise((_, reject) => setTimeout(() => reject(new Error('200ミリ後にエラーになった処理')), 200), ) const result = await Promise.race([willSuccess, willError]) console.log(result) //=> "100ミリ後に完了した処理" } test2()
- 投稿日:2020-08-03T11:26:42+09:00
JavaScript: Promise.raceの基本的な使い方
Promise.race
は2つ以上のPromise
のどちらか早い方を戻り値として取得する関数です。下の例では、100ミリ秒後に完了するPromiseと、200ミリ秒後に完了するPromiseのどちらか早く完了するほうを取得するものです。この例では、前者のほうが早いので、
Promise.race
で得られる値はPromise<"100ミリ後に完了した処理">
のほうになります。async function test1() { // 100ミリ後に完了する処理 const one = new Promise(resolve => setTimeout(() => resolve('100ミリ後に完了した処理'), 100), ) // 200ミリ秒後に完了する処理 const two = new Promise(resolve => setTimeout(() => resolve('200ミリ後に完了した処理'), 200), ) const result = await Promise.race([one, two]) console.log(result) //=> "100ミリ後に完了した処理" } test1()
- 公式ドキュメント→Promise.race() - JavaScript | MDN
- 投稿日:2020-08-03T08:51:55+09:00
JS 配列の後方最初に一致したindex番号を返す(開始位置も指定できる)
lodashのlastIndexOfを作成してみた
const lastIndexOf = (array, selectNum, startIndex = array.length - 1) => { for (let i = startIndex; 0 <= i; i--) { if (array[i] === selectNum) { return i } } return -1 } console.log(lastIndexOf([1, 2, 1, 2], 2)) // => 3 console.log(lastIndexOf([1, 2, 1, 2], 5)) // => -1 console.log(lastIndexOf([1, 2, 1, 2], 2, 2)) //2番目から『2』の値が最初に一致したindexを返す // => 1
- 投稿日:2020-08-03T07:40:03+09:00
【JS/TS】Lookupテーブルを活用する
概要
戻り値の値が与えられた値に依存している際に、
if
やswitch
を使用して、下記のように対応する場面があるかと思います。Ifを使用した場合場合
const colorMapper = (color) => { if (color == "yellow") { return "黄"; } else if (color == "blue") { return "青"; } else if (color == "red") { return "赤"; } else { return "該当なし"; } }; colorMapper("yellow"); //=> 黄 colorMapper("pink"); //=> 該当なしSwitchを使用した場合
const colorMapper = (color) => { switch (color) { case "yellow": return "黄"; case "blue": return "青"; case "red": return "赤"; default: return "該当なし"; } }; colorMapper("yellow"); //=> 黄 colorMapper("pink"); //=> 該当なしLookupテーブルの活用
Lookupテーブルを使用することで、下記のように書くことが可能です。
これによって、コードの見易さの向上、また実行時間の短縮に繋がります。const colorsTable = { yellow: "黄", blue: "青", red: "赤", }; const colorMapper = (color) => colorsTable[color] || "該当なし"; colorMapper("yellow"); //=> 黄 colorMapper("pink"); //=> 該当なしちなみに、TypeScriptを使用すると下記のように書けます。
type ColorEn = "yellow" | "blue" | "red"; type ColorJa = "黄" | "青" | "赤"; const colorsTable: Record<ColorEn, ColorJa> = { yellow: "黄", blue: "青", red: "赤", }; const colorMapper = (color: ColorEn): ColorJa | "該当なし" => colorsTable[color] || "該当なし"; colorMapper("yellow"); //=> 黄 colorMapper("pink"); //=> 該当なし注意点
下記の記事でありますが、必ずしもLookupテーブルを使用することで実行時間の短縮が出来るわけでは無いようです。
参考
- 投稿日:2020-08-03T07:09:19+09:00
AWS Amplify での Cognito アクセスは React Context.Provider を使って認証処理を Hooks 化しよう
AWS Cognito は認証・認可を提供している AWS のサービスです。Amplify と統合することで、超高速に構築できます。Cognito を使用することで、API Gateway や S3 など他の AWS サービスとの統合がより簡単にできるようになります。
本記事では、Cognito を使用した React アプリケーションの実装例を紹介します。Cognito へのアクセスには amplify-js というライブラリを使用します。さらに React の Context.Provider という機能を使うことで認証に関連する処理をカスタムフックに集約する方法を考察します。
本記事で実装されたアプリケーションは以下のような動作をします。ログイン、ログアウト、サインアップ、確認メールなど。
本アプリケーションは Vercel にデプロイされています。
https://task-app.geeawa.vercel.app/loginまた、以下の GitHub リポジトリにホストしています。
https://github.com/daisuke-awaji/task-appamplify-js でも React Hooks を使いたい
先週は React アプリに Auth0 でシュッと認証を組み込んで Vercel に爆速デプロイする という記事を書きました。Auth0 のクライアントライブラリは非常に使い勝手がよく、
<Auth0Provider>
という Provider で包むだけでuseAuth0
フックを使用できるようになります。index.tsximport React from "react"; import ReactDOM from "react-dom"; import { Auth0Provider } from "@auth0/auth0-react"; import "bootstrap/dist/css/bootstrap.min.css"; import { App } from "./App"; ReactDOM.render( <Auth0Provider domain={process.env.REACT_APP_AUTH0_DOMAIN!} clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!} redirectUri={window.location.origin} > <App /> </Auth0Provider>, document.querySelector("#root") );一方で amplify-js にはこのような機能はありません。認証系処理のメソッドは
Auth
モジュールから取り出して使う必要があります。以下はサインアップするメソッドです。参考: 公式 Sign up, Sign in & Sign outimport { Auth } from "aws-amplify"; async function signUp() { try { const user = await Auth.signUp({ username, password, attributes: { email, phone_number, }, }); console.log({ user }); } catch (error) { console.log("error signing up:", error); } }メソッドしか用意されておらず、ログインユーザの情報などを React アプリでグローバルに保持する仕組みは自分で用意する必要があります。amplify-js でも Auth0 のような使いやすい DX(開発者体験)にしたい! ということが本記事のモチベーションです。つまり、以下のように使用したいわけです。
index.tsximport React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import "./index.css"; import CognitoAuthProvider from "./cognito/CognitoAuthProvider"; ReactDOM.render( <CognitoAuthProvider> <App /> </CognitoAuthProvider>, document.getElementById("root") );
<App/>
コンポーネントを<CognitoAuthProvider>
でラップするだけで、認証系の処理やログインユーザのステートを取り出すuseAuth
フックが使えるようにしていきます。LogoutButton.tsximport React from "react"; import { useAuth } from "../../cognito/CognitoAuthProvider"; export default function LogoutButton(props: any) { const { isAuthenticated, signOut } = useAuth(); if (!isAuthenticated) return null; return <Button onClick={() => signOut()} {...props} />; }React.Context とは
React の Context は配下の子コンポーネントにデータを渡すための便利な方法です。従来は
props
を使用することで、子コンポーネントにデータを渡していましたが、コンポーネントのネストが深くなると非常に面倒で複雑になります。 Context を使用することで 認証 や UI テーマ など多くのコンポーネントが使用する情報を共有して保持・取得できます。React.createContext
Context オブジェクトを作成します。React がこの Context オブジェクトが登録されているコンポーネントをレンダーする場合、ツリー内の最も近い上位の一致する Provider から現在の Context の値を読み取ります。
const MyContext = React.createContext(defaultValue);Context.Provider
全ての Context オジェクトには Context.Provider コンポーネントが付属しています。これにより Context.Consumer コンポーネントは Context の変更を購読できます。実際のユースケースでは Consumer ではなく、useContext フックを使用することが多いでしょう。
<MyContext.Provider value={/* 何らかの値 */}>useContext
Context オブジェクトを受け取り、その Context の value を返します。
<MyContext.Provider/>
が更新されると、このフックはMyContext.Provider
に渡された value を使用してコンポーネントを再レンダーします。const value = useContext(MyContext);認証情報を Context に集約する
さて、認証情報として以下のようなメソッドとステートを保持する Context を作っていきます。これらの値があればログイン、ログアウト、サインアップ、確認コード入力の一連の流れが実装できます。
項目 概要 isAuthenticated ログインしているか isLoading ローディング中か(画面制御で使用) user ログインしているユーザの情報 error ログイン処理、サインアップ処理などでエラーがあれば詰める signIn サインインする。 signUp サインアップする。 confirmSignUp サインアップ確認コードを入力する signOut サインアウトする。 State
Context が保持するステートの定義(インタフェース)を作成します。
import { CognitoUser } from "amazon-cognito-identity-js"; export interface AuthState { isAuthenticated: boolean; isLoading: boolean; user?: CognitoUser; error?: any; } const initialState: AuthState = { isAuthenticated: false, isLoading: false, }; const stub = (): never => { throw new Error( "You forgot to wrap your component in <CognitoAuthProvider>." ); }; export const initialContext = { ...initialState, signIn: stub, signUp: stub, confirmSignUp: stub, signOut: stub, };Context
Context オブジェクトを作成します。各コンポーネントから取り出すためのカスタムフック
useAuth()
を合わせて作成しておきます。import React, { useContext } from "react"; import { SignUpParams } from "@aws-amplify/auth/lib-esm/types"; import { CognitoUser } from "amazon-cognito-identity-js"; import { AuthState, initialContext } from "./AuthState"; import { LoginOption } from "./CognitoAuthProvider"; interface IAuthContext extends AuthState { signIn: (signInOption: LoginOption) => Promise<void>; signUp: (params: SignUpParams) => Promise<CognitoUser | undefined>; confirmSignUp: (params: any) => Promise<void>; signOut: () => void; } export const AuthContext = React.createContext<IAuthContext>(initialContext); export const useAuth = () => useContext(AuthContext);Provider
最後に Provider には Cognito とやりとりする処理と、認証情報を保持する処理を実装します。
import React from "react"; import { useState, useEffect } from "react"; import { SignUpParams } from "@aws-amplify/auth/lib-esm/types"; import { CognitoUser } from "amazon-cognito-identity-js"; import { Auth } from "aws-amplify"; import Amplify from "aws-amplify"; import { AuthContext } from "./AuthContext"; export type LoginOption = { username: string; password: string; }; interface ICognitoAuthProviderParams { amplifyConfig: { aws_project_region: string; aws_cognito_identity_pool_id: string; aws_cognito_region: string; aws_user_pools_id: string; aws_user_pools_web_client_id: string; oauth: { domain: string; scope: string[]; redirectSignIn: string; redirectSignOut: string; responseType: string; }; federationTarget: string; }; children: any; } export default function CognitoAuthProvider(props: ICognitoAuthProviderParams) { Amplify.configure(props.amplifyConfig); const [isAuthenticated, setIsAuthenticated] = useState(false); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [user, setUser] = useState<CognitoUser>(); useEffect(() => { checkAuthenticated(); currentAuthenticatedUser(); }, []); const checkAuthenticated = () => { setIsLoading(true); Auth.currentSession() .then((data) => { if (data) setIsAuthenticated(true); }) .catch((err) => console.log("current session error", err)) .finally(() => { setIsLoading(false); }); }; const currentAuthenticatedUser = async (): Promise<void> => { const user: CognitoUser = await Auth.currentAuthenticatedUser(); setUser(user); }; const signIn = async ({ username, password }: LoginOption): Promise<void> => { setIsLoading(true); try { await Auth.signIn(username, password); setIsAuthenticated(true); } catch (error) { console.log("error signing in", error); setError(error); setIsAuthenticated(false); } setIsLoading(false); }; const signUp = async ( param: SignUpParams ): Promise<CognitoUser | undefined> => { setIsLoading(true); let result; try { result = await Auth.signUp(param); setUser(result.user); } catch (error) { console.log("error signing up", error); setError(error); } setIsLoading(false); return result?.user; }; const confirmSignUp = async ({ username, code }: any): Promise<void> => { setIsLoading(true); try { await Auth.confirmSignUp(username, code); setIsAuthenticated(true); } catch (error) { console.log("error confirming sign up", error); setError(error); } setIsLoading(false); }; const signOut = () => { setIsLoading(true); Auth.signOut() .then(() => { setIsAuthenticated(false); }) .catch((err) => console.log("error signing out: ", err)) .finally(() => { setIsLoading(false); }); }; return ( <AuthContext.Provider value={{ isAuthenticated, isLoading, signIn, signUp, confirmSignUp, signOut, user, error, }} > {props.children} </AuthContext.Provider> ); }使用方法
ここまで準備ができれば使用する側はこの
CognitoAuthProvider
でコンポーネントをラップすることでuseAuth()
フック経由で各種ステートの値またはメソッドを使用できます。amplifyConfig として設定値は外部ファイルで保持しています。
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import "./index.css"; import CognitoAuthProvider from "./cognito/CognitoAuthProvider"; import awsconfig from "./aws-exports"; ReactDOM.render( <CognitoAuthProvider amplifyConfig={awsconfig}> <App /> </CognitoAuthProvider>, document.getElementById("root") );amplifyConfig は以下のようなファイルになります。
const amplifyConfig = { aws_project_region: "ap-northeast-1", aws_cognito_identity_pool_id: "ap-northeast-1:12345678909876543234567890", aws_cognito_region: "ap-northeast-1", aws_user_pools_id: "ap-northeast-1_xxxxxxxx", aws_user_pools_web_client_id: "xxxxxxxxxxxxxxx", oauth: { domain: "mydomain.auth.ap-northeast-1.amazoncognito.com", scope: [ "phone", "email", "openid", "profile", "aws.cognito.signin.user.admin", ], redirectSignIn: "http://localhost:3000/", redirectSignOut: "http://localhost:3000/logout/", responseType: "code", }, federationTarget: "COGNITO_USER_POOLS", }; export default amplifyConfig;ログアウトボタンのコンポーネントです。コードベースをシンプルにできました。
LogoutButton.tsximport React from "react"; import { useAuth } from "../../cognito/CognitoAuthProvider"; export default function LogoutButton(props: any) { const { isAuthenticated, signOut } = useAuth(); if (!isAuthenticated) return null; return <Button onClick={() => signOut()} {...props} />; }さいごに
React の Context を使用することで、認証情報などのグローバルな値を一元的に管理できるようになります。
ただ、 Context は多くのコンポーネントからアクセスされる場合に使用することとしましょう。
Context はコンポーネントの再利用をより難しくする為、慎重に利用してください。本記事で紹介した React.Context を使用したカスタムフックを使用するという発想はそのうち amplify-js に PullRequest しようと思います。Cognito ユーザ(または Amplify ユーザ)が個別にこのような実装をしなくとも、ライブラリとして提供し、すぐに簡単なインタフェースで認証処理を実現できるようにしていきたいですね。
- 投稿日:2020-08-03T02:19:50+09:00
Magentaチームによる機械学習&音楽のリモートワークショップ&ハッカソンイベント”Bit Rate”が開催
Google の機械学習音楽ライブラリーMagenta開発チームと、サンフランシスコのメディアアート機関”Gray Area”による機械学習&音楽のリモートワークショップ&ハッカソンイベント”Bit Rate”が8月7日〜9月7日に開催されます。
開催場所はもちろんアメリカサンフランシスコと言う事ですが、今回はコロナ禍の状況下でリモート開催と言う事で日本からも参加できるのではないかと思います。
https://grayarea.org/event/bitrate-ml-music-series/
主にJavaScriptの音楽ライブラリーやMagentaを組み合わせて、機械学習&音楽の新しいアプリを開発するワークショップとハッカソンの組み合わせでスケジュールは下記の様になっています。
INTRODUCTION & OPENING – AUGUST 7
5:30 – 6pm PST: Introductions by Gray Area and Magenta team at Google Research.WORKSHOPS & LECTURES – AUGUST 7 – AUGUST 9
August 7, 6-9pm PST: p5.js Workshop – Instructor: Rachel Rose Waterhouse
August 8, 11am – 2pm PST: Magenta.js Workshop – Instructor: Tero Parviainen
August 9, 12pm – 3pm PST: ml5.js Workshop – Instructor: Stephanie AndrewsSERIES BEGINS – AUGUST 7
Teams gather and begin prototyping process.PROJECT SUBMISSIONS – AUGUST 31
Projects created will be submitted on devpost.com.CONCLUSION & AWARDS – SEPTEMBER 7
Finalists selected and announced.Bit Rate概要
8月7日にスタート。
7、8、9日はそれぞれワークショップが開催され8月7日
Audio Visualization with P5.js + Tone.js Workshop
P5.jsとTone.jsを使用したオーディオビジュアライゼーションワークショップ
https://grayarea.org/workshop/bitrate-series-audio-visualization/
8月8日
Making Interactive Music Apps with Magenta.js Workshop
Magneta.js(JavaScript版Magenta)を使用した音楽アプリのワークショップ
https://grayarea.org/workshop/bitrate-series-interactive-music-apps/
8月9日
Building An Interactive Machine Learning Orchestra Using ml5.js Workshop
JavaScriptでインタラクティブな機械学習オーケストラの作成ワークショップ
https://grayarea.org/workshop/bitrate-series-machine-learning-orchestra/
その後31日までハッカソンが開催されます。
通常リアル開催だと週末の3日間だけなどが多く、正直何かの開発をするのは時間的に厳しいのですが、開催場所を確保しなくても良いオンラインの場合、この様な長期のハッカソンも可能という事でしょう。
1ヶ月近くあれば実践的なアプリの開発も十分可能ではないかと思います。8月31日に締め切りで提出
9月7日に優勝チームが発表されます。優勝チームには500ドルの他、Magetna開発チームへのプレゼンテーション、その他Google Nest Hubなどが贈呈されるとの事です。
日本の方も(英語がある程度できれば)今回は参加できそうなので是非チャレンジしてみてはいかがでしょうか?
- 投稿日:2020-08-03T02:04:30+09:00
Spring+Vue.js でAPIの連携をする
はじめに
今回SpringフレームワークでRestAPIを作成し、
Vue.jsでデータを表示させることをゴールに記載していきます。
(Javaのインストール等の初期設定となる環境構築は省きます。)プロジェクト作成
◇Vue.js
VueCliを用いて作成していきます。
①VueCliをnpmよりインストールする
コンソールnpm install -g @vue-cli
②プロジェクト作成
コンソールvue create practice
実行すると、アプリケーション作成に必要な設定ファイルやサンプルソースが自動で作成されます。
③サンプル画面確認
作成したプロジェクト配下にて以下実行コンソールnpm run serve
◇Spring
「Spring Tools for Eclipse」を使用して、進めていきます。
①プロジェクト作成
「Spring Starter Project」を選択肢プロジェクトを作成するプロジェクトを作成すると以下のようなフォルダ、ファイルが作成されます。
(今回は「gradle」を使用しております。)
RestAPIの作成
H2データベースからデータを取得するAPIを作成します。
①H2データベース準備
H2データベースとは・・・・・
JAVAプラットフォーム上でオープンソースのRDB
「インメモリデータベース」として使用が可能でSpring bootではデフォルトで付属されているため複雑な設定不要
以下のJDBCドライバがすでに登録されている
今回は「インメモリデータベース」として使用するため、初期化するテーブル、データを作成していきます。
「src/main/resources」配下に「data.sql」、「schema.sql」を配置する。
アプリ起動時、インメモリデータベースのため毎回初期化されます。
初期化時には2つのSQLが自動で実行されます。
data.sql
data.sqlINSERT INTO person(code, name, belong_nm) VALUES('001', 'テスト1', '総務部'); INSERT INTO person(code, name, belong_nm) VALUES('002', 'テスト2', '人事部');
schema.sql
schema.sqlCREATE TABLE person ( id INT NOT NULL AUTO_INCREMENT, code VARCHAR(100) NOT NULL, name VARCHAR(100) NOT NULL, belong_nm VARCHAR(500) NOT NULL, PRIMARY KEY(id) );プロジェクト上で右クリックし、「Spring Boot App」にて起動する
起動後「http://localhost:8080/h2-console」
にアクセスし、テーブルが作成されていることを確認②Controller、Serviceを作成する
ControllerからServiceを呼び出します。
一旦DBの参照は行わず、固定の値を戻すようにする。固定値をListに詰めて返却します。
PracticeServiceImp.java
PracticeServiceImp.javapackage com.example.demo.service; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; @Service public class PracticeServiceImp implements PracticeService { @Override public List<String> getAll() { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); return list; } }Service呼び、取得した値をListに詰めて、返却します。
PracticeController.java
PracticeController.javapackage com.example.demo.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.demo.service.PracticeService; import com.example.demo.service.PracticeServiceImp; @RestController @RequestMapping("api/practice") public class PracticeController { private final PracticeService practiceService; @Autowired public PracticeController(PracticeServiceImp practiceService){ this.practiceService = practiceService; } @GetMapping public List<String> getAll() { List<String> list = practiceService.getAll(); return list; } }
実際に実行し確認していきます。
「http://localhost:8080/api/practice」
にアクセスし、固定値が表示されました。③DBから取得した値をFormクラスに格納し、returnするよう修正
DBとの接続、SQLの発行はDAOクラスに任せます。
「Controller(リクエスト、レスポンスのハンドリング)」「Service(ロジック)」「Dao(DB操作)」という役割です。
PracticeServiceImp.java
PracticeServiceImp.javapackage com.example.demo.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.demo.dao.PracticeDao; import com.example.demo.form.PracticeForm; @Service public class PracticeServiceImp implements PracticeService { private final PracticeDao dao; @Autowired public PracticeServiceImp(PracticeDao dao) { this.dao = dao; } @Override public List<PracticeForm> getAll() { // List<PracticeForm> list = new ArrayList<>(); // // list.add("1"); // list.add("2"); // list.add("3"); return dao.getAll(); } }
PracticeController.java
PracticeController.javapackage com.example.demo.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.demo.form.PracticeForm; import com.example.demo.service.PracticeService; import com.example.demo.service.PracticeServiceImp; @RestController @RequestMapping("api/practice") @CrossOrigin(origins = {"http://localhost:8081"}) public class PracticeController { private final PracticeService practiceService; @Autowired public PracticeController(PracticeServiceImp practiceService){ this.practiceService = practiceService; } @GetMapping public List<PracticeForm> getAll() { List<PracticeForm> list = practiceService.getAll(); return list; } }データを格納するための新規ファイル
PracticeForm.java
PracticeForm.javapackage com.example.demo.form; import javax.validation.constraints.NotNull; public class PracticeForm { public PracticeForm() {}; public PracticeForm(int id, String code, String name, String belong_nm) { super(); this.id = id; this.code = code; this.name = name; this.belong_nm = belong_nm; } @NotNull private int id; @NotNull private String code; @NotNull private String name; @NotNull private String belong_nm; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBelong_nm() { return belong_nm; } public void setBelong_nm(String belong_nm) { this.belong_nm = belong_nm; } }DB操作するための新規ファイル
PracticeDaoImp.java
PracticeDaoImp.javapackage com.example.demo.dao; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.example.demo.form.PracticeForm; @Repository public class PracticeDaoImp implements PracticeDao { private final JdbcTemplate jdbcTemplate; @Autowired public PracticeDaoImp(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public List<PracticeForm> getAll() { // TODO Auto-generated method stub String sql = "select id, code, name, belong_nm from person"; List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql); List<PracticeForm> list = new ArrayList<PracticeForm>(); for(Map<String, Object> result : resultList) { PracticeForm practiceForm = new PracticeForm(); practiceForm.setId((int)result.get("id")); practiceForm.setCode((String)result.get("code")); practiceForm.setName((String)result.get("name")); practiceForm.setBelong_nm((String)result.get("belong_nm")); list.add(practiceForm); } return list; } }DBの値が取得できております。
Vue.jsからAPIを実行
フロントエンド側からAPIを呼び出しデータを表示させます。
①axiosをインストール
APIを実行するために「axios」を使用します。
axios:HTTP通信が可能なJavaScriptのライブラリコンソールnpm install --save axios
②axiosでAPIを呼び出す。
※VueもSpringもサーバーを起動させておく。
axiosにて、APIのURLを指定しデータを取得する。
Home.vue
Home.vue// Home.vue <template> <div> {{ people }} </div> </template> <script> import axios from 'axios' export default { data () { return { people: [] } }, methods: { getPerson () { const path = 'http://localhost:8080/api/practice' axios.get(path) .then(response => { this.people = response.data }) .catch(error => { console.log(error) }) } }, created () { this.getPerson() } } </script>データが取得されました。
③テーブルに表示させる
UIフレームワーク「vuetify」のデータテーブルを用いて、画面をそれっぽく加工してみます
Person.vue
Person.vue// Person.vue <template> <div> <h1>社員一覧</h1> <v-data-table :headers="headers" :items="people"> </v-data-table> </div> </template> <script> import axios from 'axios' export default { components:{ }, data () { return { people: [], singleSelect: false, selected: [], headers: [ { align: 'start', sortable: false, }, { text: 'ID', value: 'id' }, { text: '氏名', value: 'name' }, { text: '社員コード', value: 'code' }, { text: '所属名', value: 'belong_nm' }, ], } }, methods: { getNews () { const path = 'http://localhost:8080/api/practice' axios.get(path) .then(response => { this.people = response.data }) .catch(error => { console.log(error) }) } }, created () { this.getNews() } } </script>データテーブルを使って表示させることで、簡単にそれっぽいものが表示されました。
まとめ
簡単なAPIを作成しそれをフロントエンドと連携させることができました。
いまどきのフレームワークのおかげでどこにどういうロジックを書くかがだいたい決まっております。
そのため、役割もはっきりし便利ですね。参考文献
参考にさせていただきました。
https://b1tblog.com/2020/03/17/spring-rest-2/
- 投稿日:2020-08-03T00:44:07+09:00
[JavaScript] セミコロンをつければ絶対安心できるのか?;
はじめに
こちらの記事 を読んで、改めてJavaScriotの
;
について気になったので調べてみました。結論
私が思ったより安心じゃなかった。
セミコロンありのスタイルでも、下記のように予期せぬセミコロンの挿入が起こってしまうパターンがあります。
function f() { return { foo: 'foo' }; } f(); // undefinedセミコロンに関することは参考にさせていただいたセミコロンに関する ESLint ルールという記事に全部書いてありました
以下の内容はリンク先の記事に(個人的な感想以外)書いてあることなので、ぜひそちらを参考にしてください!
ASI(Auto Semicolon Insertion)
JavaScript の改行箇所で構文エラーがあった際にセミコロンにを自動的に補い再解釈する言語機能のことですが、結局のところこいつが曲者で、初心者や他言語からJavaScriptを触るようになった方が理解し難く感じる要因でしょう。
ECMAScript の言語仕様に ASI 利用に関する警告が ES2019 から追加 されています。
ASIの振る舞いで気をつけるべきこと
予期せぬセミコロン挿入の欠損
console.log(4) // > 4 ['foo', 'bar'].forEach(el => console.log(el)) // > Uncaught TypeError: Cannot read property 'bar' of undefined
console.log(4)['foo', 'bar'].forEach
が構文上正しいと解釈され、セミコロンの自動挿入が起こりません。,
がカンマ演算子として解釈されるため、bar
が未定義というエラーが出ます。このパターンは実行してみてエラーで気づくことができそうです。また、TypeScriptなら型チェックで気づけます。
予期せぬセミコロン挿入
function f1() { return 2020; } function f2() { return { foo: 'foo' }; } function f3() { return ({ foo: 'foo', bar: 'bar' }); } f1(); // undefined f2(); // undefined f3(); // undefinedこちらが先に上げたパターンと同じですが、
return
のあとにセミコロンが自動挿入され、改行後の値が返されません。また、f2の関数には Object そのものが存在していません。
{ foo: 'foo' }
の部分は Object ではなく、ブロックとして解釈されてしまっています。foo:foo
はラベル構文です。意味のないブロックですが、文法そのものに影響しないので無視されています。C#とか Allman brace style が推奨の言語から来た方は引っかかる?(多くの場合はそんなことないでしょうが)
Lint などがない場合は間違って改行していた場合気づかないかもしれませんね。ちなみに、ラベル構文の後にカンマをつけることはできないため以下は構文エラーになります。
function f() { return { foo: 'foo', bar: 'bar }; } // > Uncaught SyntaxError: Unexpected token :まとめ
今回調べてみて結局のところ、セミコロンありなのかなしなのかは好みの問題という域を出ないように感じました。
それよりも、ASIの独特の動きを理解して(といってもそんなに多くの動作は無いように思います。)、Lint や Formatter などのツールをしっかり活用することで、こういった問題を起こさない様にすることの方が重要だと思います。特に、ESLintやPrettierを使う際にはJavaScript Standard Styleのようなプリセットを利用し、極力自力で設定しないことが、このような罠にハマらないために大切です。(JavaScript Standard Styleにはセミコロンあり版が存在します)
また、可読性についても一長一短で、個人によるところが大きいと思います。(私はセミコロンレスの方が見やすく感じます)
参考
セミコロンをつけ忘れただけなのに...【JavaScript】
To Semicolon, Or Not To Semicolon;
- 投稿日:2020-08-03T00:42:58+09:00
通常関数とアロー関数でのthisの違い【JavaScript】
最近JavaScriptを勉強し始めた中で
this
の使い方がイマイチぴんと来ていないので、今回は、
function(){・・・}
(通常関数)()=>{・・・}
(アロー関数)の
this
の使い方の違いについてまとめます。何が違うのか?
私自身、調べる前は「
this
の範囲が違うらしい」ということくらいしか分かっていませんでした。違いを一言で言うと、
- 通常関数の
this
は、function
を呼んだ時の.
の前についているオブジェクト- アロー関数の
this
は、関数の外のthis
を指してます。
通常関数を先に説明をします。
通常関数の
this
function test() { console.log(this) } var obj = {} obj.test = test obj.test() // => {test: ƒ}
this
はfunction
を呼んだ時の.
の前についているobj
オブジェクトとなります。function test() { console.log(this) } test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}オブジェクトを指定しない場合は、グローバルオブジェクトになります (non-strict モード時)。 strict モードでは
undefined
になります。とまあ、通常関数の場合は関数の呼び出し方に応じて
this
の中身が変わります。下の記事に呼び出し方による違いが書いてあります。JavaScript の this を理解する多分一番分かりやすい説明 - Qiita
記事は非常に分かりやすく書いてあります。が、実行の仕方によって
this
が変わってしまい、分かりにくくないですか? 私は読んでいて嫌になってきました。その複雑さを改善したのがアロー関数のようです。
アロー関数の
this
アロー関数の
this
は、関数の外側のthis
に固定されます。const test = () => { return this } // 関数の外側のthis const lexicalThis = this // 関数定義したタイミングで、関数の外側のthisを参照する console.log(test() === lexicalThis) //=> true // メソッドとして実行しても、thisはメソッドが属するオブジェクトを指さない const obj = { method: test } console.log(obj.method() === obj) //=> false console.log(obj.method() === lexicalThis) //=> true先ほどの通常関数の
obj.test()
はthis
=obj
として扱っていたのに対して、アロー関数のobj.test()
はthis
=lexicalThis
(関数の外側のthis
)として扱っていることが分かります。
アロー関数のthis
は、.
前のオブジェクトは指さず、必ず関数の外側のthis
を指します。また、強制的にあるオブジェクトと結びつける
bind
等も無視するようです。通常関数では有効です。通常関数のbindの例function test() { console.log(this) } var obj = { name: "obj" } var check = test.bind(obj) check() // => {name: "obj"}まとめ
通常関数は、さまざまな参照の仕方があるため汎用性があるが、
this
の対象が実行の仕方によって変動してしまう。
一方、アロー関数は、参照するオブジェクトに制約があるものの、オブジェクトが決まっているため分かりやすい。使用上の注意としては、
- アロー関数を使うときは、間違ってオブジェクトのメソッドとして使わないようにする。
- 通常関数を使うときは、
this
が使い方によって変わることに気を付ける。以上の2点でしょうか。
アロー関数で書くと格好よく書けるなー、としか思っていませんでした。
思った以上に様々な制約があり、他の違いもしっかり知っておく必要があると感じました。最後に
通常関数とアロー関数の違いは
this
の使い方だけではありません。
その他の違いについてはこちらをご覧ください。
JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。 - Qiita初投稿で拙い文章となってしまいましたが、最後までお読みいただきありがとうございました。
何か誤り等ありましたらご指摘ください。
- 投稿日:2020-08-03T00:27:02+09:00
【超初心者向け】30秒ではじめるプログラミング
はじめまして
Qiitaはじめてみましたゲバラです。今回はプログラミングを全く触ったことのない方にとりあえずプログラミングを10秒で触れていただく方法をご紹介します。
前置き
プログラミングって最初に何をしたらいいかわかりませんよね。やるにしても色々と準備しなきゃいけないし、時間もかかるわけです。
なのでプログラミングに超簡単に触れて、チャレンジするきっかけになれば幸いです。30秒でプログラミングしてみよう
1. PCを用意してください。
WindowsかMacでOKです。
2. GoogoleChromeを開いてください
3. 画面で右クリックして「検証」というメニューを押してください。
4. 右側でてくるウィンドウ内の上部にある「Console」というタブを押してください。
5. 以下のコードをコピーしてください。
let hako = "これからプログラミングをはじめてみよう。"; console.log(hako);6. 以下の場所にコピーしたコードを貼り付けて、Enterボタンを押してください。
7. 「これからプログラミングをはじめてみよう。」という文字が表示されればプログラムが実行できました。おめでとうございます!
実行したコードの解説
はじめてプログラムを実行できましたね。おめでとうございます!
これでプログラミングをはじめることができます。やっていることは二つです
1. 表示したい文字を変数という箱の中に定義する。
2. 定義した箱の中身を画面上に表示する// 「hako」という変数に「これからプログラミングをはじめてみよう。」という文字を入れています。 let hako = "これからプログラミングをはじめてみよう。"; // 「hako」の中身を画面に表示しています。 console.log(hako);でそうな質問
Q.PCがないです。
A.買いましょう。
Q.GoogleChromeって何?
A.Webブラウザのことです。普段インターネット見るのに使っているものです。
Q.「Console」ってどこ?
Q.左側にウインドウって何?
A.開発者向けのツールです。Webエンジニアになるならめちゃくちゃお世話になるものです。
次回予告、「自分への未来のメッセージをプログラムで書いてみる」
未来考えるの大変だから臨機応変にメッセージを組むプログラムを書いてみます。