20210509のJavaScriptに関する記事は30件です。

【javascript】配列内で指定した範囲を、指定したINDEXに割り込ませる

copyWithin()の移動版? 引数 名前 説明 1 arr 扱う配列 2 p 移動先のINDEX 3 f 移動させる範囲の開始位置 INDEX 4 t 移動させる範囲の終了位置 INDEX 開始位置と終了位置は逆になっても問題ない コード const moving = (arr, p, f, t) => { if (!Array.isArray(arr) || p===undefined) return arr; const m = arr.length; f = f===undefined || f<-m ? 0 : f<0 ? m+f : m<f ? m : f; t = t===undefined || m<t ? m : t<-m ? 0 : t<0 ? m+t : t; if (f===t || (f===0 && t===m)) return arr; if (f>t) [f,t] = [t,f]; p = p<-m ? 0 : p<0 ? m+p : m<p ? m : p; if (f<=p && p<=t) return arr; if (p<f) { return arr.slice(0, p).concat(arr.slice(f, t+1), arr.slice(p, f), arr.slice(t+1, m)) } else if (t<p) { return arr.slice(0, f).concat(arr.slice(t+1, p), arr.slice(f, t+1), arr.slice(p, m)) } }; const arr = [...Array(10).keys()]; console.log( moving(arr, -5, -1, -3) ); // [0, 1, 2, 3, 4, 7, 8, 9, 5, 6] console.log( moving(arr, arr.length, 2, 5) ); // [0, 1, 6, 7, 8, 9, 2, 3, 4, 5]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初投稿】HTML, CSS, JavaScriptで掲示板システムを作ってみた

