20210607のJavaScriptに関する記事は27件です。

Next.jsのAPI RoutesをTestする

はじめに Next.jsのAPI Routesを利用する機会があり、公式ドキュメントを見ながらhmhmと実装していました。 なんとかAPIの方は無事に実装でき、さあJestでテストも作成するぞ!となったところで、APIを作成するとき以上に色々とドキュメントを巡る羽目になったので、その記録です。 テスト対象のコード だいぶテキトーな感じのAPIですが、とりあえずテスト対象を用意したかっただけなので、どうかここは一つ。 今回は用いていませんが、API内で関数を用いている場合、通常通りjest.spyOnやjest.mockで関数をモックすることができます。 通常のGET,POSTリクエスト とりあえずテストできるコードがあればいいので、適当に足し算するだけのコードです。 /pages/api/testApi1.ts import type { NextApiRequest, NextApiResponse } from 'next'; export default (req: NextApiRequest, res: NextApiResponse) => { const method = req.method; switch (method) { case 'GET': { const { val1, val2 } = req.query; const result = Number(val1) + Number(val2); res.status(200).json({ result }); break; } case 'POST': { const { val1, val2 } = req.body; const result = val1 + val2; res.status(200).json({ result }); break; } default: { res.status(403).end(); } } }; Dynamic API Routesを利用するコード Next.jsだと、ファイル名を[id].tsのような形にすることで、動的なpathを設定することができます。これがテストする際にちょっとばかり面倒なので、一応例として用意しておきます。 /pages/api/test2/[userId].ts import type { NextApiRequest, NextApiResponse } from 'next'; import { setCookie } from '../../../modules/cookie'; export default (req: NextApiRequest, res: NextApiResponse) => { const method = req.method; switch (method) { case 'GET': { const { userId } = req.query; setCookie(res, 'userId', userId); res.status(200).json({ header: res.getHeader('Set-Cookie') }); break; } default: { res.status(403).end(); } } }; /modules/cookie.ts import { serialize, CookieSerializeOptions } from 'cookie'; import { NextApiResponse } from 'next'; export const setCookie = ( res: NextApiResponse, name: string, value: unknown, options: CookieSerializeOptions = {} ) => { const stringValue = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value); res.setHeader('Set-Cookie', serialize(name, String(stringValue), options)); }; 自分で頑張る編 Next.jsのAPI Routesで、作成したAPI用の関数を実行するためのapiResolverを利用し、nodeのhttpモジュールで簡易的なサーバを作成してリクエストを送ります。 基本系 クエリパラメータをapiResolverに渡す前にurlから連想配列に変える必要があることがちょっと面倒なくらいでしょうか。 /test/testApi1 import http, { IncomingMessage, ServerResponse } from 'http'; import { apiResolver } from 'next/dist/next-server/server/api-utils'; import request from 'supertest'; import handler from '../pages/api/testApi1'; describe('API Test', () => { let server: http.Server; const mockedApiPreviewProps = { previewModeId: '', previewModeEncryptionKey: '', previewModeSigningKey: '', }; // サーバで受け取るリクエストの処理 const requestHandler = (req: IncomingMessage, res: ServerResponse) => { if (typeof req.url !== 'string') { throw 'error'; } const url = new URL(`http://localhost${req.url}`); // URLの形式に合わせる const query = Object.fromEntries(url.searchParams); // クエリパラメータを連想配列に変換 return apiResolver(req, res, query, handler, mockedApiPreviewProps, false); }; beforeAll(() => { // サーバ作成 server = http.createServer(requestHandler); }); afterAll(() => { // サーバ停止 server.close(); }); test('API ROUTEのテスト GET', async () => { const agent = await request.agent(server).get('/testApi1?val1=2&val2=3'); expect(agent.status).toEqual(200); expect(agent.body.result).toEqual(5); }); test('API ROUTEのテスト POST', async () => { const agent = await request .agent(server) .post('/testApi1') .send({ val1: 2, val2: 3 }); expect(agent.status).toEqual(200); expect(agent.body.result).toEqual(5); }); }); Dynamic API Routesに対応する Next.jsのDynamic Routesを使った場合、対応する変数はクエリパラメータと同様のqueryに格納されます。 というわけで、その処理を追加します。 注意点ですが、おそらく該当のAPIによって変数名が異なる(今回はuserId)ので、元のAPIに合わせて変数名を変更する必要があります。 /test/testApi2.ts import http, { IncomingMessage, ServerResponse } from 'http'; import { apiResolver } from 'next/dist/next-server/server/api-utils'; import request from 'supertest'; import handler from '../pages/api/test2/[userId]'; describe('API Test', () => { let server: http.Server; const mockedApiPreviewProps = { previewModeId: '', previewModeEncryptionKey: '', previewModeSigningKey: '', }; const requestHandler = (req: IncomingMessage, res: ServerResponse) => { if (typeof req.url !== 'string') { throw 'error'; } const url = new URL(`http://localhost${req.url}`); // URLの形式に合わせる const userId = url.pathname.replace('/', ''); const query = Object.assign(Object.fromEntries(url.searchParams), { userId, }); return apiResolver(req, res, query, handler, mockedApiPreviewProps, false); }; beforeAll(() => { server = http.createServer(requestHandler); }); afterAll(() => { server.close(); }); test('API ROUTEのテスト GET', async () => { const agent = await request.agent(server).get('/1'); expect(agent.status).toEqual(200); console.log(); expect(agent.headers['set-cookie']).toEqual( expect.arrayContaining(['userId=1']) ); }); }); next-test-api-route-handler使う編 自分で一から書くと、上のテストコードを見ればわかる通り、そこそこ面倒です。 なので、それを解決するためにnext-test-api-route-handlerというモジュールがあります。もっとも、モジュールのリポジトリにあるコードをみるとわかりますが、やっていること自体は上の自力実装の場合とあまり変わりません。 ただ、面倒な記述を省くことができるので、こちらを利用した方が楽に済みます。 基本系 わざわざ自分でサーバを作成する必要がなくなるので、かなりコード量が減りシンプルになりました。 /test/testApi3.ts import { testApiHandler } from 'next-test-api-route-handler'; import handler from '../pages/api/testApi1'; describe('next-test-api-route-handler test', () => { test('API ROUTEのテスト GET', async () => { expect.hasAssertions(); await testApiHandler({ requestPatcher: (req) => (req.url = '/api/testApi1?val1=2&val2=3'), handler, test: async ({ fetch }) => { const res = await fetch({ method: 'GET', }); expect(await res.json()).toStrictEqual({ result: 5 }); }, }); }); test('API ROUTEのテスト POST', async () => { expect.hasAssertions(); await testApiHandler({ requestPatcher: (req) => (req.url = '/api/testApi1?val1=2&val2=3'), handler, test: async ({ fetch }) => { const res = await fetch({ method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ val1: 2, val2: 3 }), }); expect(await res.json()).toStrictEqual({ result: 5 }); }, }); }); }); Dynamic API Routesに対応する こちらも記述量がだいぶ減り、シンプルになりました。 ただ、こちらもやはり自動でDynamic Routesに対応することができない(そもそも変数名を指定してる部分がファイル名なので……)ため、paramsでurlとは別に値を指定する必要があります。 /test/testApi4.ts import { testApiHandler } from 'next-test-api-route-handler'; import handler from '../pages/api/test2/[userId]'; describe('next-test-api-route-handler test', () => { test('API ROUTEのテスト GET', async () => { expect.hasAssertions(); await testApiHandler({ params: { userId: 222 }, handler, test: async ({ fetch }) => { const res = await fetch({ method: 'GET', }); const headers = (await res.headers) as Headers; expect(headers.get('set-cookie')).toEqual('userId=222'); }, }); }); }); おわりに この記事のテスト方法自体は、主にNext.jsのリポジトリにあったDiscussionをまとめたものです。 1年開かずに似たようなDiscussionが立っているあたり、やはり地味にテスト方法が分かりづらいんだなぁ、と感じました。自分も既存のDiscussionがなければ、まずお手上げだったことでしょう。 その点、next-test-api-route-handlerは非常に手軽にテストすることができます。 惜しむらくは、まだあまり有名どころではなさそう、ということでしょうか。そもそもNext.jsのAPI Routesのテスト専用という時点でだいぶ範囲が限られており、誰もが利用する類のものでもないという理由はありそうですが。 とはいえver1.0.0がリリースされてから一年立っておらず、比較的活発に開発が続いているようなので、今のうちから利用するのも良いかもしれません。 参考 基本的な文法 [Node.js] 新旧APIでのURLパースの違い Next.jsのAPI Routesについて API Routes: Dynamic API Routes | Next.js API Routes: API Middlewares | Next.js next.js/api-utils.ts at canary · vercel/next.js · GitHub API Routesのテストについて How to test dynamic API routes? · Discussion #11784 · vercel/next.js · GitHub Are there official samples for testing API routes with Jest · Discussion #15166 · vercel/next.js · GitHub next-test-api-route-handler - npm
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】v-modelの修飾子の種類とその使いどころ

はじめに 仕事で使う事になったので1からVue.jsについて学んだ。 ちゃんと覚えておかないとまずそうな事を備忘録として1つ1つ残しておく。 v-modelの修飾子の種類とその使いどころ v-modelにはいくつか便利な修飾子があるが、その使いどころも含めて整理する。 何も修飾子がない場合 以下の動画のように即時に入力内容が反映される(双方向データバインディングになる)。 動画のソースコードは以下。 vue.App.vue <template> <div class="container-sm"> <!-- 省略 --> <div> <h2>イベントのフォーム</h2> <div class="mb-3"> <label for="title" class="form-label">タイトル</label> <input type="text" class="form-control" id="title" v-model="eventData.title" /> <p>{{ eventData.title }}</p> </div> </div> </div> </template> ソースコード全体は以下。 .lazy修飾子 どうなるのか? DOMイベントのchangeが発火した時にmodel(v-modelで指定したデータ)に内容が反映されるようになる。 動画のソースコードは以下。 vue.App.vue <template> <div class="container-sm"> <!-- 省略 --> <div> <h2>イベントのフォーム</h2> <div class="mb-3"> <label for="title" class="form-label">タイトル</label> <input type="text" class="form-control" id="title" v-model.lazy="eventData.title" /> <p>{{ eventData.title }}</p> </div> </div> </div> </template> <script> // 省略 export default { data() { return { // 省略 eventData: { title: "タイトル", }, }; }, // 省略 ソースコード全体は以下。 ※ちなみに、DOMイベントのchangeは、 タグで入力してフォーカスを外した時 タグで入力してEnterキーを押下した時 といった時に発火する。 使いどころ バリデーションなどで入力時に逐一走らせるよりは入力後に走らせたい時など。 その場合にこの.lazyを使う事で、DOMイベントのchangeが発火してmodleが反映されたらバリデーションを走らせるといった事ができるようになる。 .number修飾子 どうなるのか? 基本的に<input type="number">と指定したとしても以下のようにtypeofを出力するとstringになってしまうが、 .number修飾子をv-modelに付与するとnumberとして扱うようにできる。 動画のソースコードは以下。 vue.App.vue <template> <div class="container-sm"> <!-- 省略 --> <div> <h2>イベントのフォーム</h2> <div class="mb-3"> <label for="maxNumber" class="form-label">最大人数</label> <input type="number" class="form-control" id="maxNumber" v-model.number="eventData.maxNumber" /> <p>{{ typeof eventData.maxNumber }}</p> </div> </div> </div> </template> <script> // 省略 export default { data() { return { // 省略 eventData: { title: "タイトル", maxNumber: 0, }, }; }, // 省略 ソースコード全体は以下。 使いどころ v-modelでデータバインディングしている変数の型を厳格にnumberにしたい時など。 .trim修飾子 どうなるのか? 先頭・最後尾の空白を削除してv-modelの変数にデータバインディングしてくれる。 動画のソースコードは以下。 App.vue <template> <div class="container-sm"> <!-- 省略 --> <div> <h2>イベントのフォーム</h2> <div class="mb-3"> <label for="host" class="form-label">主催者</label> <input type="text" class="form-control" id="host" v-model.trim="eventData.host" /> <pre>{{ eventData.host }}</pre> </div> </div> </div> </template> <script> // 省略 export default { data() { return { // 省略 eventData: { host: "", }, }; }, // 省略 ソースコード全体は以下。 使いどころ 先頭・最後尾の空白を削除してv-modelの変数にデータバインディングしたい時など。 Vue.jsの勉強メモ一覧記事へのリンク Vue.jsについて勉強した際に書いた勉強メモ記事のリンクを集約した記事。 https://qiita.com/yuta-katayama-23/items/dabefb59d16a83f1a1d4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scrollHint スクロールのヒントが出るやつ

scrollHint スクロールができることを表示する 今回はテーブルなどで横スクロールできることがわかるように、補助的なアニメーションを表示させる 今回はpugとstylusを使っています。 コンパイルが必要になるので、html、CSSがいいって人は下記サイトで変換してください pug https://html2pug.vercel.app/ stylus https://www.cssportal.com/stylus-to-css/ サクッとデモが見たい方 下記コードペンで、デモを作っているので参考にしてください https://codepen.io/xhisashix/pen/RwpJZPWhttps://codepen.io/xhisashix/pen/RwpJZPW ディレクトリ構成 . ├── stylus │ ├── scroll-hint.css │ └── style.styl ├── index.pug └── js └── scrollHint.js CDNの読み込み script(src="https://unpkg.com/scroll-hint@latest/js/scroll-hint.min.js") // scrollHintのCDN script(src="./js/scrollHint.js") // 下記のjs pug .section .table.table_container table tr th タイトル th タイトル th タイトル th タイトル th タイトル th タイトル th タイトル th タイトル th タイトル tr td content td content td content td content td content td content td content td content td content tr td content td content td content td content td content td content td content td content td content stylus .section margin 0 auto max-width 980px width 90% background #ccc .table overflow-y scroll margin 0 auto width 600px table border 1px solid #ddd tr box-sizing border-box padding 15px 20px min-height 30px border-top 1px solid #ddd th, td box-sizing border-box padding 20px min-width 150px background #eee td background #fff 注意点 <link rel="stylesheet" href="https://unpkg.com/scroll-hint@latest/css/scroll-hint.css"> 上記のCSSを読み込むと実装できなかったので、下記よりCSSをダウンロードしています。 https://appleple.github.io/scroll-hint/ ディレクトリの構成のscroll-hint.cssが上記でダウンロードしてきたcssファイルになります。 js new ScrollHint('.table_container', { suggestiveShadow: true, remainingTime: 5000, i18n: { scrollable: 'スクロールできます', }, }); 最後に そんなに使うことはないかもしれませんが、 テーブルの構造的にレスポンシブにするの難しいときとかに便利なので、使って見てください. 誰かの約に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React+Material UIで、スタイルをJS in CSSで書く方法まとめ

株式会社ユニフェイスの大竹です。 製造実行システムIB-MesのWebアプリ版・IB-Mes SaaSのフロントエンドを担当しています。 IB-Mes SaaSではReact,TypescriptとMaterial UIを使っているのですが、毎回JS in CSSの書き方に詰まってしまうので、まとめました 以下の内容を読んで、 わかること:JS in CSSを利用したCSSの書き方 わからないこと:上記以外の時のCSSの書き方 記事を書いているときの各種ツールのバージョンは、Material UI v4、React 17.0.2ですReact 16.8で追加されたフック(Hook)を利用しています グローバルにCSSを当てる グローバルにCSSを当てるときは、'@global'を使います以下の例では、MuiPickersSlideTransition-transitionContainerというクラスに、グローバルにCSSを適用しています 参考:https://stackoverflow.com/questions/58755118/global-styles-with-react-and-material-ui const useStyles = makeStyles((theme) => ({ "@global": { ".MuiPickersSlideTransition-transitionContainer.MuiPickersCalendarHeader-transitionContainer": { order: -1, }, ".MuiTypography-root.MuiTypography-body1.MuiTypography-alignCenter": { fontWeight: "bold", }, }, })) 子要素を指定してスタイルを当てる ol(番号付き箇条書き)の先頭を(1)のようなカッコつきの表記にするCSSで説明しますolの子要素のliを指定する時は、ol:{}の中に"& li":{}を入れます疑似要素を指定する時は、"&::before"のように書きますカッコつきの表記にするポイントは、contentに`"(" counter(cnt) ") "`を指定することです 参考:https://www.websuccess.jp/blog/archives/2711/の技 調整する①の内容をJS in CSSに書き換えました const useStyles = makeStyles((theme: Theme) => createStyles({ ol: { paddingLeft: 0, marginLeft: theme.spacing(4), "& li": { listStyleType: "none", counterIncrement: "cnt", display: "block", position: "relative", "&::before": { content: `"(" counter(cnt) ") "`, marginLeft: -theme.spacing(4), width: theme.spacing(5), position: "absolute", top: 0, left: 0, }, }, }, }) ) スタイルを当てる部分では以下のように指定します const classes = useStyles() <ol className={classes.ol}> <li>hoge</li> <li>bra</li> </ol> 直下の階層の子要素を指定する 直下の階層の子要素を指定する時は、子セレクタ > を利用することができます const classes = makeStyles((theme: Theme) => createStyles({ editButton: { flexGrow: 1, textAlign: "center", "& > *": { margin: theme.spacing(1), }, }, }) ) 疑似クラスの指定 疑似クラスの指定は、:(セミコロン1個)を利用します例えばホバー時の処理を書く場合は、"&:hover"となります以下のように指定すると、マウスカーソルを載せた時だけ背景の色が変わります。 const useStyles = makeStyles((theme: Theme) => createStyles({ icon: { "&:hover": { background: theme.palette.action.active, }, }, }) ) もし間違いなどありましたら、ご指導ご鞭撻のほどよろしくお願いしますReactとMaterial UIなどについて勉強中なので、お手柔らかにお願いします
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】(シンプル)スプレッドシートから値取得⇒gasでHTMLメール送信(table形式)

◆やること スプレッドシートから値取得 gasでテーブル形式のhtmlにする htmlメール送信 以下コード ◆コード コード.js function myFunction() { //値取得、二次元配列格納 let vals = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getDataRange().getValues();  //ここでforを使ってval⇒htmlのtable形式に //オプションでHTMLメール本文を設定する let mailText = "htmlを入れた変数"; let options = {"htmlBody":mailText,}; //GmailApp.sendEmail(メールアドレス,メール表題,本文(htmlメール送信できないとき表示),htmlメールを設定({htmlBody:html本文}) GmailApp.sendEmail("メールアドレス","メール表題", mailText , options); } 上記のforの中身を書くとしたら、下記の感じです。 コード.js let tbl = "<table border='1' style='border-collapse: collapse;'><tr><th>ヘッダー1</th><th>ヘッダー2</th><th>ヘッダー3</th><th>ヘッダー4</th><th>ヘッダー25</th><th>ヘッダー6</th></tr>"; //ここでは最後</table>でしめません。forでtrを追加して、一番最後に<table>を追加します。 let newtd =""; vals.forEach(x=>{ //二次元配列valsから一つずつ配列取り出し for(let elnum in x){ //取り出した一次元配列から一つずつ要素取り出し newtd += "<td>"+x[elnum]+"</td>"; } let tr = "<tr>"+newtd+"</tr>" tbl += tr; tr newtd=""; }) let mailText = tbl+"</table>"//ここでtableを閉じます。 自分は、あとは送信先アドレス一覧を別シートで用意して、 アドレス一覧取得、GmailApp.sendEmail("メールアドレス","件名", "本文" ,); のアドレスに入れました。 あと、引っ張ってくる一覧をクエリ関数で作成、GASで日付書き換えて関数で内容変更⇒トリガーで定時に集計メールが届くようにしました。 ◆完成品(ほぼモザイクですいません) ◆その他 スプシにメアド書いて、それに送る 下記が参考になります。 【GAS】スプレッドシートからメールを送信する方法 ほかのhtmlファイルからテンプレート持ってくる 下記が参考になります。 Google Apps ScriptでGmailからHTMLメールを送信する方法 感想 これぞ自動化という感じです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コーディングについて~実装編①~

前回の命名編に引き続き、実装面に関しても勉強した内容を備忘録としてまとめます。 はじめに 本記事はJavaScriptをベースに作成しています。 変数の定義 基本的にconstを使いましょう。 過去に書いたコードを振り返ってみると、letではなくconstでよかった変数がいくつもあります。 constでは処理できない場合にletを使いましょう。 簡単にはイテレータ変数やフラグなどです。 varはどうしても必要な場合のみ使いましょう。 ちなみにですが、今のところどうしても必要になったことはありません。 // OK const test = "test"; // 状況に応じてOK let test = "test"; // 基本的にNG var test = "test"; 変数の使い回し これはNGです、新たに変数を定義しましょう。 使い回してはいけない理由として 変数の生存期間が長くなる 影響範囲が広くなる デバッグが難しくなる など様々です。 constで定義することで防止することができます。 foreachは極力使わない 私のプロジェクトではコーディング規約がないながらもfor文よりもforeachを使うという暗黙のルールがあります。 それ故に例えば以下のようなコードが量産されていました。 const tests = []; testList.foreach(test => { if (test === "test") { tests.push(test); } }); この場合はfilterを使いましょう。 const tests = testList.filter(test => test === "test"); 他にも以下の辺りを積極的に使いましょう。 案外foreachを使わずに実装できるのでそうすべきです。 find map reduce ネスト 浅いほど良いです。 深ければ深いほど読みにくく、よいことがないです。 深くなるようであれば一部を関数として切り出すなどの対応が必要です。 条件式は整理する 長いor複雑な条件式は関数として切り出しましょう。 // NG if (fileName !== null && filePath !== null && fileType !== null && fileSize !== null) { // 処理 } // OK function existFile() { return fileName !== null && filePath !== null && fileType !== null && fileSize !== null; } if (existFile()) { // 処理 } 行数が少ない=正義ではない 少ない行数で書けたとしても、それがきれいで読みやすいかは別問題です。 書き方や程度によりますが、三項演算子はif文に変換、メソッドチェーンは程よいところで変数に代入した方がよいと思います。 読みにくいだけではなくデバッグもやりにくくなります。 即return returnは早ければ早いほど良いです。 無駄な処理が動くことがない上に追いやすくなります。 // NG function test(String string) { // 処理 if (string === null) { return; } } // OK function test(String string) { if (string === null) { return; } // 処理 } コメントはとても大切 積極的に残すようにしましょう。 ただし、読めばわかるコードに残すのはNGです。 // NG:読めばわかるので不要 // testListの中で30未満のデータを抽出 const tests = testList.filter(test => test < 30); 以下例のように、読めばわかる実装ではありますが処理に意図がある場合は残す必要があります。 本当は変数名などで表すのがベストです。 // OK // 赤点の人を抽出 const tests = testList.filter(test => test < 30); 最後に どなたかの参考になりましたら幸いです。 参考 リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック JavaScript で forEach を使うのは最終手段 変数の使い回しはなぜよくないのか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】タブの作成方法

完成形 SCSSを使用 jQueryを使用 See the Pen RwpJKyX by c-koch0514 (@c-koch0514) on CodePen. HTML html <!----------------------- タブ -------------------------> <div id="tab" class="group"> <ul class="tab-group"> <li class="tab is-active">Tab-A</li> <li class="tab">Tab-B</li> <li class="tab">Tab-C</li> </ul> <!--タブを切り替えて表示するコンテンツ--> <div class="panel-group"> <div class="panel is-show"> Content-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-A </div> <div class="panel"> Content-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-B </div> <div class="panel"> Content-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-C </div> </div> </div> <!----------------------- end of タブ -------------------------> SCSS scss /***********************************************************************/ /********** タブの設定 *************/ /***********************************************************************/ .group { .tab-group, .panel-group { display: flex; width: 350px; margin: 0 auto; .tab { width: calc(350px / 3); background-color: pink; text-align: center; border: 1px solid #000; cursor: pointer; } .is-active { background-color: red; } .panel { width: 100%; background-color: yellow; border: 1px solid #000; padding: 10px 20px; display: none; } .is-show { display: block; } } } /********************* ena of タブ **********************************/ /***********************************************************************/ jQuery jQuery $(function(){ /***************** タブ **********************/ $('.tab').click(function(){ // クリックした要素の先祖要素の中で、classの値がgroupの要素を取得 const group = $(this).parents('.group'); group.find('.is-active').removeClass('is-active'); $(this).addClass('is-active'); group.find('.is-show').removeClass('is-show'); // クリックしたタブからインデックス番号を取得 var index = $(this).index(); // クリックしたタブと同じインデックス番号をもつコンテンツを表示 group.find(".panel").eq(index).addClass('is-show'); }); /*********************************************************/ }); 同じHTMLに複数タブが存在するとき HTMLで#tab以外のidを持ち、.groupをもったタブを作ればいい html <!--------------------------1つ目のタブ---------------------------------> <div id="tab" class="group"> <ul class="tab-group"> <li class="tab is-active">Tab-A</li> <li class="tab">Tab-B</li> <li class="tab">Tab-C</li> </ul> <!--タブを切り替えて表示するコンテンツ--> <div class="panel-group"> <div class="panel is-show"> Content-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-A </div> <div class="panel"> Content-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-B </div> <div class="panel"> Content-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-C </div> </div> </div> <!--------------------------2つ目のタブ---------------------------------> <div class="group"> <ul class="tab-group"> <li class="tab is-active">Tab-A</li> <li class="tab">Tab-B</li> <li class="tab">Tab-C</li> </ul> <!--タブを切り替えて表示するコンテンツ--> <div class="panel-group"> <div class="panel is-show"> Content-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-AContent-A </div> <div class="panel"> Content-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-BContent-B </div> <div class="panel"> Content-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-CContent-C </div> </div> </div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Array.includes

It's prual. Don't forget s.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIキーを非表示にする方法

create-react-app にて、GitHubにコミットできるようAPIキーを非表示にする方法を簡単にまとめておきます。 ①プロジェクトのディレクトリのルートに .env ファイルを作成 ② .envファイル内で、選択したAPIキー名にREACT_APP_を追加して割り当てる。 .env 内に、下記を記入。 React_APP_OPENWEATHERMAP_API_KEY = (ここにAPIキーを書く) ※React_APP_ こちらを書き忘れないこと。 ③ .envファイルを.gitignoreファイルに追加。 gitignoreはGitで使われる特殊なファイル名で、このファイルに書かれたファイルは上から順に処理されて、Gitのトラッキングの対象外になる。 Gitのトラッキングの対象外になるということは、Gitで管理していたディレクトリの中にあっても無視されるということ。 # api keys .env ④使用する process.envオブジェクトを介してAPIキーにアクセスできる。 const APIKEY = process.env.React_APP_OPENWEATHERMAP_API_KEY; デプロイ時は... ページをデプロイする前に、.envファイルを削除し、プラットフォームのキー管理システムを使用する。 その他注意点 ○チーム開発時の注意点 githubには上がらなくなるから、git pullしても、他の人の環境には入らない。 APIキーは、別で教えないといけない。 ○.env を作ったあとは、再起動をすること。 ( yarn start やり直すの忘れない)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js入門 Vol.4 ~外部API呼び出し編~

こんにちは! LIFULLエンジニアの吉永です。 本日も最近あまり関わらなくなったのであまりキャッチアップできていなかったフロントエンド開発技術についてインプットした内容を備忘録として記載していきます。 本記事の概要 Vue.js入門 Vol.1 ~jQueryとの対比編~ Vue.js入門 Vol.2 ~jQueryとの対比編~ Vue.js入門 Vol.3 ~基礎まとめで簡易家計簿を作る編~ 上記3記事の続きで、外部APIを呼び出して、その結果を画面に反映させるアプリケーションを作るまでを解説していきます! 本記事で利用させていただいた外部APIと注意事項について tsukumijimaさんの天気予報 API(livedoor 天気互換)を利用させていただきました。 2020年夏に突然サービスが終了してしまった「livedoor 天気」というサービスの互換APIを提供してくださっています。 この記事をお読みの方で、この記事に沿って動作確認アプリケーションを開発される方は事前に必ず、上記ページの注意事項を良く確認してから本記事の内容に沿って実装をするようにしてください。 ※tsukumijimaさん 便利なAPIを無料で公開していただき誠にありがとうございます。 作成するアプリケーションの仕様 天気予報情報を外部APIから取得して画面に表示させる。 天気予報を取得する都道府県、地域を選択して取得ボタンを押すと外部APIを呼び出して取得する。 天気予報を取得する都道府県をプルダウンメニューから選択したら地域プルダウンメニューの内容を都道府県配下の地域に置き換え、未選択状態にする。 取得ボタンは都道府県、地域が未選択中には押せないようにする。 取得ボタンは外部API呼び出し中はローディング表示を行い、取得が終わるまではボタンを押せないようにする。 外部API呼び出しに失敗した場合はエラーメッセージを画面に表示させる。 画面仕様 初期表示 天気予報表示時 エラー発生時 天気予報取得アプリ~完成版デモ~ See the Pen 天気予報取得アプリ~完成版デモ~ by Yuta Yoshinaga (@yuta-yoshinaga) on CodePen. この後の解説部分では各ソースコードをパーツごとに表記しているので、ソースコードの全文を見たい方は上記codepenへのリンクから参照してください。 天気予報取得アプリ実装 入力フォーム <h1 class="title">天気予報</h1> <div class="columns"> <div class="column"> <label class="label">都道府県</label> <select v-model="curPref" @change="prefChange"> <option v-for="pref in prefs">{{ pref.name }}</option> </select> </div> <div class="column"> <label class="label">地域</label> <select v-model="curCity"> <option v-for="city in citys" :value="city.id">{{ city.name }}</option> </select> </div> <div class="column"> <button :class="btnClass" @click="getWeather" :disabled="canSendBtn">取得</button> </div> </div> 入力フォームではセレクトボックスを二つとボタンを一つ用意します。 ※@clickはv-onディレクティブの省略形の書き方で、:valueはv-bindディレクティブの省略形です。 エラーメッセージ表示部分 <div v-if="hasError"> <article class="message is-danger"> <div class="message-header"> <p>Error</p> </div> <div class="message-body"> {{ errorMessage }} </div> </article> </div> v-ifディレクティブを使ってエラーがあった際にのみ有効になる部分です。 天気予報表示部分 <table class="table is-bordered is-striped" v-if="curWether"> <tr> <td>予報の発表日時</td> <td>{{ curWether.publicTimeFormatted }}</td> </tr> <tr> <td>予報を発表した気象台</td> <td>{{ curWether.publishingOffice }}</td> </tr> <tr> <td>タイトル・見出し</td> <td>{{ curWether.title }}</td> </tr> <tr> <td>リクエストされたデータの地域に該当する気象庁 HP の天気予報の URL</td> <td><a :href="curWether.link">{{ curWether.link }}</a></td> </tr> <tr> <td>天気概況文</td> <td> <table class="table is-bordered is-striped"> <tr> <td>天気概況文の発表時刻</td> <td>{{ curWether.description.publicTimeFormatted }}</td> </tr> <tr> <td>天気概況文</td> <td>{{ curWether.description.text }}</td> </tr> </table> </td> </tr> <tr> <td>都道府県天気予報の予報日毎の配列</td> <td> <table class="table is-bordered is-striped" v-for="forecast in curWether.forecasts"> <tr> <td>予報日</td> <td>{{ forecast.date }}</td> </tr> <tr> <td>予報日(今日・明日・明後日のいずれか)</td> <td>{{ forecast.dateLabel }}</td> </tr> <tr> <td>天気(晴れ、曇り、雨など)</td> <td>{{ forecast.telop }}</td> </tr> <tr> <td>天気詳細</td> <td> <table class="table is-bordered is-striped"> <tr> <td>詳細な天気情報</td> <td>{{ forecast.detail.weather }}</td> </tr> <tr> <td>風の強さ</td> <td>{{ forecast.detail.wind }}</td> </tr> <tr> <td>波の高さ(海に面している地域のみ)</td> <td>{{ forecast.detail.wave }}</td> </tr> </table> </td> </tr> <tr> <td>最高気温</td> <td> <table class="table is-bordered is-striped"> <tr> <td>摂氏 (°C)</td> <td>{{ forecast.temperature.max.celsius }}</td> </tr> <tr> <td>華氏 (°F)</td> <td>{{ forecast.temperature.max.fahrenheit }}</td> </tr> </table> </td> </tr> <tr> <td>最低気温</td> <td> <table class="table is-bordered is-striped"> <tr> <td>摂氏 (°C)</td> <td>{{ forecast.temperature.min.celsius }}</td> </tr> <tr> <td>華氏 (°F)</td> <td>{{ forecast.temperature.min.fahrenheit }}</td> </tr> </table> </td> </tr> <tr> <td>降水確率</td> <td> <table class="table is-bordered is-striped"> <tr> <td>0 時から 6 時までの降水確率</td> <td>{{ forecast.chanceOfRain.T00_06 }}</td> </tr> <tr> <td>6 時から 12 時までの降水確率</td> <td>{{ forecast.chanceOfRain.T06_12 }}</td> </tr> <tr> <td>12 時から 18 時までの降水確率</td> <td>{{ forecast.chanceOfRain.T12_18 }}</td> </tr> <tr> <td>18 時から 24 時までの降水確率</td> <td>{{ forecast.chanceOfRain.T18_24 }}</td> </tr> </table> </td> </tr> <tr> <td>天気アイコン</td> <td> <img :src="forecast.image.url" :alt="forecast.image.title" :width="forecast.image.width" :height="forecast.image.height"> </td> </tr> </table> </td> </tr> <tr> <td>予報を発表した地域を定義</td> <td> <table class="table is-bordered is-striped"> <tr> <td>地方名</td> <td>{{ curWether.location.area }}</td> </tr> <tr> <td>都道府県名</td> <td>{{ curWether.location.prefecture }}</td> </tr> <tr> <td>一次細分区域名</td> <td>{{ curWether.location.district }}</td> </tr> <tr> <td>地域名(気象観測所名)</td> <td>{{ curWether.location.city }}</td> </tr> </table> </td> </tr> <tr> <td>copyright</td> <td> <table class="table is-bordered is-striped"> <tr> <td>コピーライトの文言</td> <td>{{ curWether.copyright.title }}</td> </tr> <tr> <td>天気予報 API(livedoor 天気互換)の URL</td> <td><a :href="curWether.copyright.link">{{ curWether.copyright.link }}</a></td> </tr> <tr> <td>天気予報 API(livedoor 天気互換)のアイコン</td> <td><img :src="curWether.copyright.image.url" :alt="curWether.copyright.image.title" :width="curWether.copyright.image.width" :height="curWether.copyright.image.height"> </td> </tr> <tr> <td>天気予報 API(livedoor 天気互換)で使用している気象データの配信元(気象庁)</td> <td> <table> <tr> <td>link</td> <td><a :href="curWether.copyright.provider[0].link">{{ curWether.copyright.provider[0].link }}</a> </td> </tr> <tr> <td>name</td> <td>{{ curWether.copyright.provider[0].name }}</td> </tr> <tr> <td>note</td> <td>{{ curWether.copyright.provider[0].note }}</td> </tr> </table> </td> </tr> </table> </td> </tr> </table> 天気予報 API(livedoor 天気互換)のレスポンスフィールドの内容を参考に、APIから取得したデータを画面上に表示させるための部分になります。 都道府県データJS const infoTbl = { prefs: [ { name: "北海道", citys: [ { name: "稚内", id: "011000" }, { name: "旭川", id: "012010" }, { name: "留萌", id: "012020" }, { name: "網走", id: "013010" }, { name: "北見", id: "013020" }, { name: "紋別", id: "013030" }, { name: "根室", id: "014010" }, { name: "釧路", id: "014020" }, { name: "帯広", id: "014030" }, { name: "室蘭", id: "015010" }, { name: "浦河", id: "015020" }, { name: "札幌", id: "016010" }, { name: "岩見沢", id: "016020" }, { name: "倶知安", id: "016030" }, { name: "函館", id: "017010" }, { name: "江差", id: "017020" }, ] }, { name: "青森県", citys: [ { name: "青森", id: "020010" }, { name: "むつ", id: "020020" }, { name: "八戸", id: "020030" }, ] }, { name: "岩手県", citys: [ { name: "盛岡", id: "030010" }, { name: "宮古", id: "030020" }, { name: "大船渡", id: "030030" }, ] }, { name: "宮城県", citys: [ { name: "仙台", id: "040010" }, { name: "白石", id: "040020" }, ] }, { name: "秋田県", citys: [ { name: "秋田", id: "050010" }, { name: "横手", id: "050020" }, ] }, { name: "山形県", citys: [ { name: "山形", id: "060010" }, { name: "米沢", id: "060020" }, { name: "酒田", id: "060030" }, { name: "新庄", id: "060040" }, ] }, { name: "福島県", citys: [ { name: "福島", id: "070010" }, { name: "小名浜", id: "070020" }, { name: "若松", id: "070030" }, ] }, { name: "茨城県", citys: [ { name: "水戸", id: "080010" }, { name: "土浦", id: "080020" }, ] }, { name: "栃木県", citys: [ { name: "宇都宮", id: "090010" }, { name: "大田原", id: "090020" }, ] }, { name: "群馬県", citys: [ { name: "前橋", id: "100010" }, { name: "みなかみ", id: "100020" }, ] }, { name: "埼玉県", citys: [ { name: "さいたま", id: "110010" }, { name: "熊谷", id: "110020" }, { name: "秩父", id: "110030" }, ] }, { name: "千葉県", citys: [ { name: "千葉", id: "120010" }, { name: "銚子", id: "120020" }, { name: "館山", id: "120030" }, ] }, { name: "東京都", citys: [ { name: "東京", id: "130010" }, { name: "大島", id: "130020" }, { name: "八丈島", id: "130030" }, { name: "父島", id: "130040" }, ] }, { name: "神奈川県", citys: [ { name: "横浜", id: "140010" }, { name: "小田原", id: "140020" }, ] }, { name: "新潟県", citys: [ { name: "新潟", id: "150010" }, { name: "長岡", id: "150020" }, { name: "高田", id: "150030" }, { name: "相川", id: "150040" }, ] }, { name: "富山県", citys: [ { name: "富山", id: "160010" }, { name: "伏木", id: "160020" }, ] }, { name: "石川県", citys: [ { name: "金沢", id: "170010" }, { name: "輪島", id: "170020" }, ] }, { name: "福井県", citys: [ { name: "福井", id: "180010" }, { name: "敦賀", id: "180020" }, ] }, { name: "山梨県", citys: [ { name: "甲府", id: "190010" }, { name: "河口湖", id: "190020" }, ] }, { name: "長野県", citys: [ { name: "長野", id: "200010" }, { name: "松本", id: "200020" }, { name: "飯田", id: "200030" }, ] }, { name: "岐阜県", citys: [ { name: "岐阜", id: "210010" }, { name: "高山", id: "210020" }, ] }, { name: "静岡県", citys: [ { name: "静岡", id: "220010" }, { name: "網代", id: "220020" }, { name: "三島", id: "220030" }, { name: "浜松", id: "220040" }, ] }, { name: "愛知県", citys: [ { name: "名古屋", id: "230010" }, { name: "豊橋", id: "230020" }, ] }, { name: "三重県", citys: [ { name: "津", id: "240010" }, { name: "尾鷲", id: "240020" }, ] }, { name: "滋賀県", citys: [ { name: "大津", id: "250010" }, { name: "彦根", id: "250020" }, ] }, { name: "京都府", citys: [ { name: "京都", id: "260010" }, { name: "舞鶴", id: "260020" }, ] }, { name: "大阪府", citys: [ { name: "大阪", id: "270000" }, ] }, { name: "兵庫県", citys: [ { name: "神戸", id: "280010" }, { name: "豊岡", id: "280020" }, ] }, { name: "奈良県", citys: [ { name: "奈良", id: "290010" }, { name: "風屋", id: "290020" }, ] }, { name: "和歌山県", citys: [ { name: "和歌山", id: "300010" }, { name: "潮岬", id: "300020" }, ] }, { name: "鳥取県", citys: [ { name: "鳥取", id: "310010" }, { name: "米子", id: "310020" }, ] }, { name: "島根県", citys: [ { name: "松江", id: "320010" }, { name: "浜田", id: "320020" }, { name: "西郷", id: "320030" }, ] }, { name: "岡山県", citys: [ { name: "岡山", id: "330010" }, { name: "津山", id: "330020" }, ] }, { name: "広島県", citys: [ { name: "広島", id: "340010" }, { name: "庄原", id: "340020" }, ] }, { name: "山口県", citys: [ { name: "下関", id: "350010" }, { name: "山口", id: "350020" }, { name: "柳井", id: "350030" }, { name: "萩", id: "350040" }, ] }, { name: "徳島県", citys: [ { name: "徳島", id: "360010" }, { name: "日和佐", id: "360020" }, ] }, { name: "香川県", citys: [ { name: "高松", id: "370000" }, ] }, { name: "愛媛県", citys: [ { name: "松山", id: "380010" }, { name: "新居浜", id: "380020" }, { name: "宇和島", id: "380030" }, ] }, { name: "高知県", citys: [ { name: "高知", id: "390010" }, { name: "室戸岬", id: "390020" }, { name: "清水", id: "390030" }, ] }, { name: "福岡県", citys: [ { name: "福岡", id: "400010" }, { name: "八幡", id: "400020" }, { name: "飯塚", id: "400030" }, { name: "久留米", id: "400040" }, ] }, { name: "佐賀県", citys: [ { name: "佐賀", id: "410010" }, { name: "伊万里", id: "410020" }, ] }, { name: "長崎県", citys: [ { name: "長崎", id: "420010" }, { name: "佐世保", id: "420020" }, { name: "厳原", id: "420030" }, { name: "福江", id: "420040" }, ] }, { name: "熊本県", citys: [ { name: "熊本", id: "430010" }, { name: "阿蘇乙姫", id: "430020" }, { name: "牛深", id: "430030" }, { name: "人吉", id: "430040" }, ] }, { name: "大分県", citys: [ { name: "大分", id: "440010" }, { name: "中津", id: "440020" }, { name: "日田", id: "440030" }, { name: "佐伯", id: "440040" }, ] }, { name: "宮崎県", citys: [ { name: "宮崎", id: "450010" }, { name: "延岡", id: "450020" }, { name: "都城", id: "450030" }, { name: "高千穂", id: "450040" }, ] }, { name: "鹿児島県", citys: [ { name: "鹿児島", id: "460010" }, { name: "鹿屋", id: "460020" }, { name: "種子島", id: "460030" }, { name: "名瀬", id: "460040" }, ] }, { name: "沖縄県", citys: [ { name: "那覇", id: "471010" }, { name: "名護", id: "471020" }, { name: "久米島", id: "471030" }, { name: "南大東", id: "472000" }, { name: "宮古島", id: "473000" }, { name: "石垣島", id: "474010" }, { name: "与那国島", id: "474020" }, ] }, ] }; 本当はこちらも外部から取得したかったのですが、CORSの制限でブラウザJSからは取得できなかったのでひとまずJS上でデータを持つようにしました。 参照させてもらったデータはこちらです。 メイン処理JS const app = new Vue({ el: '#app', data: { prefs: infoTbl.prefs, citys: null, curPref: null, curCity: null, curWether: null, hasError: false, errorMessage: "", loading: false, }, computed: { btnClass: function () { return { button: true, 'is-primary': true, 'is-loading': this.loading }; }, canSendBtn: function () { return this.curPref && this.curCity && !this.loading ? false : true; } }, methods: { prefChange: function () { let pref = this.prefs.filter(pref => pref.name === this.curPref); if (pref.length != 0) { this.citys = pref[0].citys; this.curCity = null; } }, getWeather: function () { this.hasError = false; this.errorMessage = ""; this.loading = true; this.curWether = null; axios.get('https://weather.tsukumijima.net/api/forecast/city/' + this.curCity) .then(function (response) { if (response.data) { if (response.data.error) { this.hasError = true; this.errorMessage = response.data.error; } else { this.curWether = response.data; } } }.bind(this)) .catch(function (error) { this.hasError = true; this.errorMessage = error; }.bind(this)) .finally(function () { this.loading = false; }.bind(this)) } } }) ひとまず全体像です。 各詳細を以降で解説していきます。 computed 算出プロパティと呼ばれるもので、メソッドと違って関数内部で依存している値が変化したら自動的にキャッシュに反映してくれて、変化がなければキャッシュから値を取得するようにできます。 btnClass: function () { return { button: true, 'is-primary': true, 'is-loading': this.loading }; }, こちらの処理は取得ボタンのclass属性を返却する算出プロパティなのですが、this.loadingというプロパティがtrueならis-loadingというクラスが取得ボタンに追加されるようになっています。 このようにしておくことで、取得ボタンを押してから、this.loadingがfalseになるまではボタンの見た目をローディング表示に変えることが可能です。 canSendBtn: function () { return this.curPref && this.curCity && !this.loading ? false : true; } こちらは取得ボタンを押せるようにするかしないかを返却する算出プロパティです。 this.curPrefには都道府県選択プルダウンメニューで現在選択中の値が、this.curCityには地域選択プルダウンメニューで現在選択中の値が入るようになっており、未選択状態であればボタンをdisabledにして押せなくするようにしています。 今回はそれらに加えて、ローディング表示中も同様にボタンを押せなくしており、こうすることでAPIからの呼び出しが完了するまでの間はボタンを押せないので2重送信をできないようにすることができます。 methods prefChange: function () { let pref = this.prefs.filter(pref => pref.name === this.curPref); if (pref.length != 0) { this.citys = pref[0].citys; this.curCity = null; } }, こちらは都道府県選択プルダウンメニューで選択値が変化した際に呼ばれるメソッドです。 this.curPrefには現在選択中の都道府県の名称が入っているので、this.prefsというマスタデータ内を検索して、一致したデータから子要素の地域情報を取得してthis.citysに代入しています。 this.citysはHTML側ではv-forディレクティブで参照されており、内容が変わると自動的にHTML側にも反映されます。 最後にthis.curCityをnullにすることで、地域選択プルダウンメニューを未選択上に初期化しています。 getWeather: function () { this.hasError = false; this.errorMessage = ""; this.loading = true; this.curWether = null; axios.get('https://weather.tsukumijima.net/api/forecast/city/' + this.curCity) .then(function (response) { if (response.data) { if (response.data.error) { this.hasError = true; this.errorMessage = response.data.error; } else { this.curWether = response.data; } } }.bind(this)) .catch(function (error) { this.hasError = true; this.errorMessage = error; }.bind(this)) .finally(function () { this.loading = false; }.bind(this)) } 最後に外部API呼び出し部分です。 このメソッドは取得ボタンクリック時に呼ばれていて、API呼び出し前にまずは各種ステータスや変数を初期化しています。 ここでthis.loadingをtrueにすることで、算出プロパティが更新されて画面にも反映されるようになります。 外部取得用の処理にはaxiosというブラウザやnode.jsで動くPromiseベースの HTTPクライアントを使用しています。 jQueryで良く使用されていた$.ajaxと似たようなものですが、最近ではこちらを利用するのが主流のようです。 通信完了後はエラーがあればcatchへ、通信上ではエラーがなければthenの中の処理が実行されます。 ただし、thenにはHTTPのレスポンスコードが2xx系などの時に来るようになっているので、リクエストパラメーターにエラーがあった際などには外部APIの仕様次第ですが、レスポンスコードは2xx系で返却し、エラーメッセージをレスポンスデータに格納しているパターンもあります。 天気予報 API(livedoor 天気互換)はレスポンスコードは2xx系でもエラーメッセージが格納されていることもあるので、thenの中でもエラーがないかを確認してから取得したデータをVueオブジェクトのプロパティへ代入するようにしています。 この辺りは呼び出し対象のAPIの仕様書を読むか、動作確認を行ってわざとエラーを発生させてみてなどの挙動を確認しておくと良いと思います。 最後に いかがでしたでしょうか? 今回はJSから外部APIを呼び出してVue.jsで利用する実装例をご紹介しました。 jQueryでは重宝していたajaxの代用になるaxiosというライブラリがあることも知れたのと、利用方法はajaxとそんなに変わっていないのですんなりと処理を記述できたことがjQueryでの開発経験が少し役に立ったようで良かったです。 あとは登録不要で簡単に利用できるtsukumijimaさんの天気予報 API(livedoor 天気互換)には本当に感謝です!おかげで学習がはかどりました! 皆さんも外部API呼び出しの学習用やテスト用に使えるAPIなどを是非ご自身で調べてみてください。 ※利用規約などは事前にきちんと確認しましょう! それではまた次の記事でお会いしましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】クリックした箇所に移動するアニメーションサンプル【requestAnimationFrame】

クリックした箇所に要素が移動するサンプルです。 requestAnimationFrameを使用しています。 リアルタイム通信ができるデータベースと連携させると、バーチャルオフィスのようなアプリも作成可能です。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> body { height: 100vh; position: relative; } #avatar { width: 100px; height: 100px; background-color: lightcoral; position: absolute; top: 100px; left: 100px; } </style> </head> <body> <div id="avatar"></div> <script> const field = document.querySelector("body"); const avatar = document.querySelector("#avatar"); let id; field.addEventListener("click", (e) => { let lastTime = null; const clickX = e.clientX; const clickY = e.clientY; const box = avatar.getBoundingClientRect(); let boxX = box.left; let boxY = box.top; // 経過時間の割合分だけ、CSSを変更する function move(progress) { boxX += ((clickX - boxX) / 200) * progress; boxY += ((clickY - boxY) / 200) * progress; avatar.style.left = boxX + "px"; avatar.style.top = boxY + "px"; cancelAnimationFrame(id); } // コールバックが発生したときに、タイムスタンプを渡される function step(timestamp) { if (!lastTime) lastTime = timestamp; const progress = timestamp - lastTime; move(progress); id = window.requestAnimationFrame(step); lastTime = timestamp; } // 繰り返し実行したい関数を渡す window.requestAnimationFrame(step); }); </script> </body> </html> GitHubにもサンプルコードを置いています Web会議ツール作成しました! こちらのrequestAnimationFrameを利用して、Web会議ツールを作成しました! FirebaseのFirestoreに位置の情報を保存することにより、各ブラウザ間でリアルタイムにアイコンの位置を同期しています。 デモ用のコメント流しすぎました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インタープリタを作る その15

概要 インタープリタを作ってみた。 avrインタープリター書いてみた。 参考にしたライブラリ 投入したソース avr.mov(31, 31) 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQuery イベントオブジェクトについて

記事の内容 jQueryライブラリのイベントオブジェクトについてメモ程度にまとめる。 (参考:jQuery本格入門 著:沖林正紀 出版:技術評論社) イベントオブジェクトとは JavaScriptのイベントオブジェクトにいくつかメソッドやプロパティを加えて、Webブラウザごとの違いを吸収したもの。 $.Eventによって生成される。イベント処理が行われる関数の第一引数に渡される。 マウスカーソルの位置 マウスカーソルを表すイベントプロパティは以下の表のとおりである。 x y ブラウザ画面内の位置 clientX clientY Webページ内の位置 pageX pageY 要素内での位置 offsetX offsetY デスクトップ画面内の位置 screenX screenY イベント発火に関連した要素 このプロパティで参照される要素はすべてDOMオブジェクト。 イベント発火した要素と、イベントを処理した要素が違う場合、そのイベントがバブリングしたとわかる。 target イベント発火した要素 currentTarget $(...)で指定された要素のうち、イベントが発火した要素を含むもの。 relatedTarget ・mouseoverイベントの場合 イベント発火直前にマウスカーソルが重なっていた要素。 ・mouseoutイベントの場合 イベント発火時点で重なっている要素 toElement マウス移動後の要素 delegateTarget delegateまたはonを実行した$(...)で指定された要素のうち、 イベントが発火した要素を含むもの。 イベント処理の中止 preventDefault() イベント処理の最後にreturn falseを実行したときのように、フォームの送信や、ページの遷移などを行わない。 stopImmediateProbagation 同じイベントに対応するイベント処理が複数登録されていても、その後のイベント処理がされない。 stopProbagation バブリングが起こらなくなる。同じ要素で発火した特定のイベントに対応するイベントは実行される。 イベント処理にデータを受け渡す。 イベントオブジェクトをイベント処理を行う関数とは別に、引数としてオブジェクトを設定すると、イベントオブジェクトのdataプロパティの値を参照することができる。 delegateTargetプロパティ $(...)で設定した要素のうち、子孫要素にイベントを発火した要素を含むものを表す。 Deferredオブジェクトによる処理の実行 Deferredオブジェクトとは オブジェクトが持つ状態を別の状態に変化させるときに処理が実行されるように設定できる。 イベント処理はイベント発火前に対応する処理が登録されていなければ、その処理を実行することができないが、Deferredオブジェクトは状態が変化した後で、対応する処理を登録しても、実行される。 $.Deferred([関数])で生成される。関数で生成したDeferredオブジェクトに処理を追加できる。 状態と処理キューを持っていて、 オブジェクト生成時には、「保留(pending)」の状態にある。 この状態でも、処理を登録することができる。 「保留(pending)」→「解決済み(resolved)」| 「棄却済み(rejected)」と状態が変化し、状態に応じて、キューに登録されている処理が実行される。遷移した状態を元に戻すことはできない。 処理を登録するメソッド done 状態が解放済みに遷移した後に実行される処理を登録。 fail 状態が棄却済みに遷移した後に実行される処理を登録。 progress 状態が遷移する前に実行される処理を登録。 それぞれをメソッドチェーンによってつなげて記述することができる。 Deferredオブジェクトの状態の遷移させるメソッドは resolve→解決済み reject→棄却済み progress→保留状態のまま またnotifyメソッドを実行しても、状態はpendingのままなので、progressで登録された関数は何度でも実行することができる。 一方で解決済み、棄却済みに移行したオブジェクトの状態は戻せないため、done、failで実行した処理は一度しか実行することができない。 Deferredオブジェクトのメソッドは、複数の引数が設定されると、すべてdone、fail、progressで登録した関数の引数に引き継がれる。 複数の処理を同時に設定することもでき、メソッドに複数の引数を設定する、メソッドチェーンを用いる、引数に配列を設定するという方法がある。 実行する順序は、引数の並び、登録順、配列の要素順となっている。 状態を遷移させるメソッドの中で、 xxxWithメソッドの第一引数を設定した引数は、処理中のthisキーワードとなる。 現在の状態を表示する →deferred.state()メソッドを使用。 ・その他の登録方法 always(関数[, 関数,...]) →状態がresolved/rejectedどちらに移動しても、同じ関数を実行する。 then(関数1 [, 関数2[, 関数3]]) →1:doneで登録した関数の前に実行するもの、 →2:failで登録した関数の前に実行するもの →3:notifyで登録した関数の前に実行するものを登録。 中間処理を設定することができる。 Promiseオブジェクト 状態を遷移させる処理を先に行い、状態に応じた処理の登録を別の処理に委ねるために用意されたオブジェクト。 Deferredオブジェクトから、状態を遷移させるメソッドresolve/reject/notifyを除いたサブセット。 状態と処理キューはそのまま引き継いでいる。 これはアニメーションの動作時や、Ajaxの通信が終了した後に行う処理を設定するときにも利用する。 アニメーションでは、最後の処理が終了すると、Deferredオブジェクトの状態が、resolvedに、 途中で処理が中断されると、rejectedに遷移する。 show/hideメソッドのオプションでしてされるdone,fail,always,progressには、Promissオブジェクトのメソッドと同じ意味でつかわれる。 promise([オブジェクト]) →Promiseオブジェクトを生成する。引数にオブジェクトがあると、そのオブジェクトにPromiseオブジェクトのメソッドを追加する。 $.when(関数[,関数, ...]) →Promiseオブジェクトを返す処理を登録、すべての関数の処理が終わるまで待機する。 thenメソッドを実行した後では、実は、resolveメソッドを実行することができない。それはthenメソッドの戻り値がpromiseオブジェクトであり、resolveメソッドをもっていないからである。 PromiseオブジェクトはjQueryオブジェクトによる処理が終了するのを待ってから、次の処理を行いたいときに利用することができる。 これによって、 ・HTML文書に追加した要素にテキストを補う ・アニメーションが終了してから次の処理を実行する。 などが実現できる。 流れとしては、 ・promiseメソッドでPromiseオブジェクトを生成。 ・doneでその後に行いたい処理を登録する。 //新しく追加した<div>にテキストを補う $('<div>').appendTo(document.body).promise() .done(function(){this.text('jQuery'); }); //アニメーション終了後に処理を実行 $('div').slideUp(3000).promise() .done(function(){alert.'要素が隠れました'); }); promiseオブジェクトはアニメーションの終了について管理しているので、複数のオブジェクトがランダムに動く場合などでも、すべてが終了するのを待ってから、その後の処理を一度だけ行うことができる。 $.whenメソッド このメソッドは 一つ、または複数のオブジェクトを引数にとって、 戻り値はPrimiseオブジェクトを返す。 引数のすべてがDeferredオブジェクト、Promiseオブジェクトでない場合、whenに続く、doneによって登録された処理が実行される。 var d = new Date(); $.when(d.getHours(), d.getMinutes()).done(function(h, m){ $('div'), {text: h + '時' + m + '分です'}).appendTo(document.body); }); // <div>15時10分です</div> 以上のような例のように、引数のオブジェクトが、doneで登録される関数の引数に設定されている。 $.each(['red', 'blue'], function(i, name){ $('div'), {width:100, height: 100, css: {backgroundColor: name}} .appendTo(document.body).hide(); }); // 追加した要素を表示させ、両方表示されたらテキストを追加 $.when($('div:first').show(3000), $('div:last').show(300)) .done(function(){$('div').text('jQuery'); }); $.whenの引数がDeferredオブジェクト、Promiseオブジェクトのときは、それぞれの状態がすべてresolvedに移行していれば、whenに続くdoneで登録された処理が実行される。一方で、引数の中で、どれか一つでもrejectedに移行した場合は、failで登録された処理が実行される。 progressメソッドも続けて実行でき、登録された関数の引数には、 whenメソッドの引数に設定された、Deferredオブジェクト、Promiseオブジェクトが notifyを実行したときの引数が 関数の引数に設定される。 元のオブジェクトの引数が複数の場合、配列で一つの引数として渡される。 Deferredオブジェクトに隠しdone/failの追加 $.Deferredの引数に、 生成されたDeferredオブジェクトを引数とする関数を 設定し、そこで引数の関数の引数にdoneやfailを実行すると、 元の生成されたDeferredオブジェクトに処理を追加済みにすることができる。 $.Callbacks $.CallbacksはCallbacksオブジェクトを生成するメソッド。 Callbacksオブジェクト 先にコールバック関数を登録しておき、それを引数を変えつつ何度も実行したり、 先に引数を登録しておいて、それらを用いて、コールバック関数を実行したりすることができる 仕組みを持ったオブジェクト。 Deferredオブジェクトの処理キューなどに利用されている。 Callbacksオブジェクトのメソッドは、返り値がBooleanでないものなら、メソッドチェーンによって連続に実行できる。 var fn = function(){ var s = $.map(arguments, function(arg){return arg.toString(); }).join(); $('<div>', {text: s}).appendTo(document.body); }; var callbacks = $.Callbacks(); //Callbacksオブジェクト生成 callbacks.add(fn); // 関数の登録 // 引数を変えて登録した関数を実行。 callbacks.fire('A','B','C'); //ABC callbacks.fire('D','E','F'); //DEF callbacks.add(fn1, fn2); //複数の引数で登録 callbacks.add([fn3, fn4]); //配列で登録 callbacks.remove(fn1) //関数の削除 callbacks.remove(fn2, fn3, fn4); //複数の関数の削除 Callbacksオブジェクト生成時のメソッドの引数としてフラグ名を文字列で渡すことで、 生成するCallbacksオブジェクトにフラグを設定することができる。 var callbacks = $.Callbacks('once'); var callbacks = $.Callbacks('once memory') //空白で列挙できる。 フラグは以下のとおりである。 once fireの実行完了後、登録された関数は削除される。 unique 同じ関数は登録されない。 stopOnFalse 戻り値がFalseの場合、それ以降関数を実行しない。 memory 登録された関数や、実行時の引数をオブジェクトに記憶する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】ドメイン名からIPアドレスを取得する

3000文字Tips - 知ると便利なTipsをみんなへ届けよう の参加記事です。 タイトル通りです。 同じようなことはこのサイトでも可能です。 しかし、IP制限されているサーバーの場合、こういった外部サイトでは取得できません。 そのため、手元で試す方法を調べました。 環境 $ node -v v13.11.0 $ npm -v dns 6.13.7 結論 ドメイン名→IPアドレス (v4) const dns = require("dns"); const dnsPromises = dns.promises; dnsPromises.lookup("google.com").then(({ address, family }) => { console.log("address: %j family: IPv%s", address, family); // address: "172.217.25.110" family: IPv4 }); ドメイン名→IPアドレス (v6) const dns = require("dns"); const dnsPromises = dns.promises; const options = { family: 6, hints: dns.ADDRCONFIG | dns.V4MAPPED, }; dnsPromises.lookup("google.com", options).then((result) => { console.log("address: %j family: IPv%s", result.address, result.family); // address: "::ffff:216.58.220.142" family: IPv6 }); 参考 DNS | Node.js v16.3.0 Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Zoomをブラウザで開くmarklet

Zoomをブラウザで開く ブックマークレット javascript:const meetingId = window.prompt("ミーティングIDを入力してください", "");window.location.href='https://zoom.us/wc/join/'+meetingId;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペで最速で試すTwilioビデオ通話の実装 #twilio_online_contest_2021

Twilioのエルは一つ、今回のハッカソンで覚えたことです。 もはやどれが正解か分からないのがコンテンツ ということでチェック事項が多いTwillllllioですが、APIトークンやシークレットキーなども複数あって中々概要掴むのが大変でした。 【勝手企画】Twilioオンラインコンテストに応募しちゃおうハッカソン ビデオ映像のみのサンプルでまずは動かしたい ハッカソン参加者が、モジュールのインストールなどでつまづいたりをみていると、ミニマムなサンプルがやはり欲しいなと思う今日この頃...... とりあえずコピペかつフロントエンドだけで試せる実装をしてみました。 トークンをコードに直書きしてたりするので、 この実装は実用性を考えるとオススメできないですが、まず動く、大枠を掴むって意味合いだと良いと思います。 また、公式が推奨してるとかではないので悪しからず。 Twilio WebRTCハンズオン(ホワイトボード編)のコードから機能をバッサリ削除してます。 マイク利用 発話検知 ホワイトボード機能 などを排除して、カメラのみで1対1(2人)がアクセスしたときに映像を送りあえる部分だけの実装になります。なのでビデオ通話と書きましたが若干釣りですね。 コピペで最速で試す 前提として、Twilioアカウントは取得しておいてください。 1. トークン取得 トークンも色々あって初見だと手強いんですよね。。という感じでこの手順だと試しやすいかなと思っています。 1-1. サブアカウント作成 初めての場合はサブアカウント作成をします。 Dashboard > 設定 > サブアカウント でサブアカウントを作成します。 参考: Twilio WebRTCハンズオン(ホワイトボード編) 1-2. 管理画面からアクセストークンを2つ発行 テスティングツールでアクセストークンを発行できます。 ここで2回アクセストークンを発行します。 クレデンシャルアイデンティティ: 適当な文字列 Room名を選択する: 空 でOKです。 2. コピペコード全文 今回はVue.jsを利用しています。 PCの適当なフォルダにindex.htmlとscript.jsを作成しましょう。 index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Camera Test</title> <style> #video-zone{ display: flex; justify-content: space-around; } #video-zone video{ width: 300px; } </style> </head> <body> <h1>Twilio WebRTCビデオ通話テスト (1対1 && Webカメラのみ)</h1> <div id="app"> <div id="room-controls"> <button id="button-join" @click="onclickA">Aさんとして入室</button> <button id="button-join" @click="onclickB">Bさんとして入室</button> </div> <div id="video-zone"> <div id="my-video"> <video id="myStream" autoplay playsinline></video> </div> </div> </div> <script src="https://media.twiliocdn.com/sdk/js/video/releases/2.7.3/twilio-video.min.js"></script> <script src="https://unpkg.com/vue@next"></script> <script src="script.js"></script> </body> </html> 先ほど発行したアクセストークン二つをtoken1とtoken2として設定します。 script.js //Vue v3 const app = Vue.createApp({ data: () => ({ ROOM_NAME: 'VideoRoom', // 部屋の名前 Video: Twilio.Video, // Twilio Video JS SDK stream: {}, // localStream: {}, videoRoom: {}, dataTrack: {}, token1: 'トークン1', token2: 'トークン2', }), mounted: async function() { this.preview(); this.dataTrack = new this.Video.LocalDataTrack(); }, methods: { //ビデオプレビュー preview: async function(){ try { this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); document.querySelector('#myStream').srcObject = this.stream; // this.localStream = this.stream; } catch (error) { console.log(error); } }, // 部屋に入室 connectRoom: async function(token){ try { const room = await this.Video.connect(token, { name: this.ROOM_NAME, }); console.log(`Connected to Room ${room.name}`); this.videoRoom = room; // すでに入室している参加者を表示 room.participants.forEach(this.participantConnected); // 誰かが入室してきたときの処理 room.on("participantConnected", this.participantConnected); } catch (error) { console.log(error); } }, onclickA: function(){ console.log('A Click--'); this.connectRoom(this.token1)}, //Aとしてルームに接続 onclickB: function(){ console.log('B Click--'); this.connectRoom(this.token2)}, //Bとしてルームに接続 //他の参加者が入室した処理 participantConnected: function(participant){ console.log(`connect!`); console.log(`Participant ${participant.identity} connected'`); // 参加者を表示する const div = document.createElement("div"); div.id = participant.sid; div.classList.add("remote-video"); // 参加者のトラックが届いたとき participant.on('trackSubscribed', (track) => this.trackSubscribed(div, track)); // 参加者の画像を表示 const videoZone = document.getElementById('video-zone'); videoZone.appendChild(div); }, //サブスクライブ trackSubscribed: function(div, track){ const child = div.appendChild(track.attach()); if (track.kind === "video") { child.classList.add("video-style"); } }, }, }); app.mount('#app'); まだ余計なコード残ってるかもしれないですが一旦 3. 起動 VSCodeのライブサーバーやserveなどでローカルサーバーを起動させましょう。 僕はserveをnpx起動するのが最近は多いです。 $ npx serve -p 3000 4. 試し方 http://localhost:3000にアクセスすれば表示されます。 二つのタブでページを開き、片方でAさん、もう片方はBさんで入室しましょう。 トークンは1時間しか使えないので注意 Twilioは動的にアクセストークンを発行していて1時間に一回更新されるとのことです、1時間以内ならこのトークンで試すことが出来ますが、それ以上使っていく場合は大元のハンズオン資料をもとに実装していきましょう。 このコードに追加する場合はボタンクリックした際にアクセストークンを生成するサーバー(を作成しておいて)にアクセスしてトークン取得すると良さそうです。 また、今回二つのアクセストークンを作成しましたが、アクセストークン一つで良いのでは?と思う人もいそうです。 通常、動的にトークンを払い出す場合は、アイデンティティは同じでも、時間を加味して別のトークンが払い出されるので、同じユーザでも別のトークンになりますけど、今回は全く同じトークンですからね。 と赤い芸人さんからコメント頂きました。ありがとうございます。 同じアクセストークン同士だとうまく動いてくれない模様ですね。 まとめ こんな感じでNode.jsなども利用せずにフロントだけでTwilioのWebRTC機能を試すことが出来ました。 分解して再構築してみると理解深まりますね!色々と排除したのでホワイトボード側のデータやり取りについてはちゃんと追えてないですが苦笑 また、1時間でトークンは切れるみたいですが、使い終わったらトークンを削除しておくのが良いと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペで最速で試すTwilioビデオ通話の実装

Twilioのエルは一つ、今回のハッカソンで覚えたことです。 もはやどれが正解か分からないのがコンテンツ ということでチェック事項が多いTwillllllioですが、APIトークンやシークレットキーなども複数あって中々概要掴むのが大変でした。 【勝手企画】Twilioオンラインコンテストに応募しちゃおうハッカソン ビデオ映像のみのサンプルでまずは動かしたい ハッカソン参加者が、モジュールのインストールなどでつまづいたりをみていると、ミニマムなサンプルがやはり欲しいなと思う今日この頃...... とりあえずコピペかつフロントエンドだけで試せる実装をしてみました。 トークンをコードに直書きしてたりするので、 この実装は実用性を考えるとオススメできないですが、まず動く、大枠を掴むって意味合いだと良いと思います。 また、公式が推奨してるとかではないので悪しからず。 Twilio WebRTCハンズオン(ホワイトボード編)のコードから機能をバッサリ削除してます。 マイク利用 発話検知 ホワイトボード機能 などを排除して、カメラのみで1対1(2人)がアクセスしたときに映像を送りあえる部分だけの実装になります。なのでビデオ通話と書きましたが若干釣りですね。 コピペで最速で試す 前提として、Twilioアカウントは取得しておいてください。 1. トークン取得 トークンも色々あって初見だと手強いんですよね。。という感じでこの手順だと試しやすいかなと思っています。 1-1. サブアカウント作成 初めての場合はサブアカウント作成をします。 Dashboard > 設定 > サブアカウント でサブアカウントを作成します。 参考: Twilio WebRTCハンズオン(ホワイトボード編) 1-2. 管理画面からアクセストークンを2つ発行 テスティングツールでアクセストークンを発行できます。 ここで2回アクセストークンを発行します。 クレデンシャルアイデンティティ: 適当な文字列 Room名を選択する: 空 でOKです。 2. コピペコード(全文) 今回はVue.jsを利用しています。 PCの適当なフォルダにindex.htmlとscript.jsを作成しましょう。 index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Camera Test</title> <style> #video-zone{ display: flex; justify-content: space-around; } #video-zone video{ width: 300px; } </style> </head> <body> <h1>Twilio WebRTCビデオ通話テスト (1対1 && Webカメラのみ)</h1> <div id="app"> <div id="room-controls"> <button id="button-join" @click="onclickA">Aさんとして入室</button> <button id="button-join" @click="onclickB">Bさんとして入室</button> </div> <div id="video-zone"> <div id="my-video"> <video id="myStream" autoplay playsinline></video> </div> </div> </div> <script src="https://media.twiliocdn.com/sdk/js/video/releases/2.7.3/twilio-video.min.js"></script> <script src="https://unpkg.com/vue@next"></script> <script src="script.js"></script> </body> </html> 先ほど発行したアクセストークン二つをtoken1とtoken2として設定します。 script.js //Vue v3 const app = Vue.createApp({ data: () => ({ ROOM_NAME: 'VideoRoom', // 部屋の名前 Video: Twilio.Video, // Twilio Video JS SDK stream: {}, // localStream: {}, videoRoom: {}, dataTrack: {}, token1: 'トークン1', token2: 'トークン2', }), mounted: async function() { this.preview(); this.dataTrack = new this.Video.LocalDataTrack(); }, methods: { //ビデオプレビュー preview: async function(){ try { this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); document.querySelector('#myStream').srcObject = this.stream; // this.localStream = this.stream; } catch (error) { console.log(error); } }, // 部屋に入室 connectRoom: async function(token){ try { const room = await this.Video.connect(token, { name: this.ROOM_NAME, }); console.log(`Connected to Room ${room.name}`); this.videoRoom = room; // すでに入室している参加者を表示 room.participants.forEach(this.participantConnected); // 誰かが入室してきたときの処理 room.on("participantConnected", this.participantConnected); } catch (error) { console.log(error); } }, onclickA: function(){ console.log('A Click--'); this.connectRoom(this.token1)}, //Aとしてルームに接続 onclickB: function(){ console.log('B Click--'); this.connectRoom(this.token2)}, //Bとしてルームに接続 //他の参加者が入室した処理 participantConnected: function(participant){ console.log(`connect!`); console.log(`Participant ${participant.identity} connected'`); // 参加者を表示する const div = document.createElement("div"); div.id = participant.sid; div.classList.add("remote-video"); // 参加者のトラックが届いたとき participant.on('trackSubscribed', (track) => this.trackSubscribed(div, track)); // 参加者の画像を表示 const videoZone = document.getElementById('video-zone'); videoZone.appendChild(div); }, //サブスクライブ trackSubscribed: function(div, track){ const child = div.appendChild(track.attach()); if (track.kind === "video") { child.classList.add("video-style"); } }, }, }); app.mount('#app'); まだ余計なコード残ってるかもしれないですが一旦こんな感じにまとめてみました。 3. 起動 VSCodeのライブサーバーやserveなどでローカルサーバーを起動させましょう。 僕はserveをnpx起動するのが最近は多いです。 $ npx serve -p 3000 4. 試し方 http://localhost:3000にアクセスすれば表示されます。 二つのタブでページを開き、片方でAさん、もう片方はBさんで入室しましょう。 トークンは1時間しか使えないので注意 Twilioは動的にアクセストークンを発行していて1時間に一回更新されるとのことです、1時間以内ならこのトークンで試すことが出来ますが、それ以上使っていく場合は大元のハンズオン資料をもとに実装していきましょう。 このコードに追加する場合はボタンクリックした際にアクセストークンを生成するサーバー(を作成しておいて)にアクセスしてトークン取得すると良さそうです。 また、今回二つのアクセストークンを作成しましたが、アクセストークン一つで良いのでは?と思う人もいそうです。 通常、動的にトークンを払い出す場合は、アイデンティティは同じでも、時間を加味して別のトークンが払い出されるので、同じユーザでも別のトークンになりますけど、今回は全く同じトークンですからね。 と赤い芸人さんからコメント頂きました。ありがとうございます。 同じアクセストークン同士だとうまく動いてくれない模様ですね。 まとめ こんな感じでNode.jsなども利用せずにフロントだけでTwilioのWebRTC機能を試すことが出来ました。 分解して再構築してみると理解深まりますね!色々と排除したのでホワイトボード側のデータやり取りについてはちゃんと追えてないですが苦笑 また、1時間でトークンは切れるみたいですが、使い終わったらトークンを削除しておくのが良いと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでサブディレクトリにWebアプリを置くときの設定まとめ

利用シーン Reactでフロントを作ったとき、https://site.domainではなく、 https://site.domain/sub_dirctoryにindex.jsやindex.htmlを設置する場面。 そのままでは、大量のcontentsが404 Not Foundになります。 たくさんURLを書き換えるのは骨が折れるので、数か所の変更で済む方法はないのか? 情報源がいろいろあって分かりにくかったので、1ページにまとめました。 ※下記で./はプロジェクトルートディレクトリを指します。 package.jsonを設定 ./package.jsonに"homepage": "/sub_dirctory/"を追加します。 ./package.json { "name": "project001", "version": "0.1.0", "private": true, // -- any things // -- any things + "homepage": "/sub_dirctory/" } historyを追加 Routingを行っているjsファイルにて、Router系のJSXのpropsであるhistoryに設定を行います。 この時、ライブラリhistory/createBrowserHistoryが必要です。 historyを使っていない場合は、ここで新規にインストールします。 npmを使ってインストールする場合のコマンドです。 terminal npm install history npmではなくyarnの場合です。 terminal yarn add history 次に、該当箇所に変更を入れます。 ./src/app.js // -- any things import MiniApp_001 from 'miniapp_001'; import MiniApp_002 from 'miniapp_002'; import { Router, Route, Switch } from 'react-router-dom'; import { createBrowserHistory } from 'history'; // -- any things const customizedhistory = createBrowserHistory({ basename: '/sub_dirctory' }); // -- any things class App extends React.Component { render() { return ( // -- any things <Router history={customizedhistory}> <Switch> <Route exact path="/" component={MiniApp_001} /> <Route exact path="/test" component={MiniApp_002} /> <Route component={NotFound} /> </Switch> </Router> // -- any things ); } } export default App; 上から順に説明していくと、 まず、先ほどインストールしたhistoryからcreateBrowserHistoryをインポートします。 import {createBrowserHistory} from "history" 次に、createBrowserHistoryを使って、/subdirectoryを指定するためのオブジェクトcustomizedhistoryを用意します。 const customizedhistory = createBrowserHistory({ basename: '/sub_dirctory' }); JSXオブジェクトであるRouterのpropsに、historyがあります。このhistoryの値にcustomizedhistoryを設定します。 <Router history={customizedhistory}> この例ではRouterを用いていますが、他にBrowserRouterやHashRouter、MemoryRouter、StaticRouterがあります。いずれも同様に設定可能です。 .htaccessを設定する Apache の場合にはhtaccessの設定を必要とします。 ./public/.htaccess RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ ./index.html [QSA,L] Rewrite系構文のドキュメント RewriteEngine RewriteCond RewriteRule nginx.confを設定する nginxの場合はnginx.confに下記設定を追記する必要があります。 nginx.conf location ^~ /sub_dirctory{ alias /var/www/project001/build; try_files $uri $uri/ /sub_dirctory/index.html; } まとめ 多くのWebフレームワークでは、ドメインのルートに設置する前提で設計されています。もちろんReactも例外ではありません。デフォルトでは/に設定されています。しかし、いくつか設定すれば簡単に変更できます。 多くのWebフレームワークがDRYの原則から生まれたようなもの。これが出来るのは当たり前品質かもしれませんね。少しぐらいの誤差は吸収できるように、ロバストなコードを使えばサボれる。サボりは大切です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最新の技術記事を自動配信するSlackアプリを作って公開してみた

はじめに この度、各社のテックブログを中心に、最新技術記事を配信するSlackアプリWinkieを作って公開しました。 毎日定時に最新の技術記事がお使いのSlackワークスペースのチャンネルに配信されます。 現在Winkie FrontendtとWinkie Backendの二種類があり、それぞれフロントエンド、バックエンドの情報が配信されます。 開発チームの技術力向上に、個人の情報収集に、お使いください。 編集部からのおすすめポイントの解説もあるので、読む前の参考になります。 この記事では、当サービスWinkieがどのような技術を使って開発されているかをご紹介したいと思います。 Slackアプリの開発に関心のある方は特に必見です。 サービス構成図 以下がサービスの構成図です。 管理画面 投稿する記事の登録や、編集部からのおすすめの文章を編集する管理画面が必要になるため、その管理画面のフロントをReact+Typescriptで実装しています。基本的には記事の追加・編集・削除等の単純で軽量なものとなっているためRedux等のツールは導入していません。デザインも、とりわけ凝ったものにはしておらず、Material UIを使って実装しています。 APIサーバー 記事の登録や配信、slack情報の取得などを行うAPIサーバーはScala+PlayFrameWorkで実装しています。 DatabaseはPostgresを使用していて、Slickを使用してデータベースへのアクセスを行なっています。 アーキテクチャについてはDDD(ドメイン駆動開発)で構築しています。 デプロイ デプロイは管理画面・サーバーともにHerokuを使ってデプロイを行なっています。 また、記事配信のための定期実行もherokuで行なっています。heroku schedulerで実装 つまずいたポイント 今回このSlackアプリWinkieを開発するにあたってつまずいたポイントをいくつか列挙したいと思います。同じ問題に苦しむ方の参考になれば幸いです。 slack event api Botがアンインストールされたり、channelに招待されたりなどといったイベントが起こった際に、それにhookしてapiリクエストを飛ばしてくれる仕組みが用意されているのですが、イベントごとにリクエストURLの指定ができず、全ての受信イベントが1つのURLにアクセスします。 そのため、そのイベントがなんのイベントであるかを受け取ったサーバー側で判定する必要がありました。 Winkieのサーバーではadapter層で送られてきたJsonに型をつけたり、バリデーションを挟んだりしています。 そのためリクエストのbodyのJsonからどの型か(どのリクエストか)をその処理の前に判定しています。 Scalaにはapplicativeという概念があり、これを使って型の判定、振り分けを行いました。 やり方としては同一のEventBodyというtraitを各々が継承してその後型のパターンマッチングでどの処理を走らせるか決めています。 sealed trait EventBody final case class AppUninstalledEventBody( teamId: String, apiAppId: String, eventType: "app_uninstalled") extends EventBody final case class AppHomeOpenedEventBody( channelId: String, userId: String, eventType: "app_home_opened" ) extends EventBody def mapToEventCommand = case body: AppUninstallEventBody => // 後続する処理 case body: AppHomeOpenedEventBody => // 後続する処理 リクエストのJson形式 Slack event apiは日本語ドキュメントが用意されていて非常にわかりやすいのですが、たまにドキュメントとは異なる形でJsonが送られてくる場合がありました。ドキュメント通りのJsonを想定して実装を進めるとフォーマットが異なると怒られてしまうケースがあります。 もしおかしいなと思ったら、一度リクエストをそのままログ出力してみるなどして確認してみるといいと思います。 Block Kit Builderを活用する Block Kit Builderを使うと、送信するメッセージのプレビューを確認しながらUI構築が可能です。 画面左でぽちぽちで操作すると、そのUIを構築するのに必要なJsonが自動で画面右に生成されます。画面最左のメニューから、どんなUIテーマが用意されているかを確認することも可能ですね。 それに加えて例えば、ユーザーがセレクトボックスから何か選択した時にどういったリクエストが飛んでくるかもここから確認ができ、非常に便利です。 画面中央上にある、Action Previewのタブをクリックした上で、左側のUIプレビュー画面で実際に操作することで確認できます。 ちなみに右上のSend to Slackボタンから実際に送信できます。 終わりに いかがでしたか。外部APIを多く利用するようなサービスはそのAPIの仕様や制約に結構引っ張られてしまいがちなので、やはりそこが難しいですね。 Winkieはhttps://winkie.app/ から簡単にインストールができます。ぜひインストールしてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript オブジェクト

本日はオブジェクトを学習しましたので、そちらについて記事に残していきます。 配列と同様に、オブジェクトはデータをまとめて格納することが可能ですが、オブジェクトは異なるデータを格納することが可能です。 配列の例 let animal = ["イルカ", "キリン", "猫",]; 配列の使い方としては上記のように、変数 animal の中にそれに対応する値が格納されているイメージです。 もちろん、文字列の他にも数値、オブジェクト、その他の変数なども格納可能ですが、現在の私の学習レベルでは上記のような使い方が多いかと思います。 オブジェクトの例 let dolphin = { type: "動物", name: "イルカ" }; let giraffe = { type: "動物", name: "キリン" }; let cat = { type: "動物", name: "猫" }; オブジェクトはオブジェクト名に対して、{プロパティ名1: 値1,} という形でデータが格納されています。 オブジェクトの操作は、オブジェクト名.プロパティ名 とすることで値を呼び出すことが可能です。 let cat = { type: "動物", name: "猫" }; console.log(cat.name); //猫 また下記のように、変数にオブジェクト名とプロパティを代入しても値を呼び出すことが可能です。 let cat = { type: "動物", name: "猫" }; let name = cat.name; console.log(name); //猫
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

express-generator-typescriptで、npm start:devができないとき

express-generator-typescriptとは? express-generatorをTypeScriptで構築済みでなおかつ認証機能もついているという大変便利なものです。 GitHub npm 問題点 初期状態で、npm startやnpm start:devを実行したら、以下のようなエラーが出ます。 SyntaxError: Unexpected token } in JSON at position 573 at JSON.parse (<anonymous>) at parse (/hoge/node_modules/tsconfig/src/tsconfig.ts:195:15) at readFileSync (/hoge/node_modules/tsconfig/src/tsconfig.ts:181:10) at Object.loadSync (/hoge/node_modules/tsconfig/src/tsconfig.ts:151:18) at readConfig (/hoge/node_modules/ts-node/src/index.ts:425:18) at Object.register (/hoge/node_modules/ts-node/src/index.ts:189:18) at Object.<anonymous> (/hoge/node_modules/ts-node/src/_bin.ts:140:17) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) 対応方法 一見するとライブラリのエラーなので、どうしようももなさそうですが、問題は、tsconfig.jsonの中にあります。 不要な末尾に対するカンマがあったため、エラーが出たようです。 tsconfig.json /** 省略 **/ "paths": { "@daos/*": [ "src/daos/*" ], "@entities/*": [ "src/entities/*" ], "@shared/*": [ "src/shared/*" ], "@server": [ "src/Server" ] },←このカンマを削除する }, "include": [ "src/**/*.ts", "spec/**/*.ts" ], "exclude": [ "src/public/" ] } 最後に もうしかしたら、私の環境だけこうなっている可能性と英語力に自信がないので、Issuesに報告しようか迷っています。 しかし、この記事がきっかけで、express-generator-typescriptの利用を諦めていた人が救われたら幸いです。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryが動作しない

jQueryが動作しない事態に陥った。 原因は簡単でJavaScriptを読み込む前にjQueryを読み込まなければならなかった。 ちなみに順番はこう。 <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script type="text/javascript" src="action.js"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Seleniumを使ってe-typingを自動化した ①準備編

はじめに プログラミングアカデミーというyoutubeチャンネルの以下の動画で、seleniumという自動化ツールを使用して「寿司打」というタイピングゲームを自動化しているのを見て 「面白そう!」 と思ったので自分なりにやってみました! この動画ではみんな知ってる「寿司打」 を自動化しているのですが、僕は以前からタインピング練習で使っていた「e-typing」というタイピングゲームを自動化してみました。 Seleniumについて Seleniumとは、ブラウザー自動化を可能にし、それを支えるツール群とライブラリー群のプロジェクトです。 自動化テスト等でよく使われています。 自動でブラウザを操作する処理の流れは以下の通りです。 ブラウザを操作するコードを言語に対応するseleniumライブラリが読み取る。 ↓ seleniumライブラリが各ブラウザのDriverへ処理を伝える。 ↓ Driverがブラウザを操作する よって、使用する言語に対応するseleniumライブラリ及び使用するブラウザ用のdriverをインストールする必要があります。 準備 seleniumライブラリのインストール 今回はプログラミング言語にJavaScriptを使用した為、Seleniumライブラリのインストールはnpmを使います。 $ npm install selenium-webdriver Driverのダウンロード 今回はChromeを使用する為、ChromeDriverをダウンロードします。 以下のサイトからChromeのバージョンにあったChromedriverをダウンロードします。 ※Chromeのバージョンはブラウザの設定の「Chromeについて」 で確認できます。 ダウンロードが完了したら、chromedriverをパスの通っているディレクトリ配下に置く。 # 以下のコマンドを実行した際に出力されるディレクトリがパスの通っているディレクトリになります。 $ echo $PATH ※ 今回は/usr/local/binに設置しました。 これで準備は完了です! まとめ 今回は環境の準備を行いました!  実装編の記事ものちのち書いていきます! 公式ドキュメント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsbyでマークダウンの記事に目次を追加する

はじめに こんにちは。アイスで一番好きなのは、あずきばーです。筆者です さて、今回は記事に目次を追加したほうがSEOの評価がよくなるとからしい そんなわけで、ものは試しに追加してみました。 参考になれば幸いです! Gatsbyでマークダウンの記事に目次を追加する 以下を使って追加できます。 https://www.gatsbyjs.com/plugins/gatsby-remark-table-of-contents/ それではやっていきましょう。 1. gatsby-remark-table-of-contentsのインストール. $ npm install gatsby-remark-table-of-contents 2. 別途必要なgatsby-remark-autolink-headersのインストール. $ npm install gatsby-remark-autolink-headers 3. gatsby-config.jsを編集. gatsby.js module.exports = { plugins: [ { resolve: `gatsby-transformer-remark`, options: { plugins: [ `gatsby-remark-autolink-headers`, `gatsby-remark-table-of-contents`, ], }, }, ], }; 4. 各種設定が必要であれば調整する. 自分は以下の3点設定しました 目次 というh2は目次に含めないようにする. ナンバリングする. 目次に使用するh*はh2だけにする. gatsby-config.js module.exports = { plugins: [ { resolve: `gatsby-transformer-remark`, options: { plugins: [ `gatsby-remark-autolink-headers`, { resolve: `gatsby-remark-table-of-contents`, options: { exclude: '目次', ordered: true, toHeading: 2, }, }, ], }, }, ], }; マークダウン側をに以下を追加. 上で設定したので、 目次が表示される 下のh2の目次は目次に含まれません article.md ## 目次 ```toc # This code block gets replaced with the TOC ``` おわりに 意外と簡単にできました それでは!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript プリミティブ型とオブジェクトについて

はじめに JSのデータ型であるプリミティブ型とオブジェクトの違いについてまとめました。 僕の下手くそな図と共にお楽しみください。 データ型について JavaScript(ES)には8種類のデータ型が存在する。 そして、その8種類のデータ型はプリミティブ型とオブジェクト型の2つに分けることができる。 プリミティブ型について データ型 Boolean 真偽値 null null undefined 未定義 Number 数値 BigInt 長整数 String 文字列 Symbol シンボル 変数には値が格納される 一度作成するとその値を変更することはできない → immutable(不変) letを使って再代入はできる immutableについて 一度作成するとその値を変更することはできないけど、letを使って再代入はできるというのはどういう状況か図にまとめました let prop = 'hoge' propと言う変数に'hoge'と言う文字列を格納したとします、これのメモリ空間を図で表すと propという変数が'hoge'という文字列が格納されているアドレスに参照を保持している状態です。 そこで let prop = 'hoge' + prop = 'fuga' propの値を再代入すると、メモリ空間は 'hoge'と言う文字列と別のメモリに'fuga'と言う文字列が格納され、propが持つ参照が'fuga'が入っているアドレスに向けて変更されます。 なので、再代入はプリミティブ型の文字列である'hoge'が変更されているわけではなくあくまでもpropの参照先が変更されただけで一度作成するとその値を変更することはできないけど、letを使って再代入はできると言うことになります。 オブジェクトについて 上であげたプリミティブ型以外全て 変数には参照が格納される 値を変更することができる → mutable(可変) mutableについて 値を変更することができるとはどう言うことか オブジェクトを定義してメモリ上でどのように値が保持されているのか図にしました let obj = { prop: 'hoge' } propと言うプロパティーを持ったオブジェクトをobjに格納しました propの値'hoge'が変わったとしても変数objが保持している参照は変わらないことになるのでオブジェクトはmutable(値を変更することができる) オブジェクトはプロパティの参照を管理している 参照リンク https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types#data_types https://developer.mozilla.org/ja/docs/Glossary/Mutable https://developer.mozilla.org/ja/docs/Glossary/Immutable まとめ データ型にはプリミティブ型とオブジェクトが存在する プリミティブ型はimmutable オブジェクトはmutableで名前付きの参照を管理している入れ物 おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでGIF画像生成メモ #twilio_online_contest_2021

Twilio WebRTCハンズオン(ホワイトボード編)お絵描きしたものをGIF画像にしてみたいなぁと思って試しました。 JSGIF jsgifというライブラリを使ってGIF画像生成をしてみました。 JavaScriptでjsgifを使ってアニメーションGIFを動的生成する こちらからDLしたフォルダを解凍して b64.js LZWEncoder.js NeuQuant.js GIFEncoder.js を利用します。 コード全文 今回、GIT画像化したいpngファイルを7枚用意しました。 コードはほぼ参考サイトのままですが、微修正しています。 JavaScriptでjsgifを使ってアニメーションGIFを動的生成する index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GIF生成</title> </head> <body> <div id="anime"> <img src="images/gif/1.png" width="80" /> <img src="images/gif/2.png" width="80" /> <img src="images/gif/3.png" width="80" /> <img src="images/gif/4.png" width="80" /> <img src="images/gif/5.png" width="80" /> <img src="images/gif/6.png" width="80" /> <img src="images/gif/7.png" width="80" /> </div> <div> <p><button id="create">アニメGIFを作成する</button></p> <canvas id="canvas" style="display:none;"></canvas> <img id="anime_gif"> </div> <script src="jsgif/b64.js"></script> <script src="jsgif/LZWEncoder.js"></script> <script src="jsgif/NeuQuant.js"></script> <script src="jsgif/GIFEncoder.js"></script> <script src="script.js"></script> </body> </html> script.js let encoder; document.querySelector('#create').onclick = (event) => { //canvasの取得 const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); //GIFEncoderの初期処理 encoder = new GIFEncoder(); encoder.setRepeat(0); //繰り返し回数 0=無限ループ encoder.setDelay(500); //1コマあたりの待機秒数(ミリ秒) encoder.start(); //画像ファイル一覧を取得 frames = document.querySelectorAll('#anime img'); //canvasのサイズを1枚目のコマに合わせる canvas.width = frames[0].naturalWidth; canvas.height = frames[0].naturalHeight; //全ての画像をcanvasへ描画 for (let frame_no = 0; frame_no < frames.length; frame_no++) { ctx.drawImage(frames[frame_no], 0, 0); encoder.addFrame(ctx); //コマ追加 } //アニメGIFの生成 encoder.finish(); document.querySelector('#anime_gif').src = 'data:image/gif;base64,' + encode64(encoder.stream().getData()); } よもやま お絵描きしたCanvasから直接生成できるように出来るといいですよね。 取り急ぎ、#twilio_online_contest_2021のハッカソンで触ってみたので忘れないうちにメモしておきました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Async Iteretor]ちょっとずつ変化するコンポーネントを作りたい

はじめに 1 秒ごとに処理に応じてちょっとずつ変化していくコンポーネントが作りたかった です プログレスバーの拡張版みたいな... setInterval を使って実装して見たけど、setInterval の引数に渡した関数内では State が参照できないようで、以下のように setState 内で無理矢理条件分岐を作らないといけないです const intervalValue = setInterval(() => { setCount((count) => { if (count === 5) { clearInterval(intervalValue) return count } return count + 1 }) }, 1000) もっといい方法はないか考えていたらふと Generator の事を思い出したので実装してみることにしました 実装 お試しなのでシンプルにカウントアップするものを実装 作るもの ボタンを押すと 1 秒おきにカウントアップされる 比較のため Genetretor+Iteretor を使ったものと Interval を使ったものどっちも実装してみる コード See the Pen poeVOGB by あずは (@azuha) on CodePen. 解説 非同期ジェネレータ関数を利用して、count が 5 になるまで 1 秒毎に yield でカウントアップしていきます ジェネレータ関数を利用するとその戻り値は Generetor 型となり、yield で指定した値を持つ、いわゆるイテラブルなオブジェクトとして返却されます wait 関数を自作し、await で待機処理を作るために async 関数にしています const countUp = async function* (first) { let count = first; while(count < 5) { await wait(1000) count++ yield count } } 非同期ジェネレータ関数の戻り値は AsyncGeneretor 型となり、受け取ったイテラブルなオブジェクトは for await...of 文で取り出すことができます。 for await (const value of countUp(0)) { setCount(value) } ES2018 まで for 文の中で非同期処理は行えなかったみたいですが、for await...of 文が追加されてからは上記のような書き方ができるようになりました Interval を使用する場合 setInterval の戻り値を保持する State を用意しないといけなかった反面、ジェネレータ関数を利用すると State は必要なく簡潔に書けることがわかると思います 後書き 今まであまり触れてこなかったジェネレータ関数を利用することで欲しかった「ちょっとずつ変化していくコンポーネント」を実装することができました 割と最近出た構文(だと思っている)ですが、こういう新しい技術は覚えておくと今まで難しかったことがあっさり解決することがあるんだな〜と勉強になりました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む