【初投稿】HTML, CSS, JavaScriptで掲示板システムを作ってみた 【今回作った掲示板】 普段は競技プログラミングやアルゴリズムを勉強しているのですが、今回はWebプログラミングに挑戦してみました! フロントエンドはJavaScriptを使っています。今回は当掲示板で使用されているアンカーシステムについて紹介します。基本的には正規表現で>>1などの列を置き換えてリンクにして、そこにマウスが乗るとポップが表示されます。再帰することでポップ上のアンカーでも作用するようになっています。現在、新しいポップが下に表示されてしまっているのでそのうち頑張って直そうと思います。 anc.js (function ($) { $("#comment").html($("#comment").html().replace(/&gt;&gt;(\d)/g,"<a href=id$1 class='anc'>" + "&gt;&gt;$1" + "</a>")); console.log(document); func(0); function func(n) { var l = 0; $(".anc").on("mouseenter", function (e) { if (l == 0) {//ポップは一つだけ l += 1; $(".container").prepend("<div class='pop" + n + "'>"); $(".pop" + n).css({//ポップの修飾 position: 'absolute', border: '1px solid #ccc', 'background-color': 'white', top: '0', left: '0', 'z-index': 1, width:'50%', }) var anc = $(this).attr("href"),//アンカーのhref取得 res = "<table class='table'> <tr><th>番号</th><th class='mes'>メッセージ</th></tr><tr>" + $("#" + anc).html() + "</tr></table>",//id検索 x = e.pageX, y = e.pageY; $(".pop" + n) .append(res) .css({ transform: "translate3d(" + x + "px," + y + "px,0)" }) .show(); $(".pop" + n).on("mouseleave", function () { $(this).remove(); }) func(n + 1); } }); } })(jQuery); 参考リンク Simplicity Simplicity 1 「某スレッド掲示板風」にレスアンカー機能を付けたい https://wp-simplicity.com/suport/topic/simplicity-1-「某スレッド掲示板風」にレスアンカー機能/ (2016年4月25日) 次の記事へ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ] sticky & Intersection Observer & opacityの併用はchromeでバグるっぽい

position: sticky (by css) -> スクロールして画面上部で固定。弟要素が上にかぶさる Intersection Observer (by js) -> 画面表示領域内への出入りでアニメーション用のクラス追加・削除 opacity (by css) -> Intersection Observerに伴うクラスによって表示/非表示アニメーション の3点を組み合わせると、 上へのスクロールはできるけど、下へのスクロールができなくなる。 (上へのスクロールは通常だけど、下へスクロールは弟要素が画面下でバウンスして戻ってくる) なんでだろう……。 どれか2個までの組み合わせなら動くんだけどな~。 この3つになると動かない… ↓書いたもの(一部省略) parent.vue <template> <div class="l-page"> <section class="list"> <Widgets-component v-for = "item in items" :item = "item" class = "item" :key = "item.id" :ref = "'item'" /> </section> </div> </template> <script> export default { data() { return { items: [//**省略**//] } }, updated() { let targets = []; this.$refs.item.forEach(el => targets.push(el.$el)); const observer = new IntersectionObserver((entries) => { let className_active = 'is-show'; entries.forEach((entry) => { if (entry.intersectionRatio >= 0.8) { entry.target.classList.add(className_active); } else { entry.target.classList.remove(className_active); } }); }, { root: null, rootMargin: '0px', threshold: 1 }); targets.forEach((target) => { observer.observe(target); }); } } </script> <style lang="scss" scoped> .list { height: 100%; overflow-y: auto; scroll-snap-type: y mandatory; } .item { position: sticky; top: 0; height: 82vh; scroll-snap-align: start; scroll-snap-stop: always; } .disc-item::v-deep { .item-body, .item-footer { opacity: 0; transition: opacity 600ms ease; } &.is-show { .item-body, .item-footer{ opacity: 1; } } } </style> child.vue <template> <div> <div class="item-thumb"> <img :src="item.images.url" /> </div> <div class="item-body"> some body element </div> <div class="item-footer"> some footer element </div> </div> </template> <script> export default { props: { item: { type: Object } } } </script> ちなみにopacityじゃなくて、backgroundだと普通に動くんだよな~。 なんか別の理由かな~。 parent.vue <style lang="scss" scoped> .disc-item::v-deep { .item-body, .item-footer { background: red; } &.is-show { .item-body, .item-footer{ background: blue; } } } </style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カレンダー一覧表示

仕様と完成イメージ 画像のように更新日以降のみがカレンダーの一覧票として表示されます。また,土曜日と日曜日は色が変わります。 先月または来月のボタンを押すことで表示される月が変更されます。更新日より前の月は何も表示されなくなります。 HTML main.html <main> <h4>カレンダー</h4> <dl> <div class="innerDate"> <button class="beforemonth" id="beforemonth" type="submit" onclick="beforemonth()">先月</button> <div class="sectionDate"> <span id="year"></span> 年 <span id="month"></span> 月 </div> <button class="nextmonth" id="nextmonth" type="submit" onclick="nextmonth()">来月</button> </div> <dt id = "date0"></dt> <dt id = "date1"></dt> <dt id = "date2"></dt> <dt id = "date3"></dt> <dt id = "date4"></dt> <dt id = "date5"></dt> <dt id = "date6"></dt> <dt id = "date7"></dt> <dt id = "date8"></dt> <dt id = "date9"></dt> <dt id = "date10"></dt> <dt id = "date11"></dt> <dt id = "date12"></dt> <dt id = "date13"></dt> <dt id = "date14"></dt> <dt id = "date15"></dt> <dt id = "date16"></dt> <dt id = "date17"></dt> <dt id = "date18"></dt> <dt id = "date19"></dt> <dt id = "date20"></dt> <dt id = "date21"></dt> <dt id = "date22"></dt> <dt id = "date23"></dt> <dt id = "date24"></dt> <dt id = "date25"></dt> <dt id = "date26"></dt> <dt id = "date27"></dt> <dt id = "date28"></dt> <dt id = "date29"></dt> <dt id = "date30"></dt> </dl> <script> printDate(); for(i = 0; i <= 30; i++){ printDatenew(i); } </script> </main> CSS sample.css h4 { margin: 0; text-align: center; } dl { width: auto; height: auto; margin: 0; padding: 2px; background-color: white; border: 2px solid rgb(19, 141, 211); } dt { float:bottom; width: 100%; margin-top: 2px; background-color: rgb(223, 223, 223); } .innerDate { width: auto; margin-top: 0px; margin-right: 5%; margin-bottom: 0%; margin-left: 5%; text-align: center; } .beforemonth { width: 60px; height: auto; } .sectionDate { margin: 0, 10%, 0, 10%; width: 60%; height: auto; margin-left: 5%; margin-right: 5%; display: inline-block; display: inline; text-align: center; } .nextmonth { width: 60px; height: auto; } JavaScript script.js var today=new Date(); var mon = today.getMonth()+1; var year= today.getFullYear(); var stateMonth;//現在表示されている月 var stateYear;//現在表示されている年 var setday = new Date(stateYear, stateMonth, 0); /*今日の日付を表示*/ function printDate(){ document.getElementById("month").innerHTML=mon; stateMonth = mon; document.getElementById("year").innerHTML=year; stateYear = year; } /*来月ボタンの定義*/ function nextmonth(){ var nextMonth = stateMonth % 12 + 1; if(nextMonth == 1){ var nextYear = stateYear + 1; document.getElementById("year").innerHTML = nextYear; stateYear = nextYear; document.getElementById("month").innerHTML = nextMonth; stateMonth = nextMonth; }else{ document.getElementById("month").innerHTML = nextMonth; stateMonth = nextMonth; } for(i = 0; i <= 30; i++){ printDatenew(i); } } /*先月ボタンの定義*/ function beforemonth(){ var beforeMonth = stateMonth % 12 - 1; if(beforeMonth <= 0){ beforeMonth = beforeMonth + 12; } if(beforeMonth == 12){ var beforeYear = stateYear - 1; document.getElementById("year").innerHTML = beforeYear; stateYear = beforeYear; } document.getElementById("month").innerHTML = beforeMonth; stateMonth = beforeMonth; for(i = 0; i <= 30; i++){ printDatenew(i); } } /*有効な日付と曜日を返す*/ function getDate(i) { var er3 = 999; var lastday1 = new Date(today.getFullYear(), today.getMonth()+1, 0); var lastday2 = new Date(stateYear, stateMonth, 0); var day1 = new Date(today.getFullYear(), today.getMonth(), today.getDate()+i); var day2 = new Date(stateYear, stateMonth-1, i+1); var you1 = day1.getDay(); var you2 = day2.getDay(); var s = day1.getDate() var d = day2.getDate() if(stateYear < year){ return [er3, 0]; }else if(stateYear == year){ if(stateMonth < mon){ return [er3, 0]; }else if(stateMonth == mon){ if(day1 > lastday1){ return [er3, 0]; }else{ return [s, you1]; } }else{ if(day2 > lastday2){ return [er3, 0]; }else{ return [d, you2]; } } }else{ if(day2 > lastday2){ return [er3, 0]; }else{ return [d, you2]; } } } /*日付を表示する*/ function printDatenew(i) { var youbi = new Array("日","月","火","水","木","金","土"); var idlist = new Array("date0", "date1", "date2", "date3", "date4", "date5", "date6", "date7", "date8", "date9", "date10", "date11", "date12", "date13", "date14", "date15", "date16", "date17", "date18", "date19", "date20", "date21", "date22", "date23", "date24", "date25", "date26", "date27", "date28", "date29", "date30"); var [date, you] = getDate(i); const dateset = document.getElementById(idlist[i]); if(date == 999){ dateset.style.display = "none"; }else{ if(you == 0){ dateset.style.backgroundColor = 'rgb(255, 163, 163)'; dateset.innerHTML = date+"日("+youbi[you]+")"; dateset.style.display = ""; }else if(you == 6){ dateset.style.backgroundColor = 'rgb(163, 221, 255)'; dateset.innerHTML = date+"日("+youbi[you]+")"; dateset.style.display = ""; }else{ dateset.innerHTML = date+"日("+youbi[you]+")"; dateset.style.backgroundColor = 'rgb(223, 223, 223)'; dateset.style.display = ""; } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Array型のメソッドを使わずに配列を昇順に並び替える

push以外のArray型のメソッドを使用することを禁止し、配列を昇順に並び替える メソッドの確認はこちらからお願いします https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array const array = [2, 13, 42, 5, 25, 57, 1, 4, 78, 12, 11, 10, 2]; const min = Math.min.apply(null, array); const max = Math.max.apply(null, array); function ascendingOrder(arrry, min, max) { const resultArray = []; //配列内の最小値から最大値まで順に探せるようにfor文 for (i = 0; i <= max - min; i++) { //配列の数だけfor文を回す for (j = 0; j < arrry.length; j++) { //if文で最小値から順に配列に入れていく if (arrry[j] === min + i) { resultArray.push(arrry[j]); } } } return resultArray; } console.log(ascendingOrder(array, min, max)) // 出力結果 // [1, 2, 2, 4, 5, 10, 11, 12, 13, 25, 42, 57, 78];
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ビットコイン(BTC)の現在価格を教えてくれるLINE Botをつくってみた!

はじめに 皆さんこんにちは。 ゴールデンウィークも終わり、日常が戻ってきましたね。 さて、今回も仮想通貨の価格を教えてくれるアプリケーションを作ってみました! きっかけは、LINEで友人と仮想通貨についての会話しているとき、 「今、あの通貨いくらなんだっけ?」みたいな時がありまして、LINEの中で知れたらいいなというところからでした。 という事で、今回はLINEBotとAPIを利用して、Bitcoin価格を教えてくれる、LINEBotを作成しました。 ただただ、Bitcoinの価格を教えてくれるものですが何かの役に立ってくれればうれしいです! 概要 LINEのトークで「いまいくら?」と打ち込むとCoindeskの公開APIを参照しBitcoinの現在価格を返答してくれます。 環境 Node.js 15.13.0 axios 0.21.1 今回使用したAPI Coindesk API (https://api.coindesk.com/v1/bpi/currentprice.json) 処理の詳細 処理の全文は以下の通りです。 btc_price.js // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: 'チャンネルシークレット', channelAccessToken: 'チャンネルアクセストークン' }; // ########## ▼▼▼ API取得 ▼▼▼ ########## const sampleFunction = async (event) => { // ユーザーメッセージが「いまいくら」かどうか if (event.message.text !== 'いまいくら?') { return client.replyMessage(event.replyToken, { type: 'text', text: '「いまいくら?」と話しかけてね' }); } else { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: '調べています……' }); let pushText = ''; try { // axiosでBTCPriceのAPIを叩きます(少し時間がかかる・ブロッキングする) const res = await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json'); // 取得できたBTCのUSD価格を抜き出す const rates = res.data.bpi.USD.rate_float; pushText = `BTC価格は${rates}USDです!`; } catch (error) { pushText = '検索中にエラーが発生しました。'; // APIからエラーが返ってきたらターミナルに表示する console.error(error); } // 「プッシュ」で後からユーザーに通知します return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); } }; // ########## ▲▲▲ API取得 ▲▲▲ ########## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); API部分詳細 まず、はじめにユーザーからのメッセージが「いまいくら?」と合致しているか判定しています。 あっている場合は、「調べています……」と返し、APIを叩きに行きます。 異なる場合は、「「いまいくら?」と話しかけてね」と返答します。 btc_price.js const sampleFunction = async (event) => { // ユーザーメッセージが「いまいくら」かどうか if (event.message.text !== 'いまいくら?') { return client.replyMessage(event.replyToken, { type: 'text', text: '「いまいくら?」と話しかけてね' }); } else { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: '調べています……' }); APIの部分は下記のようにしています。 今回のAPIでは、USD以外にもGBP、EURも同時に拾ってくるものなので、USDのrate_floatを指定して表示させています。 btc_price.js let pushText = ''; try { // axiosでBTCPriceのAPIを叩きます(少し時間がかかる・ブロッキングする) const res = await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json'); // 取得できたBTCのUSD価格を抜き出す const rates = res.data.bpi.USD.rate_float; pushText = `BTC価格は${rates}USDです!`; } catch (error) { pushText = '検索中にエラーが発生しました。'; // APIからエラーが返ってきたらターミナルに表示する console.error(error); } // 「プッシュ」で後からユーザーに通知します return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); 終わりに 今回は、単純にBitcoinの現在価格を出してくれるものでした。 当初の野望は、仮想通貨名を入れるとその通貨の”現在価格”、”24H価格比較”、"vol"などの情報を出してくれたり、テクニカル的に買いか売りかを判断してくれたらいいなというものでした。 今後も、ちょこちょこ改良を加えていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React、Next.jsなぜページ遷移にはaタグではなく、Linkを使うのか

next.jsではなぜaタグではなくLinkを使うのかを先輩エンジニアに教えてもらったので備忘録 結論から言うと、 ページの読み込みを早くするため reactの状態変化を維持したままページ遷移を行うため 説明すると、 aタグはサーバーから別のページを取得するリンク Linkタグは同一ページ内で見た目だけ変更するリンク出そう。 Linkタグで同一ページ内で一部分だけ変更する場合、 サーバーとの通信が省略されるのでページの読み込みが格段に早くなる。 そして、ページが更新されてしまった場合reactの状態(state)がリセットされてしまうけど、 Linkタグで同一ページ内で一部分だけ変更する場合、状態(state)が維持されたまま次のページに移動できる。 と言うことみたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】filterメソッドについて超簡略化して整理してみた

はじめに Vue.jsを使ったアプリケーションを作成していると、 filterメソッドを利用する場面に遭遇しました。 今回はそこで学んだことを自身の備忘録も兼ねて簡単に整理しておきます。 filterメソッドとは filterメソッドは、JavaScriptの配列に用意されているメソッドです。 既存の配列から条件に合致した要素だけを抽出して新しい配列を作成するために使用します。 filterメソッドの使い方 アロー関数を用いた基本形は以下の通りです。 絞り込み対象の配列.filter( 引数 => 条件式 ); // 条件式がtrueであればデータは残され、falseの場合は除去される 実際に6以上の値のみを抽出するfilterメソッドを書いてみました。 var dataList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var result = dataList.filter(data => data >= 6); console.log(result); // 出力結果 // [6, 7, 8, 9, 10] 今回の記事は本質的な部分を削ぎ落とし、超簡略化した内容になっています。 filterメソッドは3つの引数を受け取ることができ...というように まだまだ整理すべきことはあるのですが、それはまた別の機会に譲ります。 参考資料 ・MDN Web Docs Array.prototype.filter()  https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter ・書籍「たった1日で基本が身に付く! Vue.js 超入門」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js 勉強メモ】v-forを使う時の注意点 key属性

はじめに 仕事で使う事になったので1からVue.jsについて学んだ(元々Angularでプロダクト開発をやっていた事はあるがAngularはだめか・・・)。 ちゃんと覚えておかないとまずそうな事を備忘録として1つ1つ残しておく。 v-forを使う時の注意点 key属性 v-forディレクティブを使う際には、key属性を付与する事で予期せぬバグが発生しないように注する必要がある。 予期せぬバグが発生してしまったパターンとして、v-forでリストレンダリングをした際に以下のような挙動になってしまった原因をみていく。 動画のソースコードは以下。 sample.html <body> <div id="app"> <ul> <div v-for="fruit in fruits"> <p>{{ fruit }}</p> <input type="text"> </div> </ul> <button @click="remove">先頭を削除</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { fruits: ['りんご', 'バナナ', 'ぶどう'] }, methods: { remove: function () { this.fruits.shift(); } } }) </script> </body> ここで意図していた動きとしては、それぞれのdivタグが消えるような動き方で、1度先頭を削除ボタンをクリックすると、inputタグには上から順に「Banana、Grape」と記載されている状態になる事・・・。 これの原因は、v-forディレクティブには、 要素の移動を最小限に抑えるアルゴリズムが採用されており、可能な限りその場の近い同じ要素を再利用しようとする という性質があるため。今回だと、配列の要素を消す前後の差分を考えて効率よくレンダリングしようとした結果、inputタグはDOM上は同じなので上記の動画のような挙動になってしまったという事。 これを解決するには、 key属性を付与し、要素とデータとを関連付ける(紐づける) ようにする。今回で言えばdivタグという要素に対してfruits配列の1つ1つのデータとを関連付けるようにする。そうすると、以下のように期待通りの動きになる。 動画のソースコード(抜粋)は以下。 sample.html <body> ・・・ <div v-for="fruit in fruits" :key="fruit"> <p>{{ fruit }}</p> <input type="text"> </div> ・・・ </body> これはVueに対し、あるkeyを持つ要素という情報を与える事で各divタグが一意の要素として扱われるようになり、同じ要素として判定されず再利用されなかったため。 ※key属性を使用する時の注意点 templateタグでは使えない理由:templateタグには属性は存在しないので v-forのindexをkey属性に指定しない理由:リストレンダリングする時のリストの中身が変化しても、リストの中身が変化するとそのインデックスも変化し、同じindexになってしまう場合があるから1 Vue.jsの勉強メモ一覧記事へのリンク Vue.jsについて勉強した際に書いた勉強メモ記事のリンクを集約した記事。 https://qiita.com/yuta-katayama-23/items/dabefb59d16a83f1a1d4 例えば今回のfruits配列で言えば、先頭の「りんご」を削除すると、「バナナ」のインデックスが0になってしまい、削除の前後でv-forのindexは変化しない事になる。すると、再利用されてしまい意図した動きにならない。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS Codeの.jsx拡張子のファイルのアイコンをReact("javascriptreact")に変えたいとき

はじめに まず、今回はコーディングとはあまり関係なく、何方かと言えば環境設定の話です。 タイトル通りですが、VS Codeの.jsx拡張子のファイルのアイコンをReactに変えたくなって悶々としておりました。 なんでこんな状況になったかというと、今学習しているUdemyのコースで講師の.jsxファイルのアイコンと自分の.jsxファイルのアイコンが何故か違ったからです。 ↓講師の画面 ↓自分の画面 こっちは.jsも.jsxのファイルも同じアイコンなのに… 自分のもReactのアイコンにしたい…(だって見た目かっこいいもん) 解決策 ということで、いろいろと調べながら試してみました。 (意外に日本語の情報はありませんでした。多分使っているエクステンションが違うのかな。それともわざわざ書くまでもないから記事になってないのかな…) もったいぶらずに結論からいうとsetting.jsonファイルで以下の記述を入れればいいんですね。 { "files.associations": { "*.js": "javascript", "*.jsx": "javascriptreact", "*.ts": "typescript", "*.tsx": "typescriptreact" } これで、ファイルを見てみると... できましたっ! VS Code画面左下の設定(⌘,) > 検索画面で associations と記入すると前述のsetting.jsonの該当箇所を見つけることができます。 ちなみにはじめはvscode-iconsのエクステンションをインストールしているので、それ関連の設定を変更することを試してみてましたが、、、ダメでした。(以下試した設定) { "workbench.iconTheme": "vscode-icons", "vsicons.associations.files": [ { "*.js": "javascript", "*.jsx": "javascriptreact",` "*.ts": "typescript", "*.tsx": "typescriptreact", } ] } 参考にしたGitHubのvscode-iconsのレポジトリのIssueによると、どうやらVS CodeにインストールしていたBabel JavaScriptエクステンションの設定が干渉しているようでした。 URL: https://github.com/vscode-icons/vscode-icons/issues/859 自分のエディタで確認してみると、画面右下のとこで確かにファイル名の言語拡張子がBabel JavaScriptとして認識されていました。 ちなみに設定を上記の解決できた記述に直したら、JavaScript Reactにちゃんと変わっていました。 ちなみにvscode-iconsの設定でもカスタマイズ設定でできるみたいです。どうやら私がはじめに試した記法は間違っていたみたいですが、解決したのでその正しい記法は試していません。興味がある方はリンク先のイシューを見てみてください(該当のところのスクショを貼っておきます) URL: https://github.com/vscode-icons/vscode-icons/issues/859 参考記事 GitHub vscode-icons/vscode-icons After last update individual icons for jsx are gone #859 URL: https://github.com/vscode-icons/vscode-icons/issues/859 フロントエンド強化月間参加中!! またQiitaのフロントエンド強化月間にも参加中です! なんというタイミング...これを機にフロントエンド技術を一気に学んでいきます。 コチラ→Qiita フロントエンド強化月間
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

迷えるあなたに意思決定LINEBot!エヴァのMAGIが判断してくれます!

こんな時ありませんか? Yes or Noの二択で迷うとき! よくありますよね、こんな時にぜひ使ってほしいLINEBotを作製しました! 作製したLINEBot(なんちゃってMAGI)の概要 LINEでユーザーが質問事項を入力するとBotが内容を審議したように見せて、可決(賛成)か否決(反対)の二択で答えてくれます。 エヴァンゲリオンに出てくるスーパーコンピューターMAGIをイメージして作製しました。 エヴァを知らない方のために簡単にMAGIを説明すると3種類のスパコンが内蔵されており、3種類それぞれが違うアルゴリズムで回答を出します。(MAGI (まぎ)とは) 今回作製したBotはランダムで3回YesorNoを返すAPIを使用してMAGIに似せています。 また内容を確認しているように見せたかったので10文字以下の入力の場合はエヴァのキャラ、アスカに怒られる仕様となっています。 ・MAGI回答 ・アスカに怒られちゃいました。 環境 node.js v15.12.0 axios v0.21.1 Line/bot-sdk v7.3.0 ngrok 2.3.39 ソースコード 今回はYES NO ジェネレーターを使用して賛否を取得しています。 3回呼び出すことにより3つの意見を表現しています。 // ########## ▼▼▼ なんちゃってMAGI部分 ▼▼▼ ########## const flakyMAGI = async (event) => { //それっぽく見せるため10文字以下ならアスカの「あんたばかぁ」を返信 if (event.message.text.length < 10) { let ngText = `あんたばかぁ?そんな10文字以下の文章じゃMAGIも判断できるわけないでしょ\nどうせ「${event.message.text}」とか入力したんでしょ!` return client.replyMessage(event.replyToken, { type: 'text', text: ngText }); } else { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: '審議中……' }); let pushText = ''; //返信メッセージ let countYes = 0; //可決カウント let countNo = 0; //否決カウント let magiAnswer = '';//多数決の結果 try { //YesNoジェネレータで3つの可否を取得 const res1 = await axios.get('https://yesno.wtf/api'); const res2 = await axios.get('https://yesno.wtf/api'); const res3 = await axios.get('https://yesno.wtf/api'); const resAnswer1 = res1.data.answer; const resAnswer2 = res2.data.answer; const resAnswer3 = res3.data.answer; //取得結果から可決、否決をカウント if(resAnswer1 == 'yes'){ countYes = countYes+ 1; } else { countNo = countNo + 1; } if(resAnswer2 == 'yes'){ countYes = countYes+ 1; } else { countNo = countNo + 1; } if(resAnswer3 == 'yes'){ countYes = countYes+ 1; } else { countNo = countNo + 1; } //可決が3票以上の場合結果を可決とする if(countYes == 3){ magiAnswer = '可決'; } else { magiAnswer = '否決'; } //結果文言 pushText = `MAGIは可決${countYes},否決${countNo}で、${magiAnswer}を回答しています`; } catch (error) { pushText = 'MAGI、完全に沈黙!信号受け付けません'; // APIからエラーが返ってきたらターミナルに表示する console.error(error); } // 「プッシュ」で後からユーザーに通知します return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); } }; うまくいかなかったこと プログラム自体はとても簡単でAPIの使用部分だけ書ければ後は単純なif文ばかりでしたので苦戦するのことはないだろうと踏んでいたら意外なところで失敗が。。。 LINEDeveroperサイト自体を自動翻訳で日本語にしていると、Botのチャネルアクセストークンをまでもおかしな翻訳をされてしまうみたいです。 それに気づかず、翻訳後のチャネルアクセストークンを使用しなんちゃってMAGIから全然返信が来ない。 なんてことになっていました。 チャネルアクセストークンを発行、再発行の時には自動翻訳をOFFにして発行しましょう! これを期に英語覚えようかな笑 ちなみにこちらの記事を参考にLINEBotのアカウントを作製しました 1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest まとめ 今回はLINEBotでMAGIを作ってみました。 正直今回の出来はあまりよくなかったかなと思っています。 本当は賛否の判定はテキトーでも同じ質問に対して同じ回答を出すというところまで作ってみたかったのです。 しかしいい方法が思い浮かばず今回のようなランダム回答となってしまいました。 さっきと言っていることが違うので原作のMAGIには程遠い(´;ω;`) 方法で今回は時間をかけてしまい、手を動かした時間が短ったので次回作製するものはプログラムにも注力できたらと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubプロフィールをカッコよくするGitHubActionsをつくってみた(つもり)

この土日にお勉強を兼ねて GitHubActions を作ってみました。 GitHub の各種情報を取得して、SVG に出力する GitHubActions です。 出力イメージは以下の通り。 (芝生が寂しかったので、自作の git-turf コマンドで文字を書いてみました) また、マーケットプレイスにも公開してしまいました。 作ってみてわかったのは、自分にカッコよくするセンスがないという絶望感でした。 きっかけ 偶然見かけた以下の Qiita 記事がきっかけになりました。 GitHub のプロフィール用のリポジトリの話 GitHubActions で GitHub のプロフィール用リポジトリに自動で画像を生成するツール GitHub のコントリビュートカレンダー(いわゆる芝生)の立体化するブラウザ拡張 なんだか、自分でも作ってみたい! とおもったのです。 GitHub GraphQL まず、芝生の立体化はやってみたい。 芝生情報を取得する方法は、大きく分けて2つありそう。 1つは、GitHub の GraphQL で問い合わせて情報を取得する方法。 もう1つは、GitHub のプロフィールページに含まれる SVG を解析する方法。 今回はいろいろ考えて、GraphQL にしました。 というか、その他の情報(言語情報とか、全リポジトリの星の合計数とか)も取得して載せたかったので、ほぼ一択だったとおもう。 とりあえず、欲しい情報をまとめてみました。 コントリビュートカレンダー(芝生)の情報 コントリビュートの種類と数(commit, issue, PullRequest, Review, Repository) コントリビュートの言語種類 厳密にいえば、コントリビュートの言語ではなく、コントリビュートしたリポジトリの主要言語をつかう 全リポジトリの星の数と、フォークされた数 GraphQL を使うのは始めただったのですが、GraphQL は以下のサイトで実際に試せるので、まずはクエリを作って試してみました(最初に使うときに認可が求められるので、確認してOKする) クエリーはこんな感じかな(ソースは直しても記事の方は直さないので少し古い版かもしれません。参考にするときには注意してください) query ($login: String!) { user(login: $login) { contributionsCollection { contributionCalendar { totalContributions weeks { contributionDays { contributionCount contributionLevel date } } } commitContributionsByRepository(maxRepositories: 100) { repository { primaryLanguage { name color } } contributions { totalCount } } totalCommitContributions totalIssueContributions totalPullRequestContributions totalPullRequestReviewContributions totalRepositoryContributions } repositories(first: 100) { nodes { forkCount stargazerCount } } } } 現状は、見ての通り集計対象のリポジトリ上限は100個にしています。 (……でも100個以上のリポジトリ持っている人もいるよな、どうしよう) SVG の組み立て 情報が取得できたら、次は SVG の組み立てです。 SVG の中身は、単なる XML なので、全部自力で造ることも可能だけど、せっかくなので d3.js を活用してみました。 以前、古い版の d3.js を少しだけ使ったことがあるので、作ること自体はそこまで困りませんでした。 (TypeScript で作っていたので、型で少し悩んだところがあるぐらい) また、SVG にはアニメーションができる機能があるというのを以前見かけたので、今回はそれも取り込もうと考えました。 SVG のアニメーションというと、JavaScript から SVG を加工してアニメーションをするか、CSSでアニメーションするかのイメージですが、SVG のタグでもある程度のアニメーションはつくれるようでした。 具体的には <animate> タグを登録して、時間に応じて値を変化させることができるようです。 今回は、立体化した芝生(緑のビルみたいな感じ)が表示した直後は短くなっていて、そこからググーっと伸びるようなアニメーションを設定してみました。 この時点では GraphQL は実際にはたたかず、上記エクスプローラで返ってきたデータをダミーデータとしてファイルに保存して、sれをもとにSVGを作成するプログラムを作りました。 SVG の組み立てる作業自体は特に問題なかったのですが、一番の問題は(冒頭でも言った通り)色や配置、見せ方のセンスが全くないということでした。絶望。 データーの見せ方について 前述の通り、今回は以下の情報をまとめて表示しようと思っています。 コントリビュートカレンダー(芝生)の情報 コントリビュートの種類と数(commit, issue, PullRequest, Review, Repository) コントリビュートの言語種類 厳密にいえば、コントリビュートの言語ではなく、コントリビュートしたリポジトリの主要言語をつかう 全リポジトリの星の数と、フォークされた数 芝生情報は立体化して表示するのが決まっていますが、それ以外をどう表現するかを考えました。 コントリビュートの種類については、レーダーチャートっぽく見せようと思いました。 ただ、通常の GitHub プロフィールも設定を変えるとレーダーチャートっぽいものが表示されますよね。 でも、あのチャートだと件数の多いものと少ないものの差が大きくて、よくわからない気がしています。 GitHub 純正のレーダーチャート: そこで、コントリビュート数の対数をとって、レーダーチャートにしたらどうかと考えました。 つまり、以下のようにします。 コントリビュート 0 件:対数が取れないので 0 とする コントリビュート 1 件:$log_{10}(1) = 0$ なので 0 とする コントリビュート 10 件:$log_{10}(10) = 1$ なので 1 とする コントリビュート 100 件:$log_{10}(100) = 2$ なので 2 とする コントリビュート 1000 件:$log_{10}(1000) = 3$ なので 3 とする コントリビュート 10000 件:$log_{10}(10000) = 4$ なので 4 とする コントリビュート 10001 件以上:レーダーチャートの範囲外になるので 4 に丸める これならレーダーチャートは 0~4の範囲で表せます。 こんなイメージです(上のレーダーチャートと同じ値での表示です): 次にコントリビュートの言語種類です。 言語情報はいくつかの情報が取得できるようです。 リポジトリに含まれる、言語別のサイズ(バイト数のようです) リポジトリの主要言語( primaryLanguage ) 今回は、コントリビュートしている先のリポジトリの主要言語を、コントリビュートした言語だと判断しています。 (つまり、主要言語が ruby となっているリポジトリのドキュメントに typo があって、プルリクを送ってマージされたとすると、ドキュメントしか直してないけど ruby にコントリビュートしている、と判断されます) 星の数とフォークされた数は、単に本人の全リポジトリの合計を表示するだけです。 (Organizationsにも自分が作ったパブリックリポジトリがあれば、それも加算されると思います) GitHubActions の種類 GitHubActions を作るのも、今回が始めてです。 GitHubActions には大きく分けて3種類あるみたい。 Docker コンテナ・アクション JavaScriptアクション 複合実行ステップアクション 今回は、この中の JavaScript アクションで作ってみました。 GitHubActions の実装 とりあえずローカルで SVG ができるようになったら、GitHubActions 化を進めます。 プロジェクトのルートに action.yml を作って、push して tag を作って、別のリポジトリで動かしてみると、エラー。 直してみて、push して tag を作り直して、動かしてみて、エラー。 と、手順がなかなか面倒ですね。 後から思ったのは、まずは小さな GitHubActions を作ってみて、それが動くところは事前に確認しておいた方が良かっただろうな、ということです。 また、大きく躓いたところが一つあります。 最初、github にある gitignore ファイルのリポジトリをもとに .gitignore ファイルを作っていました。 そうすると、アクション実行時に dist/index.js がないと怒られました。 「あー、自動でコンパイルしてくれるわけじゃないから、dist/ も push しないといけないのか」と思い push すると今度は node_modules がないと怒られました。 JavaScript アクションの作り方は以下のページに説明がありますが、そのなかに ターミナルから、action.yml、index.js、node_modules、package.json、package-lock.json、および README.md ファイルをコミットします。 node_modules を一覧表示する .gitignore ファイルを追加した場合、node_modules ディレクトリをコミットするため、その行を削除する必要があります。 という記述がありました。 非常に大量のファイルのある(噂ではブラックホール以上に重たいという) node_modules を commit しろとのこと(もちろん push もするのでしょう)。 「マジか!」と思いながら、push したらさらに変な動きになってしまいました(実行時には使うはずのない ts-node がないとか怒られた。それも実行時ではなく、setup 時に)。 悩んだ挙句、上記ページの続きに書いてある以下の記述 node_modules ディレクトリをチェックインすると、問題が発生する可能性があります。 別の方法として、@vercel/ncc というツールを使用して、コードとモジュールを配布に使用する 1 つのファイルにコンパイルできます。 を見つけて、github から node_modules を削除して、@vercel/ncc で関連ライブラリも含めて index.js にして実行するようにしました。 その他 今回のアクションで作成できる SVG は前述の通りアニメーションするのですが、アニメーションしない方が良いという人もいるかなと思い、アニメーションあり版となし版を生成するようにしています。 また、芝生の色を季節ごとに変えたらきれいかとおもって試しに実装しています。 ちなみに、例えば春に見たときと、夏に見た時で色が違うという意味ではありません。 春頃のコントリビュートは桜色(梅・桃・桜をイメージ)で、夏頃のコントリビュートは緑色(いつもな感じ)で、秋頃のコントリビュートは黄色や紅色(紅葉をイメージ)みたいな感じを考えていました(冬は積雪をイメージ)。 ですが、現状の実装だと季節の切れ目で、スパっと色が変わるのでちょっと今一つでした。 季節の変わり目はグラデーションのように色を変えた方がよさそうですね。今後の課題とします。 もし気になる人がいたら試してみてください。以下のリポジトリ、あるいは前述したマーケットプレイスのページに手順が書いてあります。 まだ、異例なケースでの不良があるかもしれないで、うまく動かなかったら教えてください。よろしくお願いします。 使い方 一応、こちらにも使い方を簡単に記載しておきます。 手順 1. スペシャルなリポジトリを作る ユーザー名と同じ名前で GitHub にリポジトリを作成してください。 (既に作成済みなら、そこを使ってください) 例:ユーザー名が octocatの場合は、octocat/octocat という名前のリポジトリを作成します。 私の場合は yoshi389111 なので、new repository を選んで yoshi38911 という名前でリポジトリを作れば yoshi389111/yoshi389111 になります。 新しく作成したリポジトリで、以降の手順を実行します。 手順 2. ワークフローファイルを作る 以下のようなワークフローファイルを作成します。 .github/workflows/profile-3d.yml スケジュールは1日1回開始するように設定されています。 起動時間を都合の良い時間に修正してください。 cron の指定は UTC なので、そこだけ注意してください。 .github/workflows/profile-3d.yml name: GitHub-Profile-3D-Contrib on: schedule: # 03:00 JST == 18:00 UTC - cron: "0 18 * * *" workflow_dispatch: jobs: build: runs-on: ubuntu-latest name: generate-github-profile-3d-contrib steps: - uses: actions/checkout@v2 - uses: yoshi389111/github-profile-3d-contrib@0.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} USERNAME: ${{ github.repository_owner }} - name: Commit & Push run: | git config user.name github-actions git config user.email github-actions@github.com git add -A . git commit -m "generated" git push 注:git config には GitHubActions のサンプルに書いてあったメールアドレスと名前を書いています。わかると思いますが、この状態では自動コミットで芝は生えません。ズルしたくない、という人は上記のままで問題ないと思います(どうしても他人のメールアドレスでは嫌だという人は登録されていない gmail のエイリアスなどを登録してください) 逆に自動で芝生を生やしたいという人は、GitHub に登録済のメールアドレスにすると自動で1日1回のコミットがコントリビュートにカウントされると思います。 これにより、アクションがリポジトリに追加されます。 注:プライベートリポジトリも集計対象としたい場合 上記の secrets.GITHUB_TOKEN を使う場合、プライベートリポジトリの情報は権限的に取得できないため、集計対象はパブリックリポジトリだけになります。 プライベートリポジトリも集計対象とする場合は、「personal access token」を新しく作って(あるいは既存の物を)リポジトリに登録し、ワークフローファイルで指定されている GITHUB_TOKEN に設定します。 多分、以下のような手順になると思います。 ユーザーの settings -> Developper settings -> Personal access tokens でトークン作成(repo/userのread権限) 自分の名前のリポジトリの settings -> Secrets に PERSONAL_ACCESS_TOKEN を登録 secrets.PERSONAL_ACCESS_TOKEN を環境変数に登録する env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} USERNAME: ${{ github.repository_owner }} ちなみに「ユーザーの settings」と「リポジトリの settings」は別物なのでご注意ください。 手順 3. アクションを手動起動する 追加したアクションを起動してください。 Actions -> GitHub-Profile-3D-Contrib -> Run workflow プロフィール画像は以下のパスで生成されます。 profile-3d-contrib/profile-green-animate.svg profile-3d-contrib/profile-green.svg profile-3d-contrib/profile-season-animate.svg profile-3d-contrib/profile-season.svg 手順 4. README.md を追加 生成された画像のパスを readme ファイルに追加します。 例: ![](./profile-3d-contrib/profile-green-animate.svg)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud Foundryのデプロイで急にNode.jsが動かなくなった件

いつも通りにCI経由でデプロイしてたらデプロイが失敗してました。 .nvmrcにバージョンを記載するやり方でバージョン指定をしていましたが、突如指定しているバージョンが利用できなくなってフェイルするという事案が発生しました。 参考: Cloud Foundry(IBM Cloud)でNode.jsのバージョン指定【.nvmrc】 no match found for 15.12.0 以下が急に発生したデプロイエラーです。 .nvmcrには確かにv15.12.0で記載してました。 API がファイルの処理を完了するのを待機しています... アプリをステージングし、ログをトレースしています... Downloading nodejs_buildpack... Downloaded nodejs_buildpack Cell f4f7f92d-7651-4673-be44-27fba5eccf8c creating container for instance 92dbe423-2c84-410c-9721-b2ebe13f7c1f Cell f4f7f92d-7651-4673-be44-27fba5eccf8c successfully created container for instance 92dbe423-2c84-410c-9721-b2ebe13f7c1f Downloading app package... Downloading build artifacts cache... Downloaded build artifacts cache (43.6M) Downloaded app package (13.4M) -----> Nodejs Buildpack version 1.7.48 **WARNING** buildpack version changed from 1.7.46 to 1.7.48 -----> Installing binaries engines.node (package.json): unspecified engines.npm (package.json): unspecified (use default) **WARNING** Using the node version specified in your .nvmrc See: http://docs.cloudfoundry.org/buildpacks/node/node-tips.html **ERROR** Unable to install node: no match found for 15.12.0 in [10.24.0 10.24.1 12.22.0 12.22.1 14.16.0 14.16.1 15.13.0 15.14.0] Failed to compile droplet: Failed to run all supply scripts: exit status 14 Exit status 223 Cell f4f7f92d-7651-4673-be44-27fba5eccf8c stopping instance 92dbe423-2c84-410c-9721-b2ebe13f7c1f Cell f4f7f92d-7651-4673-be44-27fba5eccf8c destroying container for instance 92dbe423-2c84-410c-9721-b2ebe13f7c1f アプリケーションのステージング時にエラーが発生しました: App staging failed in the buildpack compile phase 失敗 特に何も記載の変更はしてなく、 急に15.12.0が使えなくなるという。。 こんなことあるんですね。 buildpacksの指定 結局前にやったBuildpacksの指定をするのとpackage.jsonに指定をすることで回避ができました。 参考: Cloud Foundry(IBM Cloud)でNode.jsの最新バージョンを利用する manifest.yml applications: - name: My-APP random-route: true memory: 128M command: npm start buildpacks: - https://github.com/cloudfoundry/nodejs-buildpack package.jsonでバージョン固定にすると今回みたいな事象が起こるかも知れないなと思い、最新の一つ前くらいを指定して、それ以上という指定の仕方にするようにしました。 package.json "engines": { "node": ">=15.0.0" }, nodejs-buildpackの現状の最新を見ると16.1.0まで対応してるっぽいので最新版を使えそうです。 //省略 -----> Installing node 16.1.0 Download [https://buildpacks.cloudfoundry.org/dependencies/node/node_16.1.0_linux_x64_cflinuxfs3_aebbbe59.tgz] Using default npm version: 7.11.2 デプロイすると16.1.0がインストールされてました。 まとめ 特定のNode.jsのバージョン固定で指定しているかつBuildpacksの指定をしていない状態で発生する問題なのかもしれません。 Cloud Foundry側で自動的に読み込むbuildpacksのバージョンに対応しているNode.jsのバージョンと、手元で指定したNode.jsのバージョンの食い違いが発生するといった感じでしょうか。 他のPaaSなどを使ってても出会ったことがない初めてのケースでビビりました。 15.0.0などの固定ではなく、>=15.0.0などの指定にした方が無難ですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【覚書】Gist を簡易的な DB として使う

個人的な利用の範囲で、めちゃくちゃ楽な方法でクラウドにデータを保管したいと思ったので、Gist を使ってみた。 GitHub 側の設定 https://github.com/settings/tokens でアクセストークンを取得して控えておく。 https://gist.github.com/ で db.json を作成して URL を控えておく。 JS 側の実装 /** * Gist の db.json の内容を取得 * @param {string} id Gist ID /Example: https://gist.github.com/username/{HERE} * @returns {Promise<object>} 内容 */ const getGist = async (id) => (await (await fetch("https://api.github.com/gists/" + id)).json()).files[ "db.json" ].content; /** * Gist の db.json を書き換え * @param {string} id Gist ID /Example: https://gist.github.com/username/{HERE} * @param {string} token アクセストークン * @param {object} body 書き換える内容 * @returns {Promise<object>} レスポンス */ const setGist = async (id, token, body) => await ( await fetch("https://api.github.com/gists/" + id, { method: "PATCH", headers: { Accept: "application/vnd.github.v3+json", Authorization: "token " + token, }, body: JSON.stringify({ description: "Updated at " + new Date().toLocaleString(), files: { "db.json": { content: JSON.stringify(body), }, }, }), }) ).json(); これで OK 。 注意 アクセストークンが漏洩すると色々とヤバいので、見えるところに置くのはガチでやめたほうがいい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】配列の差分を取得する

はじめに 2つの配列から差分を取得したいと思い、調べたので実装方法をメモしておきます。 「2つの配列の差分を取得する」というのは例えば以下のような例を想定しています。 例 配列1 [1, 2, 3, 4, 5] 配列2 [1, 2, 3] この2つの配列の差分[4, 5]を取得したい、ということです。 実装方法 今回の実装方法では filter 関数を使います。(もっと楽な方法があればぜひ教えてください) const array1 = [1, 2, 3, 4, 5] const array2 = [1, 2, 3] const array3 = array1.filter(i => array2.indexOf(i) == -1) console.log(array3) //[4, 5] indexOfは値が見つからない場合に-1を返します。 filterとindexOfを利用することで、配列の差分を取得できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeafletでGPSログ(GPX)地図:クライアントローカルGPXファイルを指定可能に。

地図データを扱うJavaScriptライブラリ「Leaflet」を使って、GPSログ(GPXファイル)の地図表示を試している。 今回、パソコン等のクライアントローカルにある任意のGPXファイルを指定して地図表示ができるようにした。GPXファイルはサーバーに送られるわけではない。すべてJavaScriptでクライアントサイドで処理を行っている。 詳細は以下。 「LeafletでGPSログ(GPX)地図:クライアントローカルGPXファイルを指定可能に。」 https://2ndart.hatenablog.com/entry/2021/05/09/165116
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jsonからグラフを自動描画

Over View demo Github jsonでchart.jsをいくつも自動で描画しちゃいます 毎週のレポートみたいに”目標値”と”実績値”で達成度を計測してグラフ化するあれを児童にしたいのです スクショなんかの画像だと編集の時大変だしデータの使い回しができない htmlに描画するって考えたらエクセルよりもjavascriptの方に軍配が上がる というわけで、javascriptとhtmlだけで汎用性の高い毎週のレポートをめっちゃ楽に作ることができます。 ちなみに、なるべく1つのファイルで完結させたい!!ってことで今回はcssやjsなどはindex.htmlに書き込んでいます。 Code index.html <!doctype html> <html lang="ja"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <!-- google font --> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Julius+Sans+One&display=swap" rel="stylesheet"> <script type="text/javascript" src="data.json"></script> <style type="text/css"> body { font-size: 0.9rem; line-height: 2.0; font-family: 'Noto Sans JP', sans-serif; } .font-accent { font-family: 'Julius Sans One', sans-serif; } .sticky-top { position: sticky; top: 0; left: 0; } .p-line { white-space: pre-line; } p { margin: 0; } h1,h2,h3,h4,h5,h6 { margin: 0; padding: 0; } .line-through { text-decoration: line-through; } img { max-width: 100%; } .pointer:hover { cursor: pointer; } ul,li { margin: 0; } .mouseover-scroll:hover { overflow-y: auto; } .fs-105-hover:hover { font-size: 105%; } .non-list { list-style: none; } .non-list,.non-list li { list-style: none; margin: 0; padding: 0; } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script type="text/javascript"> // create random strings function randomStrings() { var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); var integers = '0123456789'.split(''); var RandomString = ''; for (var i = 0; i < 5; i++) { RandomString += alphabet[Math.floor(Math.random() * alphabet.length)] + integers[Math.floor(Math.random() * integers.length)]; } return RandomString; } // creating the chart from 4 valiables function createChart(type, label, expected, actual) { RandomString = randomStrings(); document.write(`<canvas id="${RandomString}"></canvas>`); if(type == 0) { color = "#36a2eb"; } else { color = "#EB9B9B"; } var data = { labels: label, datasets: [{ label: 'Expected', data: expected, borderColor: '#d9d9d9', pointBackgroundColor: '#d9d9d9', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: '#d9d9d9' }, { label: 'Actual', data: actual, borderColor: color, pointBackgroundColor: color, pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: color, }] }; var config = { type: 'line', data: data, options: { responsive: true, plugins: { legend: { position: 'top', }, } }, }; new Chart( document.getElementById(RandomString) ,config ); } function toggleNext(item) { target = item.nextElementSibling; target.classList.toggle('d-none'); } function createWigContent() { var randomSt = randomStrings(); var title = data.wig; for(i=0 ; i<data.index.length ; i++) { for(i=0 ; i<data.index.length ; i++) { document.write(`<div class="row mb-5">`); var type = data.index[i].type; var title = data.index[i].title; var arrayLabel = []; var arrayExpected = []; var arrayActual = []; var degreeExpected = 0; var degreeActual = 0; document.write(`<div class="col-12 col-md-4">`); document.write(`<p class="mb-2"><i class="fas fa-dice-d6 mr-1"></i>${title}</p>`); document.write(`<p onclick="toggleNext(this)" class="mb-2 pointer text-muted"><i class="fas fa-ellipsis-v mr-1"></i>data</p>`); document.write(`<div class="d-none pl-3">`); for(ii=0 ; ii<data.index[i].score.length ; ii++) { arrayLabel.push(`${data.index[i].score[ii].date}`); arrayExpected.push(`${data.index[i].score[ii].expected}`); arrayActual.push(`${data.index[i].score[ii].actual}`); percentage = (data.index[i].score[ii].actual/data.index[i].score[ii].expected)*100; if(percentage >= 100) { var classX = "text-success"; } else { var classX = "text-danger"; } text = `${data.index[i].score[ii].date} => Expected:${data.index[i].score[ii].expected}, Actual: ${data.index[i].score[ii].actual}(${Math.round(data.index[i].score[ii].actual/data.index[i].score[ii].expected*100)}%)` document.write(`<p class="${classX}">${text}</p>`); degreeActual += data.index[i].score[ii].actual; degreeExpected += data.index[i].score[ii].expected; } document.write(`</div>`); document.write(`<p>達成率: ${Math.round(degreeActual/degreeExpected*100)}%</p>`); document.write(`</div>`); document.write(`<div class="col-12 col-md-8">`); createChart(type, arrayLabel, arrayExpected, arrayActual); document.write(`</div>`); document.write(`</div>`); } } } </script> </head> <body> <div class="p-3 p-lg-5 container-fluid py-5"> <h2 class="mb-5">2020年の目標!</h2> <script type="text/javascript">createWigContent();</script> </div> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script> <!-- fontawesome --> <script src="https://kit.fontawesome.com/cb06e1242b.js" crossorigin="anonymous"></script> </body> </html> data.json data = { index: [ { type: 0, title: "毎月100時間本を読む", score: [ {date: "Jan", expected: 100, actual: 187}, {date: "Feb", expected: 100, actual: 288}, {date: "Mar", expected: 100, actual: 89}, {date: "Apr", expected: 100, actual: 40}, {date: "Mar", expected: 100, actual: 91}, {date: "Jun", expected: 100, actual: 52}, {date: "Jul", expected: 100, actual: 93}, {date: "Aug", expected: 100, actual: 104}, {date: "Sep", expected: 100, actual: 95}, {date: "Oct", expected: 100, actual: 26}, {date: "Nov", expected: 100, actual: 87}, {date: "Dec", expected: 100, actual: 98}, ], }, // end index items { type: 1, title: "毎月90時間のランニング", score: [ {date: "Jan", expected: 900, actual: 98}, {date: "Feb", expected: 900, actual: 99}, {date: "Mar", expected: 900, actual: 1000}, {date: "Apr", expected: 900, actual: 1301}, {date: "Mar", expected: 900, actual: 102}, {date: "Jun", expected: 900, actual: 23}, {date: "Jul", expected: 900, actual: 14}, {date: "Aug", expected: 900, actual: 10}, {date: "Sep", expected: 900, actual: 1206}, {date: "Oct", expected: 900, actual: 307}, {date: "Nov", expected: 900, actual: 508}, {date: "Dec", expected: 900, actual: 609}, ], }, // end index items ] }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<button>でhref=が使えない!??【解決】

はじめに こんにちは、超初学者のなかむーです。 スクールで学んだ知識や経験などを記事を通してアウトプットしています。 今回は「buttonタグにリンク移動できるのか?」というタイトルですが、 結論から言うとYesです! < a > と href="#" a タグでは以下のようにすることで遷移できる。 <a href="signup">新規登録</a> "/signup"でも遷移できます。 < button > と href="#" buttonタグでは以下のようにすることで遷移できる。 <button onclick=location.href="signup">新規登録</button> onclick=location.を後ろに合体させるだけですね! そして、これはJavaScriptで使われているものらしいですね。 aタグでは href属性(HTML) buttonタグでは onclick属性(JavaScript) フォームの中でリンク移動が可能になるので、便利だなぁと感じました。? 引用 : HTMLでbuttonタグを使ってリンクを貼る方法を現役エンジニアが解説【初心者向け】 ちなみに href は「エイチレフ」と読むみたいです。 恥ずかしながら、この課題に出会うまで知りませんでした...笑 至って簡単で単純ですぐ終わってしまいますが、実際に使用したところです!何かの参考になれば幸いです。?‍♂️ <div class="row justify-content-center mt-5"> <div class="col-4 text-center"> <h3>まだアカウントを<br>お持ちでない方はこちら</h3> <button type="button" class="btn btn-primary mt-5" onclick=location.href="signup" style="width:120px;height:50px">新規登録</button> </div> <div class="col-4 text-center"> <h3>すでにアカウントを<br>お持ちの方はこちら</h3> <button type="button" class="btn btn-primary mt-5" onclick=location.href="login" style="width:120px;height:50px">ログイン</button> </div> </div> 参考文献 •  HTMLでbuttonタグを使ってリンクを貼る方法を現役エンジニアが解説【初心者向け】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SPA】Nuxt.js,TypeScript,AWS,graphQLで作るインスタクローンアプリ

目次 作品リンク 作成目的 使用技術 アプリの使い方 1.作品リンク amplifyにてホスティングしました。 https://master.d2qrnepigha0wa.amplifyapp.com/ 2.作成目的 学習のアウトプット ポートフォリオとしての使用 3.使用技術 フロントエンド HTML/CSS TypeScript Nuxt.js(SPAモード) Vuetify(UIフレームワーク) ESLint/Prettier(コード解析ツール) バックエンド・インフラ Amplify(インフラ構築) GraphQL(API呼び出し) AppSync(API管理) DynamoDB(データベース) S3(画像ストレージ) Cognito(ユーザー認証) GitHub(バージョン管理) インフラ構成図 ER図 4.アプリの使い方 機能名 説明 ユーザー機能 新規登録・メールアドレスによる確認・登録内容変更・アイコン登録・ログイン・ログアウト・ゲストユーザー 投稿機能 投稿、編集、削除、画像複数枚登録 コメント機能 投稿に対してコメント投稿 いいね機能 投稿をいいねできる ①ユーザー機能 登録・編集 ユーザー名・メールアドレス・パスワードでアカウントを作成します。 同時にアイコンの登録も可能です。 パスワードとメールアドレスはcognitoのユーザープールにて管理され、データベースに保存されません。 登録の際に入力いただいたメールアドレスに、確認コードを送信し、 確認が完了すると機能をお使いいただけます。 (ここで確認しなかった場合、ログインの際に確認機能を挟むようになっています。) 会員登録 ログイン ゲストユーザーでのログインも用意しました。 ゲストユーザー ログイン画面・アカウントの新規登録画面に、ゲストログインのボタンを配置し、気楽に使用できるようにしました。 ②.投稿機能 投稿の作成、削除は即時に反映されます。 画像は最低1枚は登録必須になります。 登録枚数の制限はありません。 ③.いいね機能 投稿に対し、いいねできます。自分がいいねしている場合は、いいねを外すことももちろん可能です。 ④.コメント機能 投稿詳細ダイアログから、投稿に対してコメントできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「ひらがな化API」を使って、給食メニュー変換Botを作ったら、わが子に大好評だった話

今回の目的 ~大好きなわが子が喜ぶBotを作りたい!~ ひらがなに興味を持ち、読むのが楽しいわが家の息子。 先日LINEBotを学習したので、子どもが楽しめるツールを作れないか?と思い立ちました。 "ひらがな化API"という面白そうなものを見つけ、LINEBotを作ったので、ご紹介します。 Javascript初心者(学び始めて数週間。コードに大苦戦中)ですが、同じような初心者の方にも参考になれば幸いです。 完成品(給食メニュー変換Bot) まずは、完成品のキャプチャーです。 わが家の息子は、慎重派タイプで、保育園の給食メニューがとても気になる様子。毎日のように、「明日の給食メニューは何?」と聞かれるのが日課なので、何かできないか?と考えました。 (メニューは保育園から電子提供されますが、漢字が分からない息子には読めないんです・・・) そこで! メニューをテキストに貼り付けて送信するだけで、ひらがな変換してくれるBotを作ったら、子どもに大好評でした。 勝手ながら、「給食メニュー変換Bot」(別名:ピカチュウBot)と名付けます! 事前に楽しくメニューを確認できるので、安心して登園してくれそうです。 "息子に楽しんでもらうこと"が目的なので、ひと工夫として、「リッチメニュー」を使って息子が大好きなピカチュウに話しかける設定にしました。(ピカチュウの画像は、pokeAPIから取得。) リッチメニューの作成は、こちらの記事が分かりやすかったので参考にしました。 LINEbotが完成!ひらがなが読めるようになった息子のために、変換botを作りました?大好きなピカチュウに会える仕掛けつき。喜んでくれるかなぁ? #子ども #LINEBot #ポケモン #プログラミング初心者 pic.twitter.com/ajVBVaJ4mp— くるみ (@Kokano23) May 9, 2021 作り方 以下の手順で準備をしていきます。 ①Botアカウント等を作成します 以下の記事を参考にしました。 分かりやすくまとめられているので、初心者にも大変参考になりました。 1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest ②アプリケーションID(gooラボAPI)を取得します 今回は、gooラボによる「ひらがな化API」を利用しました。 利用にあたっては、アプリケーションIDの取得が必要となるため、サイトに従って事前に設定しました。 ③APIを取得します こちら、コードに不慣れな初心者にとっては大変苦戦した部分です。 前回の記事では"get"を使ってAPIを取得しており、今回も同様にトライするもエラー発生。 なかなか原因に気づけずにいましたが、API仕様をよく読むと、"post"にて受け付けると記載がありました。(仕様確認は基本ですね・・・) プログラミング用語(get/post)については、改めて以下の記事で勉強しました。 プログラミングでよく使う英単語のまとめ【随時更新】 Javascript // axiosでひらがな化APIを叩きます(少し時間がかかる・ブロッキングする) const res = await axios.post('https://labs.goo.ne.jp/api/hiragana',{app_id:'[自身のアプリケーションID]',sentence:event.message.text,output_type:'hiragana'}); //変換後の文字列を格納 pushText = res.data.converted; ソースコード 全体のソースコードは、以下のとおりです。 Javascript 'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', //作成したBOTのチャンネルシークレット channelAccessToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' //作成したBOTのチャンネルアクセストークン }; // ########## ▼▼▼ メイン関数 ▼▼▼ ########## const sampleFunction = async (event) => { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: 'ちょっとまってね' }); let pushText = ''; try { // axiosでひらがな化APIを叩きます(少し時間がかかる・ブロッキングする) const res = await axios.post('https://labs.goo.ne.jp/api/hiragana',{app_id:'[自身のアプリケーションID]',sentence:event.message.text,output_type:'hiragana'}); //変換後の文字列を格納 pushText = res.data.converted; } catch (error) { pushText = 'エラーです。ごめんね'; // APIからエラーが返ってきたらターミナルに表示する console.error(error); } // 「プッシュ」で後からユーザーに通知します return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); }; // ########## ▲▲▲ メイン関数 ▲▲▲ ########## // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); まとめ 「ひらがな化API」は、リクエストされた文字列を"カタカナ変換"することも可能です。読みやすいように自動でスペースも入れてくれるので、大変便利。 今回は息子のために"給食メニュー変換"をテーマにしましたが、API自体はどんな文字列でもひらがな変換可能です。 Botを動かすところも息子に見せたところ、「へ~、ママどうやって作ったの?すごいね」と褒められ、テンションが上がりました!(まだまだ未熟ですが、褒められると頑張っちゃうタイプです) 子どもに小さい頃からプログラミングに興味を持ってもらう、という副次的な効果も生まれる予感です! 今回のLINEBotを作る過程で、画像を返してくれるBotや、条件分岐で作ってみたいアイデアも出てきたので、次回取り組んでみたいと思います。 参考 今回は、以下の記事を参考にさせていただきました。 ▶1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest ▶プログラミングでよく使う英単語のまとめ【随時更新】 ▶【完成!!】LineBotのUIを整える~テレ〇限定・今日のドラマ再放送は何やるの?LineBotを作ろうと思う ▶Qiitaにtwitterを埋め込む方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Cloud Vision APIを使って画像認識してくれるLINE Botを作る

GCPに用意されているVision APIは画像を投げると各種画像解析(ラベリングや文字の読み取り、顔検出等)を行ってくれます。 今回はLINE Botと組み合わせて簡単な画像認識Botを作りました。 できたもの LINEのトークルームで画像をアップロードすると、何が写っているのかを確からしさと一緒に返してくれます。 (今回は最大3つまでの候補を返します。) ※某赤い彗星っぽい人は無視してください。 作り方 動きを見ていただいたところで作り方を解説していきます。 準備① LINE Bot LINE DevelopersでBotアカウントを作ります。 以下の記事を参考にしました。 1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest 準備② Vision API GCPでVision APIを使えるようにします。 こちらも参考記事があります。 GCPのCloud Vision APIを使ってみた Botアプリケーションの作成 今回はNode.js環境でサーバー側のBotアプリケーションを作成しました。 使用した環境は以下の通りです。 node.js v15.12.0 axios v0.21.1 Line/bot-sdk v7.3.0 express v4.17.1 完成したコードは以下の通りです。(長いのでセクションにして畳んでいます。) コード全文 'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const fs = require('fs'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: '[自分のチャネルシークレット]', channelAccessToken: '[自分のチャネルアクセストークン]' }; //Google Cloud Vision API用のキー const apiKey = "[自分のAPIキー]"; //画像保存用パス const downloadPath = './image.jpg'; //Base64エンコード関数 function toBase64(imagePath) { const imageFile = fs.readFileSync(imagePath); return Buffer.from(imageFile).toString("base64"); } // 返信用関数 const imagetovisionapi = async (event) => { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: 'すまない。少し待ってもらえるかな' }); let pushText = ''; try { // 送られてきた画像を取得 let getContent = await downloadContent(event.message.id, downloadPath); let visionApiUrl = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;//VisionAPIのURL let options = { requests: [ { image: { content: toBase64(downloadPath),//画像はBase64にエンコードする }, features: [ { type: "LABEL_DETECTION", maxResults: 3//結果を3つまで返す }, ], }, ], }; //VisionAPIを叩いて画像を解析 let result = await axios.post(visionApiUrl, options); console.log(result.data.responses[0].labelAnnotations); //返信用にテキストを成型 for(let item of result.data.responses[0].labelAnnotations) { pushText = pushText + String(item.score * 100).substring(0, 2) + "%の確率で" + item.description + "\n"; } pushText = pushText + "だな。"; } catch (error) { pushText = 'エラーが発生したようだ。'; // APIからエラーが返ってきたらターミナルに表示する console.error(error); } // 「プッシュ」で後からユーザーに通知 return client.pushMessage(event.source.userId, { type: 'text', text: pushText }); }; //ダウンロード関数 function downloadContent(messageId, downloadPath) { return client.getMessageContent(messageId) .then((stream) => new Promise((resolve, reject) => { const writable = fs.createWriteStream(downloadPath); stream.pipe(writable); stream.on('end', () => resolve(downloadPath)); stream.on('error', reject); })); } // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化 const client = new line.Client(config); // LINEサーバーからのWebhookに反応 async function handleEvent(event) { // 受信したWebhookが「イメージ以外」であれば画像を送信するように示唆 if (event.type !== 'message' || event.message.type !== 'image') { return client.pushMessage(event.source.userId, { type: 'text', text: '画像を送って欲しい' }); } // 返信用の関数を実行 return imagetovisionapi(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて処理 app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答 console.log('検証イベントを受信しました!'); // ターミナルに表示 return; // これより下は実行されない } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示 console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理し、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返す Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開 // (環境によってはローカルネットワーク内にも公開される) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 解説 ここからは、個人的に難しかった部分を解説していきます。 トークルームで送信された画像の取得 LINE Botのトークルームでユーザーが送信した画像はLINEのサーバーに保管されます。 指定されたURLにメッセージIDをパラメーターに設定してGETリクエスト投げることで画像を取得できます。 公式ドキュメント 以下の記事を参考に、取得した画像を一旦サーバーローカルに保存しています。 LINE BOTに送った画像をNode.jsで受信して保存するサンプル ソース上だと以下の関数を用意しています。 //ダウンロード関数 function downloadContent(messageId, downloadPath) { return client.getMessageContent(messageId) .then((stream) => new Promise((resolve, reject) => { const writable = fs.createWriteStream(downloadPath); stream.pipe(writable); stream.on('end', () => resolve(downloadPath)); stream.on('error', reject); })); } この関数にメッセージIDと保存先のパスを渡すと送信された画像を保存してくれます。 Vision APIの利用 Vison APIの実装には幾つかのパターンがあるのですが、今回は ・REST APIを利用 ・認証はAPIキー ・画像の渡し方はBase64にエンコードしてパラメーターに設定 でやりました。 実際のソースコードはこんな感じ let visionApiUrl = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;//VisionAPIのURL let options = { requests: [ { image: { content: toBase64(downloadPath),//画像はBase64にエンコードする }, features: [ { type: "LABEL_DETECTION", maxResults: 3//結果を3つまで返す }, ], }, ], }; //VisionAPIを叩いて画像を解析 let result = await axios.post(visionApiUrl, options); これで最大3つまで候補が返ってくるので、それを抽出して成型しています。 //返信用にテキストを成型 for(let item of result.data.responses[0].labelAnnotations) { pushText = pushText + String(item.score * 100).substring(0, 2) + "%の確率で" + item.description + "\n"; } pushText = pushText + "だな。"; 最後に 今回作ったBotですが、送る画像によって色んな答えが返ってくるのが楽しくて結構遊べてます。 (因みに、Vison APIは画像*処理で1000回/月までは無料です。) 次は返ってきたラベル候補を日本語化していきたいですねー。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第 4 章 TypeScript で型をご安全に メモ

型アノテーション(Type Annotation) TypeScript では value: Type というフォーマットで宣言時の変数 に型の注釈がつけられる アノテー ションによって静的に型付けされた情報はコンパイル時のチェックに用いられ、書かれたコード中 に型の不整合があるとコンパイルエラーになる $ ts-node >let n : number = 3; >n = 'foo'; [eval].ts:2:1 - error TS2322: Type '"foo"' is not assignable to type 'number'. > if (n) console.log('`n` is truthy'); `n` is truthy $ node > const s = '123'; >constn=456; >s*3 369 >246/s 2 >s+n '123456' $ ts-node > const s = '123'; >const n = 456; >s*3 [eval].ts:4:1 - error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. >246/s [eval].ts:4:1 - error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. >s+n '123456' JavaScript には『暗黙の型変換』という機能があって、この例のように文字列データを算術演算しようとすると、勝手にその変数の型を数値型に変換してしまう TypeScript では、数値型と互換性のない型のデータは算術演算が許されないのでコンパイルエラーになる + は加算ではなく文字列の連結として評価される 型推論(Type Inference) コンパイラがその文脈からその型を推測できる場合は、型ア ノテーションを省略しても自動的に補完して解釈してくれる JavaScript と TypeScript のちがいはほぼ型システムだけ 型の種類 プリミティブ型は JavaScript と共通した次の 7 種類 ・Boolean 型 ...... true および false の 2 つの真偽値を扱うデータ型。型名は boolean ・Number 型 ...... 数値を扱うためのデータ型。型名は number ・BigInt 型 ...... number 型では表現できない大きな数値(253 以上)を扱う型。型名は bigint ・String 型 ...... 文字列を扱うためのデータ型。型名は string ・Symbol 型 ......「シンボル値」という固有の識別子を表現する値の型。型名は symbol ・Null 型 ...... 何のデータも含まれない状態を明示的に表す値。型名は null ・Undefined 型 ......「未定義」であることを表す値。型名は undefined 配列の型 //型名の後ろに [] をつけると、その型データの配列になる const numArr: number[] = [1, 2, 3]; //配列は Array オブジェクトとして定義する別の書き方もできる //ジェネリクス(Generics) const strArr: Array<string> = ['one', 'two', 'three']; オブジェクトの型定義 const red: { rgb: string, opacity: number } = { rgb: 'ff0000', opacity: 1 }; TypeScript ではオブジェクトの型に名前をつけることができるようになってる。それが『インターフェース(Interface)』と呼ばれる interface Color { readonly rgb: string; opacity: number; name?: string; } const turquoise: Color = { rgb: '00afcc', opacity: 1 }; turquoise.name = 'Turquoise Blue'; turquoise.rgb = '03c1ff'; // error TS2540: Cannot assign to 'rgb' because it is a read-only property. readonly 修飾子を つけたプロパティは書き換え不可になる プロパティ名の末尾に ? をつけると、そのプロ パティは省略可能になる interface Status { level: number; maxHP: number; maxMP: number; [attr: string]: number; } const myStatus: Status = { level: 99, maxHP: 999, maxMP: 999, attack: 999, defense: 999, }; 4 つめが任意のキーのプロパティ値を定義してる。これは『インデック スシグネチャ(Index Signature)』というもので、これのおかげで attack や defense というプロパテ ィを追加できてる。 インデックスシグネチャのキーに使える型は、文字列と数値の 2 種類のみ enum 型 TypeScript の enum はデフォルトでは数値で、かつ型安全が保証されない バージ ョン 2.4.0 から導入された文字列 enum を使えばその問題は解決する >enumPet{ > Cat='Cat', > Dog='Dog', > Rabbit='Rabbit', >} > let Tom: Pet = Pet.Cat; > Tom = 'Hamster'; [eval].ts:7:1 - error TS2322: Type '"Hamster"' is not assignable to type 'Pet'. > Tom = 'Dog'; [eval].ts:8:1 - error TS2322: Type '"Dog"' is not assignable to type 'Pet'. リテラル型 リテラル型は単独ではあまり使いみちがないんだけど、演算子 | で並べることによってあ たかも列挙型のように扱える これは『共用体型』というものの合わせ技 > let Mary: 'Cat' | 'Dog' | 'Rabbit' = 'Cat'; > Mary = 'Rabbit'; > Mary = 'Parrot'; [eval].ts:5:1 - error TS2322: Type '"Parrot"' is not assignable to type '"Cat" | "Dog" | "Rabbit"'. 文字列リテラル型は文字列 enum と比べてもシンプルに記述できて扱いやすく、JavaScript へのコ ンパイル後のコードもより短くなるというメリットがある リテラル型には文字列だけじゃなく、値が数値に限定された『数値リテラル型』もある。 タプル型 TypeScript には個々の要素の型と、その順番や要素数に制約を設けられる特殊な配列 const charAttrs: [number, string, boolean] = [1, 'patty', true]; タプルの定義にもレストパラメータが使える const spells: [number, ...string[]] = [7, 'heal', 'sizz', 'snooz']; 2021 年 2 月リリースの TypeScript 4.2 で導入されたばかりのものなので、使うとき はバージョンに気をつけて タプルの使い方 > const userAttrs: [number, string, boolean] = [1, 'patty', true]; > const [id, username, isAdmin] = userAttrs; >console.log(id,username,isAdmin); //1 patty true any 型 any で定義された変数は、その名のとおりいかなる型の値でも受けつけるようになる > let val: any = 100; 100 > val = 'buz'; > val = null; any で定義するとそこだけ JavaScript に差し戻すようなものといえる unknown型 any の型 安全版で、任意の型の値を代入できる点は同じ、それ自体は何のプロパティもプロトタイプメソッドも持たない型 never型 何者も代入できない型 関数の型定義 TypeScriptではコンパイラオプションに noImplicitAnyが指定されてないと、引数の型定義がなくても暗黙の内にany型があてがわれてコンパイルが通ってしまうので、まずは設定ファイルの tsconfig.json でそのオプションを有効に TypeScript では戻り値の型は型推論が有効な場合には省略もできるけど、引数の型は必ず指定する必要がある 何も返さない関数の戻り値型は void になる // function declaration statement { function add(n: number, m: number): number { return n + m; } console.log(add(2,4)); //6 } // function keyword expression { const add = function(n: number, m: number): number { return n + m; }; console.log(add(5,7)); //12 } // arrow function expression { const add = (n: number, m: number): number => n + m; const hello = (): void => { console.log('Hello!'); }; console.log(add(8,1)); //9 hello(); // Hello! } 引数 と戻り値をまとめて定義する方法もある。関数を『呼び出し可能オブジェクト(Callable Object)』 として定義するもの // callable object type { interface NumOp { (n: number, m: number): number; } const add: NumOp = function (n, m) { return n + m; }; const subtract: NumOp = (n, m) => n - m; console.log(add(1, 2)); // 3 console.log(subtract(7,2)); //5 } // in-line { const add: (n: number, m: number) => number = function (n, m) { return n + m; }; const subtract: (n: number, m: number) => number = (n, m) => n - m; console.log(add(3, 7)); // 10 console.log(subtract(10, 8)); // 2 } 前者がインターフェースとして呼び出し可能オブジェクトを定義し、それを関数式に適用したも の。後者はそれをアロー型アノテーションによってインラインで行ってる 関数の型宣言にジェネリクスを用いる記法 > const toArray = <T>(arg1: T, arg2: T): T[] => [arg1, arg2]; > toArray(8, 3); [8,3] > toArray('foo', 'bar'); [ 'foo', 'bar' ] > toArray(5, 'bar'); [eval].ts:4:12 - error TS2345: Argument of type '"bar"' is not assignable to parameter of type 'number'. 型引数(Type Parameter) 数に渡す引数と同じで、任意の型を <> によって引数と して渡すことで、その関数の引数や戻り値の型に適用できるようになる 最初の例では T は 型推論によって number、次の例では string になってる 最後のは引 数 arg1 と arg2 の型が任意のものに統一されてないのでエラー データの型に束縛されないよう型を抽象化してコードの再利用性を向上させつつ、 静的型付け言語の持つ型安全性を維持するプログラミング手法を『ジェネリックプログラミング (Generic Programming)』と呼ぶ。 そして型引数を用いて表現するデータ構造のことを『ジェネリクス(Generics)』っていう TypeScript でも可変長引数はちゃんと型安全に扱える > const toArrayVariably = <T>(...args: T[]): T[] => [...args]; > toArrayVariably(1, 2, 3, 4, 5); [1,2,3,4,5] > toArrayVariably(6, '7', 8); [eval].ts:3:20 - error TS2345: Argument of type '"7"' is not assignable to parameter of type 'number'. TypeScript でのクラスの扱い TypeScript のクラスと JavaScript のク ラスは、一見似ているようで異なる部分がそこそこある。 class Rectangle { readonly name = 'rectangle'; sideA: number; sideB: number; constructor(sideA: number, sideB: number) { this.sideA = sideA; this.sideB = sideB; } getArea = (): number => this.sideA * this.sideB; } TypeScript ではメンバー変数は、クラスの最初で その宣言をしておく必要がある 『プロパティ初期化子(Property Initializer)』という機能 コンストラクタに引 数がないクラスでは、インスタンスの初期化をこれだけで済ませてコンストラクタを省略できる 宣言時に readonly 修飾子を付けることで、そのメンバー変数を変更不可にもできる 『アクセス修飾子(Acdess Modifier)』 宣言時につけることでそのメンバーのアクセシビリティをコントロールできる アクセス修飾子のバリエーションは次のとおり ・public ...... 自クラス、子クラス、インスタンスすべてからアクセス可能。デフォルトではすべ てのメンバーがこの public になる ・protected ...... 自クラスおよび子クラスからアクセス可能。インスタンスからはアクセス不可 ・private...... 自クラスからのみアクセス可能。子クラスおよびインスタンスからはアクセス不可 TypeScript でのクラスの扱い 今日のオブジェクト指向プログラミングでは、『継承よりも合成Compostion over Inheritance)』のスタイルが優勢になってる class Square extends Rectangle { readonly name = 'square'; side: number; constructor(side: number) { super(side, side); } } 前者の継承で書いてるほう Square は暗黙の内に不必要な公開メンバー変数 sideA と sideB まで 継承してしまっていて、それがのちのちバグを生む芽になりかねない。また getArea() メソッドが 完全に共有されているため、親クラスの実装を不用意に変更できず、継続的なコード改善の障害に なる。つまり保守性が悪い class Square { readonly name = 'square'; side: number; constructor(side: number) { this.side = side; } getArea = (): number => new Rectangle(this.side, this.side).getArea(); } 後者の合成の例では、Rectangle クラスを独立したただの部品として扱ってる ただその API としての入力と出力の仕様を知って さえいればいい。そして依存がないゆえに Rectangle クラスの内部の変更に Square クラスが影響さ れることはない。個々のモジュールの独立性が高く、より保守性にすぐれたコード React でもコンポーネントをクラスで作成するときは、継承を避けるよう公式ドキュメントに書 かれてる クラスの 2 つの顔 クラスの型を抽象化して定義する方法が、TypeScript には 2 つある。 abstract 修飾子 を用いて抽象クラスを定義するもの 継承そのものがダメというより、抽象クラスはその定義に実装を含むことができてしまうからだ よ。避けるべきは実装を伴った継承で、実装を伴わずに型だけを適用したいの。そこで 2 つめの選択肢、インターフェースを使う interface Shape { readonly name: string; getArea: () => number; } interface Quadrangle { sideA: number; sideB?: number; sideC?: number; sideD?: number; } class Rectangle implements Shape, Quadrangle { readonly name = 'rectangle'; sideA: number; sideB: number; constructor(sideA: number, sideB: number) { this.sideA = sideA; this.sideB = sideB; } getArea = (): number => this.sideA * this.sideB; } JavaScript では関数は第一級オブジェクトでプロパティの値にできるから、インターフェー スでもそのまま関数の型を定義すれば、それがそのメンバーメソッドの型になる Quadrangle インターフェースの getArea の定義にアロー構文を使ったけど、getArea(): number という書き方もできる そして実はこの 2 つの定義のやり方には微妙に差分がある アロー構文だと『オーバーロード(Overload)』ができない class Point { x: number = 0; y: number = 0; } const pointA = new Point(); const pointB: Point = { x: 2, y: 4 }; interface Point3d extends Point { z: number = 0; } const pointC: Point3d = { x: 5, y: 5, z: 10 }; インターフェースはクラスみたいに extendsで拡張できる TypeScript でクラスを定義すると、実際には 2 つの宣言が同時に実行されてる ひとつ はそのクラスインスタンスのインターフェース型宣言。もうひとつはコンストラクタ関数 の宣言 クラスは TypeScript にとって、型でもあり関数でもあるという二重の存在 型エイリアス VS インターフェース インターフェースはオブジェクトとクラスの型が定義できる TypeScript にはもうひとつ、任意の型に別名を与えて再利用できる型エイリアス(Type Alias)というものがある type Unit = 'USD' | 'EUR' | 'JPY' | 'GBP'; type TCurrency = { unit: Unit; amount: number; }; interface ICurrency { unit: Unit; amount: number; } const priceA: TCurrency = { unit: 'JPY', amount: 1000 }; const priceB: ICurrency = { unit: 'USD', amount: 10 }; 新しい型を作成しているのではなく、無名の文字列リテラル型 にそれを参照するための別名 Unit を与えてる コンパイラはそれを区別してる TCurrency のほうは構造まで表示されてますけど、ICurrency は名前だけしか表示されませんね。 このちがいって? これは型そのものに名前がついてるかどうかによるちがい インターフェース文は型の宣言 なので、その型には本来の名前が与えられる。いっぽう型エイリアスの構文はすでに無名で作られてしまった型に参照のための別名を与えているものなので、その型には本来の名前がないまま セミコロンの有無も同様で、関数宣言とインタ ーフェース構文はブロック {} で終わる文なのでセミコロンが不要。関数式と型エイリアスの構文 は最終的に代入文になるのでセミコロンが必要になる インターフェースは、よくいえば拡張に対してオープンな性質がある interface User { name: string; } interface User { age: number; } interface User { species: 'rabbit' | 'bear' | 'fox' | 'dog'; } const rolley: User = { name: 'Rolley Cocker', age: 8, species: 'dog', }; 同じプロパティ値を別の型として上書き定義はできないし、他のプロパティはそのままなので再 宣言ではない あくまで新しいプロパティの型定義が追加されていくだけ 普通に React でアプリケーショ ンを開発するのなら一貫して型エイリアスだけを使っていればいいと思う。そのほうが interface と type の構文が混在することもなくシンプルに書ける。 共用体型と交差型 TypeScript では既存の型を組み合わせて、より複雑な型を表現できる そのひとつ、まずは共用体型(Union Types、交差型) $ ts-node > let id: number | string = 208239; >id 208239 > id = 'a6ba7fb9-8435-4226-804e-387f3d2e53a7'; >id' a6ba7fb9-8435-4226-804e-387f3d2e53a7' 演算子 | で型を並べることで、それらの内のいずれかの型が適用される複合的な型になる オブジェクト型も共用体型に適用できるんだけど、これも基本は文字列リテラル型と同じで、 単にその並べられたオブジェクトの型のいずれかが適用される typeA={ foo: number; bar?: string; }; type B = { foo: string }; type C = { bar: string }; type D = { baz: boolean }; typeAorB=A|B; //{foo:number|string;bar?:string} typeAorC=A|C; //{foo:number;bar?:string}or{bar:string} typeAorD=A|D; //{foo:number;bar?:string}or{baz:boolean} 交差型(Intersection Types、インターセクション型) 演算子 & で並べていく 共用体型が『A または B』と適用範囲を増やしていくのに対して、 交差型は『A かつ B』と複数の型をひとつに結合させるもの 用途としては、もっぱらオブ ジェクト型の合成に使われる type A = { foo: number }; type B = { bar: string }; typeC={ foo?: number; baz: boolean; }; typeAnB=A&B; //{foo:number,bar:string} typeAnC=A&C; //{foo:number,baz:boolean} typeCnAorB=C&(A|B); // { foo: number, baz: boolean } or { foo?: number, bar: string, baz: boolean } 内部のプロパティの型がひとつずつマージされる感じ AnC 型の foo プロパティのように、同じ型でありながら必須と省略可能が交差したら、必 須のほうが優先される もし同じプロパティで型が共通点のないものだった場合はnever 型になる type Unit = 'USD' | 'EUR' | 'JPY' | 'GBP'; interface Currency { unit: Unit; amount: number; } interface IPayment extends Currency { date: Date; } type TPayment = Currency & { date: Date; }; const date = new Date('2020-09-01T12:00+0900'); const payA: IPayment = { unit: 'JPY', amount: 10000, date }; const payB: TPayment = { unit: 'USD', amount: 100, date }; extends によるインターフェースの拡張と同等のことが、交差型を使えばできる 結合させた型をこうやって型エイリアスにしてしまえば、結果的には同じになる ただ同名のプロパティが交差した場合、インターフェース拡張では互換性のない型だとコンパイル エラーになるという挙動のちがいがある 型の Null 安全性を保証する TypeScript はデフォルトの設定ではすべての型に null と undefined を代入できてしまう TypeScript で厳密に null や undefined を他の型から区別するためには、コンパイラオプション strictNullChecksを設定する必要がある あえて null を許容したい場合は共用体型で明示的に表現する > let foo: string | null = 'fuu'; > foo = null; type Resident = { familyName: string; lastName: string; mom?: Resident; }; const getMomName = (resident: Resident): string => resident.mom.lastName; const patty = { familyName: 'Hope-Rabbit', lastName: 'patty' }; getMomName(patty); getMomName を定義してる行、resident.mom のところにエラーがでる Resident 型の mom プロパティは省略可能なので undefined へのアクセスになる可能性があって、 コンパイルするまでもなく VS Code が教えてくれてる - const getMomName = (resident: Resident): string => resident.mom.lastName; + const getMomName = (resident: Resident): string => resident.mom!.lastName プロパティアクセスの前に ! が追加 非 Null アサーション演算子(Non-Null Assertion Operator)といって、『ここには絶対に null も undefined も入りませんよ』とコンパイラを強引に黙らせるもの ただこれはせっかくの null 安 全性を壊すもので、実際に値が null や undefined だったら実行時エラーになる。だからよっぽどの 保証がない限り使うべきじゃない 参考書籍
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

タイトルを呟いたらポスターとあらすじを答える映画どろbotを作ってみた

『世界侵略: ロサンゼルス決戦』と『スカイライン -征服-』ってどっちがどっちでしたっけ? ラストに主人公が〇〇になって「うぉぉぉ!」とテンション上がったほうを見返したいんですけど、どっちでしたっけ? という時にあらすじとポスターを調べてくれるLINE botを作りました。 実際に動いているところの動画 手軽にネタバレなしのあらすじを知りたいときに タイトルをGoogleやWikipediaで調べてしまうと記事タイトルでネタバレを見てしまうリスクがあります。TMDb(The Movie Database)から取得するので公式のあらすじとポスターだけを知ることができます。 こんな時に使えます。 - 映画をおすすめされた時にあらすじとポスターを知りたい - 自分が長期シリーズの何作目まで観たのか思い出したい 環境 Node.js 15.12.0 npm 7.6.3 axios 0.21.1 LINE bot TMDb API コード app01.js 'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: '【LINEのAPIキーを入力】', channelAccessToken: '【LINEのアクセストークンを入力】' }; // ########## ▼▼▼ メイン関数 ▼▼▼ ########## const sampleFunction = async (event) => { let pushTitle = ''; let pushOriginalTitle = ''; let pushOverview = ''; let pushPosterUrl = ''; let pushBackdropUrl = ''; try { //APIの説明ページは https://developers.themoviedb.org/3/search/search-movies です。 let response = await axios.get( 'https://api.themoviedb.org/3/search/movie?api_key=【TMDbのAPIキーを入力】&language=ja&query='+ encodeURIComponent(event.message.text)+'&page=1&include_adult=false' ); //検索結果が複数の場合に備えてループする for (let i = 0; i < response.data.total_results; i++) { pushTitle = response.data.results[i].title; pushOriginalTitle = response.data.results[i].original_title; pushOverview = response.data.results[i].overview; pushPosterUrl = 'https://image.tmdb.org/t/p/original' + response.data.results[i].poster_path; pushBackdropUrl = 'https://image.tmdb.org/t/p/original' + response.data.results[i].backdrop_path; await client.pushMessage(event.source.userId, { type: 'text', text: pushTitle, }); await client.pushMessage(event.source.userId, { type: 'text', text: pushOriginalTitle, }); await client.pushMessage(event.source.userId, { type: 'text', text: pushOverview, }); await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: pushPosterUrl, previewImageUrl:pushPosterUrl }); await client.pushMessage(event.source.userId, { type: 'image', originalContentUrl: pushBackdropUrl, previewImageUrl:pushBackdropUrl }); } } catch (error) { // APIからエラーが返ってきたらメッセージに表示する await client.pushMessage(event.source.userId, { type: 'text', text: "ごめんなさい、エラーです・・・", })}; }; // ########## ▲▲▲ メイン関数 ▲▲▲ ########## // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 詰まったところをシェアします 前回QiitaAPIを使って記事の作成日を取得したときにはdataが配列でしたが、TMDb APIはdataより1段下のresultsが配列でした。TMDb APIの仕様書をちゃんと読みましょう。 QiitaAPI: response.data[i].created_at TMDb API: response.data.results[i].original_title LINE botで画像として送るにはhttpではダメで、httpsにする必要がありました。LINE botの仕様書をちゃんと読みましょう。 おわりに Googleでタイトルを検索すれば同じことはできるのですがLINE botに呟くほうが圧倒的に簡単で余計な情報を目にする必要もなく、ポスターの画質も良くて満足です。今回はngrokで仮で動かしましたが自分用に常時起動しておいてもいいと思ってしまうほどでした。 TMDbのサイトをブラウザで見ると劇中画像が複数枚あったり、各国のポスターも閲覧できるので、これもAPIで取れないか今後やってみたいです。 ━-━-━-━-━-━-━-━-━-━-━-━-━━-━-━-━-━-━-━-━-━ 2021年4月からプロトアウト(プロトタイプ+アウトプット)スタジオに参加して、技術を学んだり自身を深掘りして卒業制作=クラウドファンディングのテーマを決めたりしています。 金融系SEという安定稼働を最優先にガチガチに設計書を作ってバグは許さぬ、という世界で十数年やってきました。自分の性格としても独創的なアイディア出しは苦手で、決まったことを正確に効率的にこなすことが得意です。 そんな私が無事クラウドファンディングに辿り着いて成功できるのか、見守っていただけましたら幸いです。 〇情報発信 ・自身とテーマ深掘り的な記事 → note ・開発中のつぶやき → Twitter 参考URL TMDbのAPIの映画情報をPythonで取得してみた Web APIを利用して映画のポスターやあらすじを自分のサイトに表示する ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。 LINE Messaging API でできることまとめ【送信編】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSの勉強でタイマーカウントライブラリを作成してみた!

JSの勉強でタイマーカウントライブラリを作成してみた! 概要 今まで何となくで書いていたJS。最近になってようやくモチベーションも少し上がってきたので新しく何かやってみようと思いました。 そこで、初めてライブラリを作成してみようと発起。 とりあえずは簡単なライブラリで挑戦ということで、サンプルとかでありそうなタイマーカウントのライブラリを作成してみました! 使い方 今回のはjqueryなしでもいけます。なので基本的にはどの環境でもいけるのではないかと思われます。 HTML HTML側ではjsの読み込みと、カウンターの表示用のタグしか記述しておりません。 見た目はまったく装飾していないので、ご自身のサイトで使用する場合はCSSでお好きなように装飾してください。 index.html <!DOCTYPE html> <html> <head> <title>setTimerCount.js</title> <script src="./assets/js/setTimerCount.js"></script> <script src="./assets/js/index.js"></script> </head> <body> <div class="contents"> <div class="inner"> <div> <p id="timer_count_set1"></p> </div> </div> </div> </body> </html> JS setTimerCount.js これが本体側のJSファイルになります。 ここは基本的にいじる必要はありません。 setTimerCount.js let TimerSetCounter = (function () { }); TimerSetCounter.prototype = { getDefaultOptions: function (options) { let defaultOptions = {}; if (options) { defaultOptions = options; } else { defaultOptions = { max_count: 100, count_separate: 1000, finish_method: function () { }, }; } return defaultOptions; }, countTimerSet: function (targetEle, options) { this.countTimer(targetEle, options); }, countTimer: function (targetEle, options) { const option = this.getDefaultOptions(options); const target = document.getElementById(targetEle); let max_count = option.max_count; let timer_id = 0; const countDown = () => { timer_id = setTimeout(countDown, option.count_separate); if (max_count >= 1) { max_count--; } else { this.countFinished(option); clearTimeout(timer_id); } target.textContent = max_count; return max_count; } current_timer = countDown(); return current_timer; }, countFinished: function (options) { const option = this.getDefaultOptions(options); if (option.finish_method) { option.finish_method(); } } }; index.js これが呼び出し側のJSになります。 オプションをほんの少しだけですが使用できるようにしてありますので、ご利用くださいませ。 index.js window.onload = function () { const timerSetCounter1 = new TimerSetCounter(); const options = { max_count: 10, count_separate: 1000, finish_method: function () { console.log('終了'); }, }; timerSetCounter1.countTimerSet('timer_count_set1', options); }; finish_method: function () { console.log('終了'); }, この部分でカウンターが0になったら何か任意で処理ができるようにしてあります。 「finish_method」の中身は今はconsole.logしか書いてありませんが、何かしたい処理を書いてください。 例)Domの操作とか 最後に コードが汚くて申し訳ないです。。 一応動くと思いますので良かったら使ってやってください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モダンWebフロント個人的ピックアップ #2 - アーキテクチャ編

はじめに 今回はフロントエンドアーキテクチャを中心にピックアップしてみました。後ほど紹介する記事のなかで「フロントエンドにおいても現代設計論が適応できる時代になっている」と言うことを知り、重点的に取り上げてみようと思いました。自分自身も将来はフロントエンドの設計が適切にできるようになりたいと感じました。 前回の記事↓ 知識の露出・共有を適切にしてクリーンな設計をしよう 個人的まとめ ※個人的な解釈も含まれます。間違っていたらご指摘頂けると嬉しいです。 - クリーンな設計をしよう ≒ 技術的負債を少なくしよう - 技術的負債とは「保守や機能追加が高コストになったシステム」 - 技術的負債が貯まる原因は密結合の範囲が広い。知識を露呈しすぎている - 適切な広さの密結合はメリットも多いため、密結合と疎結合を使い分けることが重要 良くない例 - データを直接読み書きする -> 読み書きするコードはデータ構造の知識を持っていることになるので、そのデータ構造が変更されたら、そのデータ構造にアクセスしているコードを探し出して変更する必要があるため改修が大変。 - 責務が複数存在している -> 関数が複数の責務を持っていると言うことは、その分その関数を使っているコードが多く存在すると言うことになる。つまり、この関数を改修しようとすると、影響範囲が広くなるため改修コストが大きい。 - 関数に書かれたコードが知識として露出される -> 関数に書かれたコードが知識として露出されていると、コピペなどによって技術負債が貯まる原因となる。実装されたコードを隠すためにインターフェースを用いて露出する知識と、そうではない知識を明確に分離する。 - 実装が使う側の知識を持っている -> 実装が関数やクラスを使う側の知識を持っていると、改修した時に使う側が影響を受ける可能性があるため改修コストが高くなる。 良くない例のように知識が露呈してしまうと、密結合になり、モジュール同士が複雑に絡み合ったガチガチの状態でアプリが完成して改修が大変になってしまうため、知識を不必要に露呈しないように設計するのが重要だと感じました。 宣言的UIはReact Hooksで完成に至り、現代的設計論が必須の時代になる 個人的まとめ ※個人的な解釈を含みます。間違っていたらご指摘頂けると嬉しいです。 - React Hooksと宣言的UIによって大規模開発に用いられる設計論に対応できるようになり、技術的負債の抑制が可能になった(広範囲の密結合を防げる) - ある程度以上の規模で開発するなら設計論をうまく使い設計しないと、技術的負債を抱え込む - React Hooks以後の宣言的UIならば完全に設計論に対応できる - 宣言的UIにより以前のDOMの状態にアクセスする必要性が無くなった -> 状態管理とDOMが切り離された - 状態管理とDOMが切り離されたことによって、状態管理に設計論を適応できるようになった。代表例がFlux -> 「このDOMにアクセスして状態を更新して」といったようなことをしなくて良い。つまり状態に関する実装が特定のUI、DOMに依存せずに実装することが可能になった。状態の更新に関する処理は特定のDOMに依存しないため、使い回すことができる。 - Hooksによりローカルの状態を持つ関数をできるようになった -> Hooks以前はローカルの状態(フォームの値など)に関する処理は特定のコンポーネントに依存する形でしか記述ができなかった。HOCなどを用いれば切り離せるが、Reactのシンプルさを失う。Hooksという解決方法が登場したことによって、UIとローカルの状態管理が綺麗に分離されたため、設計論を適応できるようになった。 Clean Architecture for React 個人的まとめ SOLIDの原則 - SOLIDの原則はモジュール間の接続に関する設計原則。SOLIDの原則を満たしていることは、Clean Architectureを実現する上で重要 - 単一責任の原則(SRP): モジュールはたった一つのアクターに対して責務を負うべきである(DRYの原則を適応するときは、アクターが同じかを確認するべき) - オープン・クローズドの原則(OCP): 機能追加などの拡張の際に既存のコードを修正する必要がなく(オープン)、修正する際に他のモジュールに影響がない(クローズド)べき - リスコフの置換原則(LSP): SがTの派⽣ならば、Tが使われている箇所をSに置き換えても同じ振る舞いをするべき - インターフェース分離の原則(ISP): 一つのインターフェースに全てを詰め込むのではなく、クライアントが必要とする機能のみを提供するべき - 依存関係逆転の原則(DIP): ソースコードの依存関係が(具象ではなく)抽象だけを参照するべき - 補足: 下の図1上の円の内側の安定なコードから外側の不安定なコードに依存すると、依存している内側まで不安定になってしまうため、依存関係を逆転するべきという考え 図1 Clean Architecture - 核となるビジネスドメインを他の依存からいかに守るかを重視する - エンティティ: 企業全体の最重要ビジネスルールをカプセル化したもの。その企業が取り組んでいるビジネスのルールを表す - ユースケース: アプリケーション固有のビジネスルールを扱う。エンティティを操作しながらアプリケーションが必要とする処理を行う - DIP違反をする箇所はメインコンポーネント(main関数など)にまとめる Dependency Injection - DIコンテナなどを使わずにDIを行うと、DIする度に具象クラスをインスタンス化する必要があるため、結局依存することになる(具象クラスをimport箇所が依存している箇所) - DIコンテナを使うと、依存する箇所を一箇所にまとめることができる - TypeScriptにはInversifyJS、TSyringeなどのDIライブラリがある SPA - Viewは変更されやすい(不安定な)ためClean Architectureにおいて円の一番外側に位置する - SPAはViewに相当する - SPAには複雑な設計が求められるようになってきたため、本来プロダクト全体を設計する手法であるClearn Architectureのような現代的な設計論を適用する余地が生まれた - SPAにおける円の中心はビューモデル。ビューモデルはエンティティを表示のために変換したもの - ユースケースはビューモデルを操作しながら表示に必要な処理を行う ぼくのかんがえたフロントエンドアーキテクチャ kichionさんが考案した実際に現代的設計論を適用したフロントエンドアーキテクチャです。 僕自身もフロントエンドアーキテクチャを少し考えたりしています。今後の記事で書こうと思っているので頑張って完成させたいと思います?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React状態管理ライブラリJotaiとValtioのGitHubスター数推移を見てみた

これを見ると、jotaiの順調な伸びに比べるとvaltioの伸びが良くないように見えます。valtioはv1リリースして、jotaiはまだv1リリースに到達していないのですが。ちなみに、valtioの初回リリースのスター数の伸びは凄まじかったです。このグラフでもほぼ直角に上がっているように見えますね。 それぞれのリンクはこちら。 どちらが好みですか?「React Fan」というコミュニティのSlackでjotaiやvaltioに関する質問や雑談ができますので、よろしければご参加ください。 React開発者向けオンラインサロン「React Fan」の入り口ページ React開発者向けオンラインサロン「React Fan」のTwitterアカウント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeafletでGPSログ(GPX)地図:GPXファイルをURLで任意指定可能に。

地図データを扱うJavaScriptライブラリ「Leaflet」を使って、GPSログ(GPXファイル)の地図表示を試している。 今回、GPXファイルをURLで任意指定できるようにした。 詳細は以下に。 https://2ndart.hatenablog.com/entry/2021/05/09/104740
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javascript】カルーセルを作ってみた

初めに 学習した内容をもとにjavascriptとsassでthumbnailの矢印ボタンをクリックしたら中央の写真が切り替わるカルーセルを作ってみました。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 完成形のイメージ 作業の手順 画像を集める 画像を軽量化 画像サイズを変更 html、sassやjavascriptファイルの配置 htmlで骨組みを作る sassでanimation&見た目をよくする javascriptでボタンをクリックした時の動きをプログラミングする 画像を集める 無料素材を使いした。画像無料素材と検索すると色々出てきますが、そんな中でpixabayが良さそうだったので使ってみました。合計7枚をダウンロード。 https://pixabay.com/ja/ 画像をカスタマイズ ダウンロードした画像はそのままでは重いので、軽量化することに。サイズを減らすことでローディングに時間が掛からないようにしてパフォーマンスを上げることができます。imageoptimというツールがあるのでそれを利用しました。使い方についての関連記事は以下のとおり。 https://akihiko-s.com/imageoptim 画像サイズを変更 画像高さを360pxに調整しました。画像がはみ出ると見栄えが良くないので、7枚の写真の高さを揃えます。画像編集ツールはなんでもいいですが、今回使用したのはSketchBookです。 https://www.autodesk.co.jp/products/sketchbook/overview html、sassやjavascriptファイルの配置 htmlファイルとjavascriptファイルはそれぞれメインのフォルダーに置きました。また、画像はimagesフォルダーにsassはcssフォルダーに配置します。 htmlで骨組みを作る carouselというsectionの中にcarousel_mainとcarousel_thumbnails_wrapperを用意します。mainのところに表示させるターゲットの画像が入り、thumbnailsには全ての画像を一覧表示するようにします。また、ボタンになるprevやnextを用意します。thumbnail画像はulタグの中でリストとして表示させたいので、carousel__thumbnailsもあわせて用意します。id名はjavascriptで使うようにし、class名はsassの記述に使うようにしています。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/app.css"> <title>Carousel</title> </head> <body> <section id="carousel" class="carousel"> <div class="carousel__main"><img id="carousel__main"></div> <div class="carousel_thumbnails_wrapper"> <div id="carousel__prev" class="carousel_thumbnails_wrapper__prev"></div> <div id="carousel__next" class="carousel_thumbnails_wrapper__next"></div> <ul class="carousel__thumbnails"></ul> </div> </section> <script src="main.js"></script> </body> </html> sassでanimation&見た目をよくする mainとthumbnailsのエリアは横並びにしたいので、displayをflexにします。また、mainの画像はclip-pathを使って表示させたい画像を楕円形に切り抜きます。また、表示する画像はfade inアニメーションを適用したいので@keyframesを使って透明度を0~1に0.8秒間に渡って変化するようにします。タイミングはボタンをクリックした時にアニメーション処理をしたいので、mainクラスの直下のimgタグのactiveクラスに記述します。activeクラスはjavascriptで付けたり外したりの処理を行います。 thumbnailsの画像は透明度を0.4にします。こうすることによって選択されているかいないかが分かります。現在選択したthumbnailsの画像にはcurrentクラスのところにopacityを1にして不透明にします(鮮明に見えるようにする)。currentクラスも後からjavascriptで処理します。 .carousel { width: 840px; height: 400px; margin: 0 auto; position: relative; display: flex; &__main { width: 640px; height: 400px; margin: 0 auto; clip-path: ellipse(40% 39% at 50% 50%); & > .active { animation: fadeImg 0.8s; } } &__thumbnails { list-style: none; padding: 0; & li { cursor: pointer; opacity: 0.4; &:hover { opacity: 1; } &.current { opacity: 1; } } & img { width: 80px; } } } @keyframes fadeImg { from { opacity: 0; } to { opacity: 1; } } 続いて矢印ボタンをcssで作成します。作り方は前回の記事を参考にしました。prevが"<"矢印でnextが">"です。 .carousel_thumbnails_wrapper { position: relative; &__prev { position: absolute; top: 23px; left: 110px; cursor: pointer; color: lightgray; width: 30px; height: 30px; &:hover { transition-duration: 0.2s; transform: translateX(-10px); } &::before { content: ""; display: block; width: 5px; height: 5px; position: absolute; top: 50%; left: 10px; margin-top: -2.5px; border-top: 1px solid #ccc; border-right: 1px solid #ccc; transform: rotate(45deg); } } &__next { position: absolute; top: 23px; right: 110px; cursor: pointer; color: lightgray; width: 30px; height: 30px; &:hover { transition-duration: 0.2s; transform: translateX(10px); } &::after { content: ""; display: block; width: 5px; height: 5px; position: absolute; top: 50%; right: 10px; margin-top: -2.5px; border-top: 1px solid #ccc; border-right: 1px solid #ccc; transform: rotate(-135deg); } } } javascriptの記述 最後にjavascriptでボタンをクリックした際の動きを書いていきます。 Main Concept ①imagesフォルダーにある画像は配列の形(images配列)にして格納する ②currentIndexという変数を用意し、現在選択されている画像のindexを取得する ③for文を使い、配列のindexと値(image)をそれぞれ取り出してmainやthumbnailに画像を表示させる ④prevやnextをクリックした時にtargetという変数を用意し、現在選択されている画像のindexを+もしくは-する処理をする "use strict"; // imagesという定数に画像を配列として格納する const images = [ "images/1.jpg", "images/2.jpg", "images/3.jpg", "images/4.jpg", "images/5.jpg", "images/6.jpg", "images/7.jpg", ]; //現在選択した画像のindex番号をcurrentIndexとして初期化する let currentIndex = 0; //メインの画像が配置されるimgタグをmainImageとして取得。srcメソッドで配列の画像をcurrentIndexを添字にして表示 const mainImage = document.getElementById("carousel__main"); mainImage.src = images[currentIndex]; // 配列imagesからindexと中身imageを一つひとつ取り出し、thumbnails画像を表示する for (let [index, image] of images.entries()) { // thumbnails用のimgタグを生成して、imageを格納する const img = document.createElement("img"); img.src = image; //liタグを作り、liタグの子要素にimgタグを配置する const li = document.createElement("li"); li.appendChild(img); document.querySelector(".carousel__thumbnails").appendChild(li); // thumbnailsの画像を配置するliを作成する。for文のindexが現在選択されているcurrentIndexと同じ場合はliタグにcurrentクラスを付与する(不透明になる) if (index === currentIndex) { li.classList.add("current"); } //thumbnails(liタグ)をクリックした時の処理 ---❶ li.addEventListener("click", () => { //main画像は配列から取り出したimageになる。また、activeクラスを付与してfade inアニメーションを適用する mainImage.src = image; mainImage.classList.add("active"); //activeクラスは0.8秒後に消えるようにする。理由は他のthumbnail画像(liタグ)をクリックした時にactiveクラスがすでに存在する状態だとアニメーションがスタートしないため setTimeout(() => { mainImage.classList.remove("active"); }, 800); //thumbnailsという定数にthumbnails画像が入っているliタグ全てを取得して格納(配列の形になる) const thumbnails = document.querySelectorAll(".carousel__thumbnails > li"); //現在選択されているindexに該当する配列thumbnailsのli要素にcurrentクラスを外して初期化する thumbnails[currentIndex].classList.remove("current"); //currentInexにクリックしたliタグのindex番号をセットし直し、currentクラスを付与する(currentが付いていると不透明になる) currentIndex = index; thumbnails[currentIndex].classList.add("current"); }); } //nextボタンをクリックした時の処理。変数targetを用意してcurrentIndexに1を引いた値を格納する const next = document.getElementById("carousel__next"); next.addEventListener("click", () => { let target = currentIndex - 1; //もしtarget(添字)が0以下の場合は7番目の画像から1を引くようにする。 if (target < 0) { target = images.length - 1; } //liタグを全て取得し(配列の形)添字のtargetを指定。.click()メソッドでliタグがクリックされた時と同じ処理をするようにする(❶の処理) document.querySelectorAll(".carousel__thumbnails > li")[target].click(); }); //prevボタンをクリックした時の処理。変数targetを用意してcurrentIndexに1を足した値を格納する const prev = document.getElementById("carousel__prev"); prev.addEventListener("click", () => { let target = currentIndex + 1; //もしtarget(添字)がimages画像の最後の番号である場合には8番目の画像はないため、0にする。 if (target === images.length) { target = 0; } //liタグを全て取得し(配列の形)添字のtargetを指定。.click()メソッドでliタグがクリックされた時と同じ処理をするようにする(❶の処理) document.querySelectorAll(".carousel__thumbnails > li")[target].click(); }); 参考記事 https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/click https://qiita.com/righteous/items/e38ce1d1c7861d02b87d
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】filterMap()をジェネレータ関数を使って作ってみた

前置き //これを array.filter().map() //こうしたかった array.filterMap() ざっくり言いますと filter()やmap()のcallback関数をジェネレータ関数で処理するようにしただけです。 引数は同様。 最初のyieldでfilter()、2回目のyieldでmap()。 「こんなのがあったらいいな」というものなので、実用に耐え得るか否かはわかりません。 function const filterMap = (arr, generator) => { try { if (!Array.isArray(arr)) { throw new Error('1st arg : not Array'); } if (typeof generator!=='function' || generator.constructor.name!=='GeneratorFunction') { throw new Error('2nd arg : not GeneratorFunction'); } const array = arr.slice(); const result = []; for (let i=0,m=array.length; i<m; i++) { const gen = generator(array[i], i, array); if ( gen.next().value ) { result.push( gen.next().value ); } gen.return(); } return result; } catch(e) { console.error(e); } }; const arr = [0,1,7,2,1,3,4,5,9,4,5,0,8,3,5,7,4,4]; //値が5のindexを出力 const idx5 = filterMap(arr, function*(x,i,a) { yield x===5; //filter yield i //map }); console.log( idx5 ); //  [7, 10, 14] const chunk = filterMap([...Array(10).keys()], function*(x,i,a) { yield i%3===0; //filter yield a.slice(i, i+3); //map }); console.log( chunk ); //[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] Array.prototype if (!Array.prototype.filterMap) { Object.defineProperty(Array.prototype, 'filterMap', { value: function(generator) { try { if (typeof generator!=='function' || generator.constructor.name!=='GeneratorFunction') { throw new Error('arg is not a GeneratorFunction'); } const array = this.slice(); const result = []; for (let i=0,m=array.length; i<m; i++) { const gen = generator(array[i], i, array); if ( gen.next().value ) { result.push( gen.next().value ); } gen.return(); } return result; } catch(e) { console.error(e); } }, }); } const arr = [0,1,7,2,1,3,4,5,9,4,5,0,8,3,5,7,4,4]; //値が5のindexを出力 const idx5 = arr.filterMap(function*(x,i,a) { yield x===5; //filter yield i //map }); console.log( idx5 ); //  [7, 10, 14] const chunk = [...Array(10).keys()].filterMap(function*(x,i,a) { yield i%3===0; //filter yield a.slice(i, i+3); //map }); console.log( chunk ); //[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] 補足 const idx5 = arr.filterMap(function*(x,i,a) { yield x===5; //filter yield i //map }); 処理が単純であればyield* [filter, map]でまとめられます。 const idx5 = arr.filterMap(function*(x,i,a) { yield* [x===5, i]; // [filter, map] }); 私感・妄想 ジェネレータ関数にもアロー関数のような略した書き方がほしい。 妄想.js //これを function*(x,i) { yield* [x===5, i]; }); //こうして (x,i) *> { yield* [x===5, i] }; //更にこうして (x,i) **> [x===5, i]; //こうなる const idx5 = arr.filterMap((x,i) **> [x===5, i]); 妄想2.js //こういうのも function* increase(n) { while (true) yield n++; }); //こう…欲張りすぎか const increase = n **> n++; なんてね 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

next.jsでreactのcreateContextをviewとデータを切り離して綺麗に書く方法

開発環境: macOSX catalina バージョン10.15.7 visualStudioCode react next.js createContextを綺麗に書く方法を教わったので、備忘録 以下のような書き方をして、Contextを定義する そうすることによって、viewとデータを切り離すことができ、可読性が高まるとのこと import React, { useReducer, createContext } from "react"; import reducer from "./reducer"; const tagList = []; const MyContext = createContext(""); const Store = ({ children }) => { const [state, dispatch] = useReducer(reducer, tagList); return ( <MyContext.Provider value={{ state, dispatch }}> {children} </MyContext.Provider> ); }; export { MyContext, Store }; 以下のようにContextを下位コンポーネントにつなぐ import "../styles/globals.css"; import React from "react"; import { Store } from "../store/store"; function MyApp({ Component, pageProps }) { return ( <Store> <Component {...pageProps} /> </Store> ); } export default MyApp;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む