20211015のJavaScriptに関する記事は21件です。

Node.jsでディレクトリを再帰的に作成/削除するのにmkdirpやrimrafはもう必要ない

Node.jsでディレクトリを再帰的に作成/削除するためのnpmパッケージとしてmkdirpやrimrafがありますが、現代のNode.js(v14.14.0以降)において、それらはもはや必要ありません。 ディレクトリを再帰的に作成する方法 mkdir -pのようにディレクトリを再帰的に作成するには、fs.mkdirのrecursiveオプションを使います。 コールバックAPIを使う場合: const fs = require('fs') const dir = process.argv[2] console.log(`try creating ${dir} directory`) fs.mkdir(dir, { recursive: true }, (e) => { if (e) { console.error(e) } else { console.log(`created ${dir} directory`) } }) Promise APIを使う場合: const fs = require('fs').promises const dir = process.argv[2] console.log(`try creating ${dir} directory`) fs.mkdir(dir, { recursive: true }) .then(() => console.log(`created ${dir} directory`)) .catch((e) => console.error(e)) なお、fs.mkdirにrecursiveオプションが追加されたのはv10.12.0です。 ディレクトリを再帰的に削除する方法 rm -rfのようにディレクトリを再帰的に削除するには、fs.rmのrecursiveオプションとforceオプションを使います。 コールバックAPIを使う場合: const fs = require('fs') const dir = process.argv[2] console.log(`try deleting ${dir} directory`) fs.rm(dir, { recursive: true, force: true }, (e) => { if (e) { console.error(e) } else { console.log(`deleted ${dir} directory`) } }) Promise APIを使う場合: const fs = require('fs').promises const dir = process.argv[2] console.log(`try deleting ${dir} directory`) fs.rm(dir, { recursive: true, force: true }) .then(() => console.log(`deleted ${dir} directory`)) .catch((e) => console.error(e)) なお、fs.rmが追加されたのはv14.14.0です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】v-onについて

はじめに こんにちは! 松本 佑大(まつもと ゆうだい)と申します! 今回はv-onについてアウトプットしていきます! v-onとは v-onとはボタンをクリックしたり、スクロールなどのユーザーによる一連の挙動を『イベント』と呼びます。そして、それら様々なイベントに対応して発動させる処理のこと(関数)を『イベントハンドラ』と呼びます。 Vue.jsにおいて、イベントによってイベントハンドラを実行させるには、v-onを使用します。 書き方 今回はボタンclick!を押したら、現在の日時が表示されるプログラムを実行していきます。 HTML <button v-on:click="Onclick"> Click! </button> <p> {{ now }} </p> Vue.js var app = new Vue({ el:'#app', data:{ now='' } methods: { Onclick: function() { this.now = new Date().toLocaleString(); } } }) dataオプションにnowプロパティ(p要素と同じもの)を設置します。 イベントハンドラのメソッドmethodsを定義していきます。内容は、”ボタンをクリックしたらdataのnowに現在時刻を代入する”というふうにします。それが上記になります。 それをp要素 {{now}}とHTMLに記述すれば出力されます。 出力結果が画像のようになります。 ポイントまとめ ・<button v-on:〇〇="〇〇〇">を記述 ・<p>{{〇}}</p>を記述 ・メソッドmethodsを定義し関数の処理を書く。 ・dataに{{〇}}と共通のプロパティを記述する 最後に 今回はv-onについてアウトプットしました。 今回の記事を読んで気になったこと、質問等あればコメント等頂けると幸いです。 今後ともQiitaにてアウトプットしていきます! 最後までご愛読ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSでクラスの位置を取得する

nanikaはClass名 targetをconsole.log()で見てみると、offsetTopとoffsetLeftで位置が確認できる scrollTab (): void { const target: any = document.getElementsByClassName('nanika'); window.scrollTo({ top: target[0].offsetTop, left: target[0].offsetLeft, behavior: 'smooth' }) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでトポロジカルソートを行うKahn’s algorithmのメモ

はじめに この記事は、Kahn’s algorithmを理解するための個人的メモです。はじめにKahn’s algorithmを文字ベースで説明した後、geeksforgeeksのトポロジカルソートのページで掲載されているJavaScriptのKahn’s algorithmを参考に、コードでの実装を見ていきます。 個人的メモではありますが、他の人のコードを見て実装はできるけど内部で何やってるかわからない、他のサイトのKahn’s algorithmがわかりにくい、という人の助けになる…かもしれません。 ただし、トポロジカルソートの定義に関してはwikiのトポロジカルソートのページなどにまとまっているため割愛します。 Kahn’s algorithm (文字ベース) Kahn’s algorithmは以下の1~6の手順によりトポロジカルソートを行うアルゴリズムです。 n=1とする グラフから入次数(定義は後で説明します)が0のノードを一つ選ぶ 2で選んだノードをトポロジカルソートの結果のn番目のノードとする nにn+1を代入する グラフから、2で選んだノードと、そのノードから他のノードに向かう辺を削除する 上の2~5を入次数が0のノードがなくなるまで繰り返す 入次数が0のノードがなくなれば成功・なくならなければ失敗 6で入次数が0のノードがなくなってもまだグラフにノードが残っている場合、残ったノードには循環部分があるためにトポロジカルソートは不可能(失敗)です。 ※入次数とは、他のノードから対象のノードに向かう辺の数です。例えば、以下のグラフの5のノードの入次数は0、0のノードの入次数は2です。 Kahn’s algorithmのコード全文 以下がgeeksforgeeksに載っていたコードです。元のコメントは削除しました。topologicalSort関数内には、何をやっている部分かのコメントを追加しました。後ろでより詳しく見ていきます。 let V; let adj; function Graph(v) { V = v; adj = new Array(V); for (let i = 0; i < V; i++) adj[i] = []; } function addEdge(u, v) { adj[u].push(v); } function topologicalSort() { // 1. グラフの入次数を計算する let indegree = new Array(V); for(let i = 0; i < V; i++) indegree[i] = 0; for (let i = 0; i < V; i++) { let temp = adj[i]; for (let node = 0; node < temp.length; node++) { indegree[temp[node]]++; } } // 2. 入次数が0のノードを探す let q = []; for (let i = 0; i < V; i++) { if (indegree[i] == 0) q.push(i); } // 3. トポロジカルソートする let cnt = 0; let topOrder = []; while (q.length!=0) { let u = q.shift(); topOrder.push(u); for (let node = 0; node < adj[u].length; node++) { if (--indegree[adj[u][node]] == 0) q.push(adj[u][node]); } cnt++; } // 4. ソートに失敗した場合のエラーを出す if (cnt != V) { document.write( "There exists a cycle in the graph"); return; } // 5. 結果を出力する for (let i = 0; i < topOrder.length; i++) { document.write(topOrder[i] + " "); } } Graph(6); addEdge(5, 2); addEdge(5, 0); addEdge(4, 0); addEdge(4, 1); addEdge(2, 3); addEdge(3, 1); document.write( "Following is a Topological Sort<br>"); topologicalSort(); // This code is contributed by avanitrachhadiya2155 コードのメモ コードでは2つのグローバル変数と、3つの関数が定義されます。これらを用いてトポロジカルソートが行われます。 コード下の方のGraph(6);の行の関数呼び出し以降は、トポロジカルソートのプログラムの実行例です。 以下のような有向グラフを定義し、トポロジカルソートを行って結果をコンソールに出力します。 グローバル変数 V グラフのノード数を保存するための変数です。Vには整数(ノード数)が代入されます。 作成されたノードにはそれぞれ(0)~(要素数-1)のidが振られます。このノードのidを使って辺が定義されます。 adj グラフの辺の情報を保存するための変数です。adjには2次元配列が代入されます。その2次元配列の要素は整数です。adjは、次のようにグラフの辺の情報を表します。 adj[(前のノードのid)] = (後ろのノードのidの配列); 例: adj[3] = [0, 4]; 上記の例は、以下のグラフを表します(idが1と2のノードは省略しています)。 関数 Graph関数 ノードを定義する関数です。引数は整数です。これはグラフのノード数を表していて、(0)から(Graph関数-1)までのidを持つノードが使えるようになります。 addEdge関数 辺を定義する関数です。引数は2つとも整数です。(第一引数のノードid) -> (第二引数のノードid)の向きの辺が定義されます。 topologicalSort関数 Kahn’s algorithmを実行する関数です。長いので細かく分けて見ていきます。 1. グラフの入次数を計算する // 1. グラフの入次数を計算する let indegree = new Array(V); for(let i = 0; i < V; i++) indegree[i] = 0; for (let i = 0; i < V; i++) { let temp = adj[i]; for (let node = 0; node < temp.length; node++) { indegree[temp[node]]++; } } グラフ内のすべてのノードの入次数を計算して、結果を配列indegreeに保存します。1つ目のfor文で入次数の計算結果を入れる配列を初期化し、2つ目のfor文(2重のfor文)でグローバル変数adjから辺の情報を取ってきて入次数を計算します。 入次数の数は配列indegreeに以下のように保存されます。 indegree[(ノードのid)] = (ノードのidに対応する入次数); 上のグラフでの例: indegree[0] = 2; indegree[5] = 0; 2. 入次数が0のノードを探す // 2. 入次数が0のノードを探す let q = []; for (let i = 0; i < V; i++) { if (indegree[i] == 0) q.push(i); } 入次数が0のノードのidを配列qに保存します。 3. トポロジカルソートする let cnt = 0; let topOrder = []; while (q.length!=0) { let u = q.shift(); topOrder.push(u); for (let node = 0; node < adj[u].length; node++) { if (--indegree[adj[u][node]] == 0) q.push(adj[u][node]); } cnt++; } 配列indegreeと配列qを用いてトポロジカルソートした結果を配列topOrderに保存します。変数cntはトポロジカルソートしたノードの数で、topOrder.lengthと等しくなります。 while文では、以下の処理を配列qがなくなるまで繰り返します。 1. 配列qからノードidを一つ取り出します。 2. 1.で取り出したidを配列topOrderの一番最後に追加します。 3. 配列adjから、1.で取り出したidのノードの後ろにつながるノードidを全て取り出します。 4. 配列indegreeの、3.で取り出したすべてのid番目の要素を1減らします。 5. 4.の引き算で配列indegreeの要素が0になったものがあれば、その要素に対応するidを配列'q'に保存します。 それぞれの変数の役割を入れて冗長に書くとこうなります。 1. 入次数が0のノードidの配列qからノードidを一つ取り出します。 2. 1.で取り出したidを配列topOrderの一番最後に追加します。 3. 辺の情報が保存されている配列adj(グローバル変数)から、1.で取り出したidのノードの後ろにつながるノードidを全て取り出します。 4. 入次数の数が保存されている配列indegreeの、3.で取り出したすべてのid番目の要素を1減らします。 5. 4.の引き算で配列indegreeの要素が0になったものがあれば、その要素に対応するidを配列qに保存します。 3.と4.でやっていることのイメージとしては、考える対象のグラフの縮小です。Kahn’s algorithm (文字ベース)では、「5. グラフから、2で選んだノードと、そのノードから他のノードに向かう辺を削除する」に対応します。入次数が0のノードと、そのノードから出ていく辺を削除し、グラフを縮小する感じです。 例: while文に入る前 例: while文に入る前 while文一周目の後 これを配列qが0になるまで繰り返すことで、巡回グラフである場合を除いて、全てのノードのトポロジカルソートが完了します。グラフに巡回している部分がある場合、巡回している部分のノードの入次数が0にならないためにソートされない、すなわち配列topOrderに入らないノードidが存在します。 4. ソートに失敗した場合のエラーを出す // 4. ソートに失敗した場合のエラーを出す if (cnt != V) { document.write( "There exists a cycle in the graph"); return; } 巡回グラフである場合にエラーを返します。 変数cntは配列topOrderの要素数と等しく、Vはグラフ中の全ノード数です。これらが異なることは、「3. トポロジカルソートする」の最後に書いた通り、巡回部分があって一部ソートできていないことを意味します。 5. 結果を出力する // 5. 結果を出力する for (let i = 0; i < topOrder.length; i++) { document.write(topOrder[i] + " "); } トポロジカルソートした結果を返します。 おわりに Kahn’s algorithmを文字ベースとコードベースの両方から見てみました。 読んでくださった方に何か発見や、理解の助けになる部分があれば幸いです。 読んでいただきありがとうございました。 参考文献 Kahn’s algorithm for Topological Sorting (geeksforgeeks)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

discord.js-v13でdiscordbot (スレッド周りの機能実装)

はじめに 当記事は筆者の忘備録であり、完全なチュートリアルではないことをご理解ください。 スレッド周りのみを見たい方はこちらを 開発環境 Node.js - 16.11.0 discord.js - 13.2.0 環境構築 Node.js 公式サイト discord.js 公式Guide まずは適当な場所に新規フォルダを作成し、そのフォルダ内で npm init -y を実行、完了したら同フォルダ内で npm install discord.js 上記二つが完了すればひとまず開発環境は構築できました。 botの招待 discord developer portal その1 ログイン後New Application buttonをクリックし、Appを作成してください 作成が完了したらページ左側にあるBot項目をクリックし、Add Botをクリックします。 ここでTOKENという項目と青色のボタンが現れると思うので左側のCopyをクリックしトークンを控えておいてください。 今控えたトークンは必ず他人に教えないでください。またGithub等にトークンが記載されたファイルを 上げることも控えてください。当警告を無視した結果起こりえる被害について筆者は責任を負いかねます。 その2 ページ左側のOAuth2をクリック ここが重要で SCOPEの中にあるbotとapplications.commandsに必ずチェックを入れてください。 後々追加することも可能ですが思わぬエラーと無駄な時間を過ごすことになります。 チェックを入れると下側にBOT PERMISSIONSという項目が出てくるので 皆様の用途に合った設定をしてください。 全てにチェックを終えたら、先ほどのSCOPEにURLが表示されていると思うのでコピーし踏んでください。 これで導入は完了! ファイル作成 discord.js Guide(Initial files) トークン管理ですが、ここではひとまずローカル環境での動作を目的としているので 最初に作成したフォルダのルートディレクトリにconfig.jsonファイルを作成してください。 以下中身 config.json { "token": "先ほど控えたトークンを張り付けてください。" } gitignoreも作成します。 node_modules .env config.json ようやくメインファイルであるindex.jsを作成します。 以下中身 index.js const { Client, Intents } = require('discord.js'); const { token } = require('./config.json'); const client = new Client({ intents: [Intents.FLAGS.GUILDS] }); client.once('ready', () => { console.log('Ready!'); }); client.login(token); 作成が完了したらターミナル(コマンドプロンプト)で node index.js を実行してみてください。ターミナル上にReady!と表示されたらひとまず作業は完了です。 終了はCtrl + cです。 コマンド作成 ここではv13から追加されたスラッシュコマンドを作成します。 まずは以下画像を見てください。 このように/を入力するとコマンド一覧が現れますよね?botがいないサーバーで入力してもビルドインコマンドが表示されます。 これらを今から作成します。 ちなみにですが、ここでapplications.commandsにチェックを入れていない場合次の作業を行った時に 必ずエラーが発生します。だからチェックをいれる必要があったんですね! 本題ですが、まずはプロジェクトフォルダ内で npm install @discordjs/builders @discordjs/rest discord-api-types を行ってください。これらはスラッシュコマンドを作成するときに必須な物です。 インストールが完了したら、先ほど作成したconfig.jsonを以下のように修正します config.json { "clientId": "123456789012345678", "guildId": "876543210987654321", "token": "your-token-goes-here" } clientIdとguildIdはそれぞれ - clientId ⇒ https://discord.com/developers/applications のOAuth2タブにCLIENT IDが記載されていますのでそれを張り付けてください。 - guildId ⇒ botを参加させたサーバーのIDです。サーバーを開いて名前を右クリックするとIDをコピーと出てきますのでコピペ。 次に核となるdeploy-commands.jsを作成します。 以下中身 deploy-commands.js const { SlashCommandBuilder } = require('@discordjs/builders'); const { REST } = require('@discordjs/rest'); const { Routes } = require('discord-api-types/v9'); const { clientId, guildId, token } = require('./config.json'); const commands = [ new SlashCommandBuilder().setName('ping').setDescription('Replies with pong!'), new SlashCommandBuilder().setName('server').setDescription('Replies with server info!'), new SlashCommandBuilder().setName('user').setDescription('Replies with user info!'), ] .map(command => command.toJSON()); const rest = new REST({ version: '9' }).setToken(token); rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands }) .then(() => console.log('Successfully registered application commands.')) .catch(console.error); 少しだけ解説をば - setName ⇒ コマンド名。setName('ping')であれば/pingと入力することができるようになります。 - setDescription ⇒ コマンドの説明文です。先ほどの画像でコマンド名の下に出てきてたやつです。 オプションは他にも沢山あります。詳細はこちら 後ほど記述するスレッド周りのコマンドも他オプションを利用します。 作成出来たら ファイルがあるディレクトリで node deploy-commands.js を実行してください。完了したら自身のサーバーで/を入力してみてください。コマンド一覧が出てきます。 ただ、現状はコマンドを登録できただけで機能は実装していないので次項からいよいよ実装に入ります。 コマンドに対しての機能実装 公式ガイド ここではコマンドに対して起こる機能を実装します。index.jsを開いて以下のように編集してください。 index.js client.once('ready', () => { console.log('Ready!'); }); //以下追加 client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; const { commandName } = interaction; if (commandName === 'ping') { await interaction.reply('Pong!'); } else if (commandName === 'server') { await interaction.reply('Server info.'); } else if (commandName === 'user') { await interaction.reply('User info.'); } }); // ここまで client.login(token); 一応解説 if (commandName === 'ping') { await interaction.reply('Pong!'); // もしコマンド名が 'ping' だったら 'Pong!' を返します 詳しくは以下画像を見てください。 interactionってなんぞや?って人はここを参照してください。 返信内容に引っ張ってきたデータを入れたい場合はシングルクォーテーションで囲うのではなくバッククォートで囲って await interaction.reply(`Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`); このように記述すると こんな感じで出せます。 ひとまず・・・ 以上で簡単なbotは作成完了しました。お疲れさまでした。 コマンドが増えていくとコマンド事にファイルを分ける等の作業が必要になりますがそれらはここでは解説しません。 次項からは応用編としてタイトルにもあるようにスレッド周りの実装を行います。 元気な方は引き続き頑張りましょう。 スレッド周りの実装 はじめに 当項目はスレッド周りの実装をしてみようと思った矢先、日本語記事が全くなくとてもつらい思い(ドキュメント漁り)をしたので こうして日本語記事として残そうと思った次第です。 ソース index.js else if (commandName === 'ct') { const thread = await interaction.channel.threads.create({ name: `${interaction.options.getString('name')}`, autoArchiveDuration: 60, reason: 'Needed a separate thread for food', }); interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });; } deploy-commands.js new SlashCommandBuilder().setName('ct').setDescription('create thread').addStringOption(option => option.setName('name').setDescription('スレッド名を入力してください。')) 解説 まずはdeploy-commands.jsの方から deploy-commands.js .addStringOption(option => option.setName('name').setDescription('スレッド名を入力してください。')) .addStringOptionでコマンドに対して以下の画像のようにオプションをつけることができます ここではオプションの名前をnameとしています。 ここで入力されたhelloは後ほど使います。 続いてindex.js index.js if (commandName === 'ct') { const thread = await interaction.channel.threads.create({ name: `${interaction.options.getString('name')}`, autoArchiveDuration: 60, reason: 'Needed a separate thread for food', }); interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });; } await interaction.channel.threads.create // コマンドが送信されたチャンネルにスレッドを作成します 以下channel.threads.createのオプション name: `${interaction.options.getString('name')}` // interaction.options.getString('ここに(option.setNameで付けた名前を入れます)') // こうすることで先ほどオプションで入力させた'hello'がこれから立ち上がるスレッドの名前になります autoArchiveDuration: 60, // 何分で自動アーカイブするかの設定です reason: 'Needed a separate thread for food', // これよくわからないんですよね...() interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });; // この部分、実はなくてもスレッドは立ちますがこれを記述しないと以下画像のようにエラーを吐きます。 // ephemeralは初期値だとFalseですがtrueにすることでbotからのメッセージがコマンドを打った人間にしか見えなくなります。 まとめ ここまで見てくださった皆様、お疲れ様でした。そしてご覧いただきありがとうございます。 雑な解説ではありましたがどなたかのお役に立てたなら幸いです。 私自身discord.pyを過去に使っていて最近discord.jsにやってきた身なのでまだまだ分からないことだらけですが頑張ってこれからも開発していけたらなぁと思っています。 また、そのうち本記事では紹介できなかった部分も記事に起こすつもりなのでまた良ければお願いします。 さいごに 当記事は公式のGuideをもとに個人的な解釈を含めて解説したものです。 なのでもちろん正しい完璧な記事とは言えないのでもし問題・質問等ありましたらお気軽にコメントしてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「マークダウン記法の画像サイズ調整」が面倒くさいのでツール作った。

マークダウン記法の画像サイズ調整のやり方 Qiitaの記事を書いていて 「あれ…?マークダウン記法で画像サイズの変更ってどうやるの?」と思い、調べると こちらの記事がヒット! …なるほど。これ↓ ![***画像のタイトル***](***画像のURL***) を、こう↓ <img src="***画像のURL***" width="***画像サイズ***"> してimgタグの形にすれば良いわけですね。なるほど。 でも、ちょっとめんどくさい 普通はQiitaに画像を、ポイっとドラックアンドドロップすれば自動的に ![***画像のタイトル***](***画像のURL***) こう↑なるのに、そこから打ち直さないとダメなのは地味に面倒くさい…ので、前に作った文章校正ツールのプログラムをちょっと改造して… 「マークダウン記法の画像テキストを貼り付けると、imgタグの形へ置換してくれるツール」 ↓を作りました。 作ったツールの使い方 こんな感じで、左側のテキストエリアに貼り付けるだけで(右側に)変換してくれます。 (widthの指定は、変更できます。) 複数の画像を一気に変換もできるので、打ち直すよりラクで早いはずです。 主に自分用ですけど、よかったらどうぞ。↓
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kendo UIを使っていたのを振り返ってみた

はじめに 初めまして。yamaguchiです。 IT系企業に入社して20余年のエンジニアです。 記事を書いた切っ掛けは、社会人となってから世の中の状況が大きく変わり、また業務における開発環境も様変わりしたことを実感して、その節目でどのように感じたのかを残しておきたいと考えたからです。 今回は、その節目の1つで 「Kendo UI for jQuery」 を利用した開発手法と出会った頃にフォーカスを当てたいと思います。 1.「Kendo UI for jQuery」って何ですか? Kendo UI は、HTML5とJavaScriptを使用して、Webソリューション開発を実現させるためのビジネス向けUIライブラリです。 当時すでに jQuery-UI が存在していましたが、よりモダンなレイアウトの提供をコンセプトとしたUIライブラリの1つとなっています。 jQuery の拡張をベースとした同コンセプトのフレームワーク群とは異なり、Kendo UIはJavaScriptを用いて速度を意識してゼロ構築されており、様々なデバイスで開発する上での強みともなっています。 「Kendo UI for jQuery」は、Kendo UI 製品の1つでjQueryを用いた開発をターゲットとしたコンポーネントセットになります。 提供元 telerik の「Kendo UI for jQuery」紹介サイトTop Kendo UI では、「Kendo UI Core」パッケージとして一部がオープンソース化されています。 「Grid」や「Editer」、「Stok Charts」などは商用ライセンスの下に置かれていますが、オープンソース化の割合は全機能の70%以上とも言われています。 提供元 telerik の「Kendo UI Core」パッケージDLサイト 2.「Kendo UI for jQuery」で何ができますか。 「Kendo UI for jQuery」には、現時点で100を超えるコンポーネントが含まれており、提供元紹介サイトでも「Grid」、「Scheduler」、「Chart」などを大きく紹介しています。 これら多くの利便性の高いコンポーネントを使用することにより、よりモダンで高速、そして効率のよいWeb開発の実現に大きな力となります。 例えば、「Grid」では、簡易なプロパティ設定で多機能な一覧表示が可能です。 ※提供元 telerik の Progress Kendo UI Dojo にてサンプル表示 以下は、2021/09/01時点で、提供元の telerik が公開している「Kendo UI for jQuery」紹介サイトの一部をクローズアップしたものです。 提供元 telerik の「Kendo UI for jQuery」デモ紹介サイト 3.「Kendo UI for jQuery」をどのように用い、それまでと何が変わりましたか? 一番多用したのは、やはり「Grid」コンポーネントになります。 それ以外にも「TreeView」、「DatePicker」、「DropDownList」などのコンポーネントを利用させてもらいました。 使ってみた感想ですが、以前の JQuery ui などを使用した業務との比較になりますが、実際のところ業務では生産性がだいぶ違っていたと思います。 「Grid」コンポーネントを例に挙げると、表を一覧表示するためのレイアウト生成やサーバから取得した表示データとのバインドが、プロパティ設定だけで済み、実際にサーバとのMVVMモデルでの連携部分や、業務要件によるレイアウトのカスタマイズなどに注力できたためです。 コンポーネントを使用する際に欲しいと感じる機能の多くはプロパティ設定で対応できるのが大きな強みとなっています。 「Grid」コンポーネントだと、ソートやデータのフィルタ、表示行の選択、位置変更、そしてセルでの内容編集などです。他にも多くの機能がプロパティ設定で対応可能です。 ※提供元 telerik の Progress Kendo UI Dojo にてソートのサンプル表示 ※提供元 telerik の Progress Kendo UI Dojo にてフィルタのサンプル表示 ※提供元 telerik の Progress Kendo UI Dojo にて表示行選択のサンプル表示 ※提供元 telerik の Progress Kendo UI Dojo にてセル編集のサンプル表示 多くの機能がプロパティ設定で対応可能ですが、一覧のヘッダやデータ行のセルなど色々な機能でテンプレートを設定することもでき、より柔軟に業務へ適用させることもできます。 例えば、データ行のセル編集で、行の内容から最適な内容をプルダウン選択させるといったこともテンプレートを用いて可能です。 例え始めればキリがないくらい多機能な「Kendo UI for jQuery」でありますが、提供元 telerik の非常に強力な支援の1つに Progress Kendo UI Dojo があります。 これは「Kendo UI for jQuery」のソースを貼り付けて動作を確認できるサイトですが、提供元 telerik が公開している「Kendo UI for jQuery」のAPI Referenceでもそのサンプルコードと紐づけられてあり、実際に telerik のサイト上でAPI Referenceの動作を確認できます。 サンプルコードを編集して動作の変化を見るなどといったことも可能なのです。 既に「Kendo UI for jQuery」を利用している技術者だけでなく、「Kendo UI for jQuery」初心者や導入を検討している技術者達にも非常に強力な支援といえます。 従来の業務において、要件の実現性を確認する際には、環境を作りサンプルを作りと多大な労力を要することも少なくありませんでした。 それが、お手軽にしかも詳細に確認できるため、「Kendo UI for jQuery」の多機能さと相乗して生産性の向上に大きく寄与しています。 実際の業務でも、レイアウト的な確認を含む「Kendo UI for jQuery」での実現性の検証作業が発生したとしたら、まずAPI ReferenceとProgress Kendo UI Dojo で確認を行っており、大変お世話になっていました。 4.「Kendo UI for jQuery」を使ってみて、不便に感じた点などはありますか? 実際に JQuery UI ベースの開発から移行してみると、あまり不満など出る余地がなさげだったのですが、細かいところをあえてあげてみると、ページ単位のスコープでscssなどを利用している場合でも「Kendo UI for jQuery」の「Grid」などが動的生成するタグには、スコープが設定されないので、全体へ適用されるscssに記載する必要がありました。 何か方法はあったのかもしれませんが、当時は結局業務終了まで解決できませんでした。「Kendo UI for jQuery」の問題というより組み合わせるライブラリ間での相性の問題ですね。 私の使い方が熟練できていなかっただけのかもしれませんが。 5.まとめ 実際に業務で使用してきて 「Kendo UI for jQuery」 は、非常に強力なビジネス向けUIライブラリであると考えます。 特に日々、対応/更新されておりどんどん新しいことができるようになるのは素晴らしいの一言です。 私の業務の歴史で、「Kendo UI for jQuery」 は、業務の作業手法に変化を起こして1つの節目としたことは間違いありません。 導入の相談を受ければ自信をもって紹介できる製品と感じています。 今後もまた使用する機会があるはずで、その際にはまた新しい驚きを提供してくれることを期待しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 無名関数とラムダ式

無名関数とは 無名関数(匿名関数)とは、関数名なしで定義できる関数。記述が短くて済むというメリットがある。 無名関数 const func = function (a,b){ return a + b; } この無名関数をより簡潔したのがラムダ式。 ラムダ式の書き方 無名関数では「function (引数) {処理内容}」と書く部分をラムダ式では「(引数) => {処理内容}」と定義できる。 上記の無名関数のコードをラムダ式で書くと以下となる。 ラムダ式 const func = (a,b) => { return a + b; } また、「=>」を使った書き方の関数を「アロー関数」とも呼ぶ。 さらに引数や処理によっては、より短く記述することもできる。 ■ 引数が1つの場合 引数が1つの場合、丸括弧()が省略可。 num => { console.log(num); } ■ 処理が1つの場合 処理が1つの場合、中括弧{}が省略可。 () => console.log('Hello!'); ■ 処理がreturn文のみの場合 処理がreturn文のみの場合、returnと中括弧{}が省略可。 (a,b) => a + b;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DOMを使用してHTMLテキストを書き換える方法まとめ

はじめに DOMにはHTMLを操作するための様々なプロパティが用意されていますが、HTMLテキストの書き換えができるプロパティが複数あり、ややこしかったので違いをまとめます。 具体的には textContent innerText innerHTML outerHTML の4つのプロパティの違いについてまとめます。 使い分け 最初に使い分けをまとめておくと、 HTMLタグを含む文字列を設定したい場合はinnerHTMLあるいはouterHTMLを使う。 単純なテキストを設定したい場合はtextContentを使う。 改行などユーザーがブラウザで見ているのと同じ状態で取得したい場合はinnerTextを使う。 textContent 対象のノードに含まれるテキストを取得・設定できる。 テキストを参照 node.textContent 対象のノードおよびノードの子孫ノードのテキストをすべて連結した値を取得することができる。 テキストを書き換え node.textContent = 'value' ノードの textContent プロパティの値に新しい文字列を設定すると、対象のノードの子孫のノードがすべて削除されたあとに設定した文字列の値を持つテキストノードが追加される。 設定する文字列の中にHTMLのタグなどが含まれていた場合 HTML のタグを含む文字列を設定した場合、タグとしては認識されず単なる文字列として扱われる。 innerText 要素内のレンダリングされたテキストを取得・設定できる。 テキストを参照 htmlElement.innerText 対象の要素および要素の子孫要素のテキストを取得することができる。 空白ノードなどのようにブラウザでは表示されないものは取得されない。 スタイルシートの設定などで非表示になっている要素、 <style> タグのように元々ブラウザに表示されない要素のテキストは取得されない。 また <br> タグで改行が行わている場合は改行された状態でテキストを取得する。 テキストを書き換え htmlElement.innerText = 'value' 要素の innerText プロパティの値に新しい文字列を設定すると、対象の要素の子孫要素のノードがすべて削除されたあとに設定した文字列の値を持つテキストノードが追加される。 設定する文字列の中にHTMLのタグなどが含まれていた場合 HTML のタグを含む文字列を設定した場合、タグとしては認識されず単なる文字列として扱われる。 innerHTML 要素に含まれるHTML文を取得・設定できる。 HTML文を参照 element.innerHTML 要素に子や孫の要素が含まれる場合は、それらの要素の HTML 文も含めて取得する。 HTML 文は HTML ファイルに記述されている内容をそのまま取得する。 HTML文を書き換え element.innerHTML = 'value' 要素の innerHTML プロパティの値に新しい文字列を設定すると、対象の要素の子孫要素のノードがすべて削除されたあとに設定した HTML 文の値が追加される。 新しく追加した HTML 文の中にタグが含まれていた場合にはそのままタグとして扱われる。 HTML文の中にscriptタグが含まれていた場合 innerHTML プロパティには HTML 文を新しい値として設定できるため、 <script> タグが含まれる HTML 文も設定できる。 ただし、innerHTML プロパティを利用して <script> が設定された場合は記述されたスクリプトは実行されない。 outerHTML 要素自身と要素に含まれるHTML文を取得 要素自身も含めたHTML文を参照 element.outerHTML innerHTML では要素に含まれる HTML 文を取得するが、 outerHTML では要素自身も含めて取得する。 要素自身も含めたHTML文を書き換え element.outerHTML = 'value' その他の仕様はinnerHTMLと同様。 参考・引用サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】変数と参照の振り返り⑦ 分割代入と値・参照の比較

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 変数についての理解を深める 本題 1.参照と分割代入 基本構文. let{a,b} = object; オブジェクトから特定のプロパティを抽出して宣言を行う場合に使われる。 この場合、aとbがオブジェクトの初期値として扱われる。 例 const a = { prop: 0 } // 分割代入 // プロパティの名前と変数の名前は一致させる let { prop } = a; prop = 1; // ここで出力されるのは0と1となる // 分割代入により変数propの値に1が代入されている // 最初に定義した{prop}には影響を受けない console.log(a, prop); 関数と分割代入 // 関数fnを定義 function fn(obj){ // 分割代入 let { prop } = obj; // 変数の値を変更 prop = 1; // 結果を出力 console.log(obj, prop); } // { prop : 0}と1と出力される fn(a); 関数内で特定のプロパティを使用したい場合は以下のように書き換えることができる // 関数fnを定義 // 下記のようにすると渡ってきたオブジェクトがこの時点で分割代入で展開されて、propという変数が使用できるようになる // オブジェクトの中身は変更されない function fn({ prop } ){ // 変数の値を変更 prop = 1; // 結果を出力 console.log(a, prop); } // { prop : 0}と1と出力される fn(a); オブジェクトが多階層だった場合 // cがprop1を格納しており、さらにprop2を格納している状態 const c = { prop1: { prop2: 0 } } // 分割代入 let {prop1} = c; // prop1を出力すると{prop2: 0}と出力される console.log(prop1); // その中身のprop2の値を変更すると prop1.prop2 = 1; // 結果はprop1の中身のprop2の値が1になる // cからprop1の値を抽出した時点で、参照自体が保持される console.log(c, prop1); 2.参照の比較と値の比較の違い // オブジェクトを定義 const a = { prop: 0 } const b = { prop: 0 } // aとbは同じオブジェクトであるか確認する // 等価性を下記で確認するとfalseになる // その理由は変数a,bに入っているのはオブジェクトへの参照であるため // オブジェクトへの参照同士を確認して参照先のオブジェクトが変わってくるのでfalse console.log(a === b); // 抽象的な等価性も同様 console.log(a == b); // 確認するにはオブジェクトの中にあるプロパティを比較する必要がある // 下記のように書くとtrue console.log(a.prop === b.prop); // cにaを代入した場合 const c = a; // 下記はtrueとなる // 参照先を12行目で渡しているため console.log(a === c); プリミティブ型では値の比較 オブジェクトでは参照の比較 が行われる 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptのコールバック関数について

Javascriptのコールバック関数に関して、アウトプットのため本記事を投稿します。 Javascriptにおけるコールバック関数とは「将来のある時点で実行される関数」のことを指します。 簡易的な例 下記に例を記載します。 function a(callback) { callback(); } function b() { console.log("コールバック関数として呼び出されました"); } a(b); //実行結果 コールバック関数として呼び出されました 処理の流れを記載します。 ① aという関数を宣言します。(仮引数の名称は何でも大丈夫です) ② bという関数を宣言します。 ③ ②で宣言したbという関数を実引数として、①で宣言したaという関数を呼び出します。 ④ aの処理が実行され、bという関数が実行されます。 ⑤ bの処理が実行され、文字列が出力されます。 関数aの仮引数には、実引数である関数bが入るので、callback() と b()は同じ命令に変換されます。 このように、コールバック関数では、引数に関数を渡して任意のタイミングで呼び出すことができます。 複雑な例 次に、前章のコードよりも複雑になったコールバック関数を解説します。 function a(callbackB) { console.log("関数aが呼び出されました"); callbackB(); } function b(callbackC) { console.log("コールバック関数bが呼び出されました"); callbackC(); } function c(callback) { console.log("コールバック関数cが呼び出されました"); callback(); } a(function() { b(function() { c(function() { console.log("全ての処理が終了しました"); }); }); }); // 実行結果 関数aが呼び出されました コールバック関数bが呼び出されました コールバック関数cが呼び出されました 全ての処理が終了しました 処理の流れを記載します。 ① 関数a,b,cを宣言します。 ② 関数aを実行します。 ③ 関数aの仮引数「callbackB」に関数aの呼び出し元の実引数が渡されます。 関数aの呼び出し元の実引数は下記コードです。 function() { b(function() { c(function() { console.log("全ての処理が終了しました"); }); }); }) 関数bの実引数があるため分かりづらくなっていますが、関数bの呼び出し元の実引数は使用しないので、実質的な仮引数は下記のように、関数bを呼び出す無名関数になります。 function() { b(); }) ④ 「console.log("関数aが呼び出されました")」が実行されます。 ⑤ ③で渡された引数(callbackB)を実行します。   先ほど説明した通り、実引数は無名関数であるため、関数bを実行する処理になります。 ⑥ 関数bの仮引数「callbackC」に、関数bの実引数(関数cを呼び出す無名関数)が渡されます。 ⑦ 関数bが実行され、「console.log("コールバック関数bが呼び出されました")」が実行されます。 ⑧ ⑥で渡された引数(callbackC)を実行します。 ⑨ 関数cの仮引数「callback」に、関数bの実引数(無名関数)が渡されます。  実引数(無名関数)は以下です。 function() { console.log("全ての処理が終了しました") } ⑩ 関数cが実行され、「console.log("コールバック関数cが呼び出されました")」が実行されます。 ⑪ ⑨で渡された引数を実行します。 ⑫ 「console.log("全ての処理が終了しました")」が実行されます。 上記の様に、関数a,b,cを順番に実行することができます。 また、関数の呼び出し元は下記の様にアロー関数でも実行することができます。 a(() =>{ b(() =>{ c(() =>{ console.log("全ての処理が終了しました"); }); }); }); アロー関数の方が、記述がシンプルになり可読性が上がります。 setTimeoutメソッドを使用した例 setTimeoutメソッドを使用した際のコールバック関数の処理に関して記述します function a(callback) { setTimeout(callback, 3000); }; function b() { console.log("コールバック関数として呼び出されました"); }; a(b); console.log("関数呼び出し後"); // 実行結果 関数呼び出し後 コールバック関数として呼び出されました setTimeoutはノンブロッキングメソッドであるため、関数bが呼び出されるより先に、「console.log("関数呼び出し後")」が実行されます。 コールバック関数はあくまでも関数を順番に実行しするために使用するため、ブロッキングをして同期処理を実現いるわけではないことが分かります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v-modelとpropsとstore(vuex)

※ ぱっと見でわかるように説明と文法の省略をしています。 前提知識 <input v-model="title" /> は ⇩ の 糖衣構文 (シンタックスシュガー = 簡単な書き方) <input :value="title" @input="title = $event.target.value" /> 親子間の v-model パターン 親コンポーネント // Parent <child-component v-model="title" /> data() { return { title: "", }; }, ノーマル なやり方 親コンポーネントは v-model 子コンポーネントは v-bind と v-on // Child <input :value="title" @input="$emit('input', $event.target.value)" > props: { title: { type: String, }, }, computed 使ったやり方 親コンポーネントは v-model 子コンポーネントは computed の get と set get は値を取得 set は値の変更を検知してイベント発火 // Child <input v-model="inputedValue" /> props: { title: { type: String, }, }, computed: { inputedValue: { get() { return this.title; }, set(newValue) { this.$emit("input", newValue); }, }, }, store(vuex)絡み store.js state: { title: '', }, mutations: { updateTitle(state, title) { state.title = title; }, }, 親コンポーネント <child-component :title="title" @update-title="updateTitle($event)" /> computed: { title() { return this.$store.state.title; }, }, methods: { updateTitle(value) { this.$store.commit('updateTitle', value); }, }, 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] React Router 後編

React初心者がReact Routerについて勉強した時のメモです。 後編の今回は、 クエリパメーターの値の受け渡し stateを渡すページ遷移 Linkを使わないページ遷移 404ページ です。 クエリパラメーター クエリパラメーターとはURLの末尾ついてる?name=hogehogeのような部分のこと。 Page2.jsx import { Link } from "react-router-dom"; export const Page2 = () => { return ( <div> <h1>Page2ページです。</h1> <Link to="/page2/100">URL Parameter</Link> <br /> <Link to="/page2/100?name=hogehoge">Query Parameter</Link> </div> ); }; Urlparameter.jsx import { useParams, useLocation } from "react-router-dom"; export const Urlparameter = () => { const { id } = useParams(); const { search } = useLocation(); const query = new URLSearchParams(search); return ( <div> <h1>Urlparameterページです。</h1> <p>パラメーターは {id} です</p> <p>クエリは {query.get("name")}</p> </div> ); }; useLocationを使うことでクエリパラメーターの値の受け渡しができる。 useLocation内のsearchという部分に?以降の文字列が渡ってくる。(今回の場合だとname=hogehogeの部分) searchをURLSearchParamsメソッドの引数に渡すことでgetというメソッドを使いnameを指定することでhogehogeが取得できる。 stateを渡すページ遷移 ページ遷移の時にstateを持たせる方法。 stateを受け渡すメリットは、APIでつぶやきなどの一覧ページを取得し、取得した情報(state)を持ってつぶやき詳細ページに遷移すればもう一度APIを呼ぶ必要なく情報(state)を受け渡せる。 Page1.jsx import { Link } from "react-router-dom"; export const Page1 = () => { //-------- 渡したい情報 -------------- const arr = [...Array(100).keys()]; console.log(arr); //---------------------------------- return ( <div> <h1>Page1ページです。</h1> <Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link> <br /> <Link to="/page1/detailB">DetailB</Link> </div> ); }; Linkの部分にpathnameと書き、パスとstateを持たせる。 Page1DetailA.jsx import { useLocation } from "react-router-dom"; export const Page1DetailA = () => { const { state } = useLocation(); console.log(state); return ( <div> <h1>Page1DetailAページです。</h1> </div> ); }; useLocationを使うことで渡ってきたstateを取得できる。 Linkを使わないページ遷移 JavaScript側でページ遷移する方法。 例えば、ボタンを押した後のページ遷移や、投稿などの処理が終わった後に起きるページ遷移。 Page1.jsx import { Link, useHistory } from "react-router-dom"; export const Page1 = () => { const arr = [...Array(100).keys()]; console.log(arr); const history = useHistory(); const onClickDetailA = () => history.push("/page1/detailA"); return ( <div> <h1>Page1ページです。</h1> <Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link> <br /> <Link to="/page1/detailB">DetailB</Link> <br /> <button onClick={onClickDetailA}>DetailA</button> </div> ); }; useHistoryを使う。 history.push(リンクのパス)とすることで画面遷移できる。 今回の場合はボタンを押したら、/page1/detailAに画面遷移する。 Page1DetailA import { useLocation, useHistory } from "react-router-dom"; export const Page1DetailA = () => { const { state } = useLocation(); console.log(state); const history = useHistory(); const onClickBack = () => history.goBack(); return ( <div> <h1>Page1DetailAページです。</h1> <br /> <button onClick={onClickBack}>戻る</button> </div> ); }; history.goBack()とすることでブラウザの戻るボタンと同じような戻る画面遷移ができる。 404ページ 存在しないページを作成しておく。 Page404.jsx import { Link } from "react-router-dom"; export const Page404 = () => { return ( <div> <h1>ページが見つかりません</h1> <Link to="/">TOPに戻る</Link> </div> ); }; Router import { Switch, Route } from "react-router-dom"; import { Home } from "../Home"; import { Page404 } from "../Page404"; import { Page1Route } from "./Page1Route"; import { Page2Route } from "./Page2Route"; export const Router = () => { return ( <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/page1" render={({ match: { url } }) => ( <Switch> {Page1Route.map((route) => ( <Route key={route.path} exact={route.exact} path={`${url}${route.path}`} > {route.children} </Route> ))} </Switch> )} ></Route> <Route path="/page2" render={({ match: { url } }) => ( <Switch> {Page2Route.map((route) => ( <Route key={route.path} exact={route.exact} path={`${url}${route.path}`} > {route.children} </Route> ))} </Switch> )} ></Route> ------- 追加 ----------- <Route to="*"> <Page404 /> </Route> ------------------------ </Switch> ); }; 追加した部分のようにすることでどのページにも一致しない時にPage404がレンダリングされる。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] React Router 前編

React初心者がReact Routerについて勉強した時のメモです。 コード部分が長くなってしまったので前編後編の2回に分けます。 今回は、 基本的なページ遷移 ネストされたページ遷移 ルーティング分割 URLパラメーターの値の受け渡し です。 事前にreact-router-domをインストール。 基本的なページ遷移 import { BrowserRouter, Link, Switch, Route } from "react-router-dom"; import { Home } from "./Home"; import { Page1 } from "./Page1"; import { Page2 } from "./Page2"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/page1"> <Page1 /> </Route> <Route path="/page2"> <Page2 /> </Route> </Switch> </div> </BrowserRouter> ); } BrowserRouterで全体を囲みLinkにパスを書きリンクを生成。 どのパスの時にどのコンポーネントをレンダリングするかは、Switchで全体を囲み、コンポーネントをRouteで囲みパスを指定。 exactをつけることで完全一致のみになる。 exactがないと/でレンダリングされるので、Page1 と Page2はレンダリングされずHomeのみのレンダリングになるので注意。 ネストされたページ遷移 App.js import { Home } from "./Home"; import { Page1 } from "./Page1"; import { Page1DetailA } from "./Page1DetailA"; import { Page1DetailB } from "./Page1DetailB"; import { Page2 } from "./Page2"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> <Switch> <Route exact path="/"> <Home /> </Route> -------------------- 追加 -------------------- <Route path="/page1" render={({ match: { url } }) => ( <Switch> <Route exact path={url}> <Page1 /> </Route> <Route path={`${url}/detailA`}> <Page1DetailA /> </Route> <Route path={`${url}/detailB`}> <Page1DetailB /> </Route> </Switch> )} > </Route> -------------------- 追加 ---------------------- <Route path="/page2"> <Page2 /> </Route> </Switch> </div> </BrowserRouter> ); } Page1.jsx import { Link } from "react-router-dom"; export const Page1 = () => { return ( <div> <h1>Page1ページです。</h1> <Link to="/page1/detailA">DetailA</Link> <br /> <Link to="/page1/detailB">DetailB</Link> </div> ); }; page1/detailApage1/detailBのようにネストされたページ遷移のルーティングの仕方。 renderはデフォルトでpropsを受け取りprops内のmatchのurlを使うことでネストされたルーティング部分はpage1であることを保証している書き方にできる。 ルート定義の分割 ルーティング部分を別ファイルに切り出す。 App.jsx import { BrowserRouter, Link } from "react-router-dom"; import { Router } from "./router/Router"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> <Router /> </div> </BrowserRouter> ); } Router.jsx import { Switch, Route } from "react-router-dom"; import { Home } from "../Home"; import { Page2 } from "../Page2"; import { Page1Route } from "./Page1Route"; export const Router = () => { return ( <Switch> <Route exact path="/"> <Home /> </Route> -----------Page1Detail部分------------------- <Route path="/page1" render={({ match: { url } }) => ( <Switch> {Page1Route.map((route) => ( <Route key={route.path} exact={route.exact} path={`${url}${route.path}`} > {route.children} </Route> ))} </Switch> )} ></Route> ------------------------------------------- <Route path="/page2"> <Page2 /> </Route> </Switch> ); }; Page1Detail部分も別ファイルに切り出す。 Page1Route.jsx import { Page1 } from "../Page1"; import { Page1DetailA } from "../Page1DetailA"; import { Page1DetailB } from "../Page1DetailB"; export const Page1Route = [ { path: "/", exact: true, children: <Page1 /> }, { path: "/detailA", exact: false, children: <Page1DetailA /> }, { path: "/detailB", exact: false, children: <Page1DetailB /> } ]; Page1Routeコンポーネントではpath、exactかどうか、レンダリングされるコンポーネントは何か、が分かるように切り出してRouterコンポーネントでmapを使うことで実現できる。 URLパラメーター URLパラメーターはパスにIDのようなパラメーターを渡す部分。 今回はPage2にIDを渡すことにする。 Page2もPage1同様に別ファイルに切り出す。 Router.jsx import { Switch, Route } from "react-router-dom"; import { Home } from "../Home"; import { Page1Route } from "./Page1Route"; import { Page2Route } from "./Page2Route"; export const Router = () => { return ( <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/page1" render={({ match: { url } }) => ( <Switch> {Page1Route.map((route) => ( <Route key={route.path} exact={route.exact} path={`${url}${route.path}`} > {route.children} </Route> ))} </Switch> )} ></Route> <Route path="/page2" render={({ match: { url } }) => ( <Switch> {Page2Route.map((route) => ( <Route key={route.path} exact={route.exact} path={`${url}${route.path}`} > {route.children} </Route> ))} </Switch> )} ></Route> </Switch> ); }; Page2Route.jsx import { Page2 } from "../Page2"; import { Urlparameter } from "../UrlParameter"; export const Page2Route = [ { path: "/", exact: true, children: <Page2 /> }, { path: "/:id", exact: false, children: <Urlparameter /> } ]; pathの部分に:パラメータ名と書くことでパラメーターを受け取ることができる。 今回はIDを受け取るので:idとする。 Page2.jsx import { Link } from "react-router-dom"; export const Page2 = () => { return ( <div> <h1>Page2ページです。</h1> <Link to="/page2/100">URL Parameter</Link> </div> ); }; Link部分に今回渡す値の100を設定しておく。 UrlParameter.jsx import { useParams } from "react-router-dom"; export const Urlparameter = () => { const { id } = useParams(); return ( <div> <h1>Urlparameterページです。</h1> <p>パラメーターは {id} です</p> </div> ); }; useParamsを使うことでパラメーターの値の受け渡しができる。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] ReactのCSSの当て方

ReactのCSSの当て方の種類について勉強した時のメモです。 今回当てているCSSは全て以下のスタイルで統一しています。 InlineStyle export const InlineStyle = () => { const contaierStyle = { border: "solid 2px #329eff", borderRadius: "20px" padding: "8px", margin: "8px" } return ( <div style={contaierStyle}> <p>スタイル</p> </div> ) } コンポーネント内にスタイルを記述していくReactがデフォルトで提供している方法。 CSSプロパティはキャメルケース文字列のように "" で囲い , で区切る。 CSSModules import classes from "./CssModules.module.scss"; export const CssModules = () => { return ( <div className={classes.container}> <p>スタイル</p> </div> ); }; .container { border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; } CSSを別ファイルに切り出してimportする方法。 切り出すCSSファイル名にはmoduleをつける。 CSSの書き方は普通のCSSの書き方と同じ。 node-scssをインストールすることで使用可能。 Styled JSX export const StyledJsx = () => { return ( <> <div className="container"> <p>スタイル</p> </div> <style jsx="true"> {` .container { border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; } `} </style> </> ); }; JSXの中でスタイルを書いていく方法。 デフォルトだとhoverなどの疑似要素は使用できない。 Next.jsに標準搭載されている。 styled-jsxをインストールすることで使用可能。 Styled-Components import styled from "styled-components"; export const StyledComponents = () => { return ( <Container> <p>スタイル</p> </Container> ); }; const Container = styled.div` border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; `; スタイルで当てたコンポーネントを当てていく方法。 コンポーネントファイルでstyled-componentsをimportする必要がある。 styled-componentsをインストールすることで使用可能。 書き方の工夫 import styled from "styled-components"; export const StyledComponents = () => { return ( <SContainer> <STitle>スタイル</STitle> </SContainer> ); }; const SContainer = styled.div` border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; `; const STitle = styled.p` color: red; `; コンポーネント名の頭文字をSにすることでぱっと見でスタイルを当てているコンポーネントだと分かるのでこの書き方はおすすめ。 Emotion Emotionは色々な書き方がある方法。 1行目に/** @jsx jsx */と書く必要がある。 @emotion/reactと@emotion/styledをインストールする必要がある。 CSSと同じ書き方ができる方法 /** @jsx jsx */ import { jsx, css } from "@emotion/react"; export const Emotion = () => { const containerStyle = css` border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; `; return ( <div css={containerStyle}> <p>スタイル</p> </div> ); }; inline styleと似た書き方 /** @jsx jsx */ import { jsx, css } from "@emotion/react"; export const Emotion = () => { const containerStyle = css({ border: "solid 2px #329eff", borderRadius: "20px", padding: "8px", margin: "8px" }); return ( <div css={containerStyle}> <p>スタイル</p> </div> ); }; styled-componentと似たような書き方 /** @jsx jsx */ import { jsx, css } from "@emotion/react"; import styled from "@emotion/styled"; export const Emotion = () => { return ( <SContainer> <p>スタイル</p> </SContainer> ); }; const SContainer = styled.button` border: solid 2px #329eff; border-radius: 20px; padding: 8px; margin: 8px; `; 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React]再レンダリングの仕組みと最適化について

再レンダリング最適化について勉強した時のメモです。 再レンダリングが起きる条件 stateが更新されたとき。 propsが変更されたとき 親コンポーネントが再レンダリングされた時のコンポーネント配下の子要素。 以下の場合Bが再レンダリングされるとCも再レンダリングされる。 Stateが更新された時 import { useState } from "react"; export const App = () => { console.log("Appレンダリング") const [text, setText] = useState(""); const onChangeText = (e) => setText(e.target.value); return ( <div className="App"> <input value={text} onChange={onChangeText} /> </div> ); }; この場合inputのテキストボックスに値を入れるたびにconsoleが走るのでstateが変わるたびに再レンダリングが発生している。 propsが更新された時 src/App.jsx import { useState } from "react"; import { ChildArea } from "./components/ChildArea"; export const App = () => { const [text, setText] = useState(""); const [open, setOpen] = useState(false); const onChangeText = (e) => setText(e.target.value); const onClickOpen = () => setOpen(!open); return ( <div className="App"> <input value={text} onChange={onChangeText} /> <br /> <br /> <button onClick={onClickOpen}>表示</button> <ChildArea open={open} /> </div> ); }; src/components/ChildArea.jsx export const ChildArea = (props) => { const { open } = props; console.log("ChildAreaレンダリング"); return ( <> {open ? ( <div> <p>子コンポーネント</p> </div> ) : null} </> ); }; ChildAreaにopenというpropsを渡しているのでボタンを押すたびに再レンダリングが起きている。 親コンポーネントが再レンダリングされた時 src/App.jsx import { useState } from "react"; import { ChildArea } from "./components/ChildArea"; export const App = () => { const [text, setText] = useState(""); const [open, setOpen] = useState(false); const onChangeText = (e) => setText(e.target.value); const onClickOpen = () => setOpen(!open); return ( <div className="App"> <input value={text} onChange={onChangeText} /> <br /> <br /> <button onClick={onClickOpen}>表示</button> <ChildArea open={open} /> </div> ); }; src/components/ChildArea.jsx export const ChildArea = (props) => { const { open } = props; console.log("ChildAreaレンダリング"); return ( <> {open ? ( <div> <p>子コンポーネント</p> </div> ) : null} </> ); }; この場合ChildAreaのpropsであるopenには触れていないが、親コンポーネントに再レンダリングが起きているためChildAreaにも再レンダリングが起きている。 コンポーネント最適化 memo useCallback memo(コンポーネントをmemo化) src/components/ChildArea.jsx import { memo } from "react"; export const ChildArea = memo((props) => { const { open } = props; console.log("ChildAreaレンダリング"); return ( <> {open ? ( <div> <p>子コンポーネント</p> </div> ) : null} </> ); }); 子コンポーネントをmemo化することでpropsに変更があった時のみレンダリングが起きるようにできる。 基本的にコンポーネントは全てmemo化するべき。 useCallback(関数をmemo化) src/App.jsx import { useState } from "react"; import { ChildArea } from "./components/ChildArea"; export const App = () => { const [text, setText] = useState(""); const [open, setOpen] = useState(false); const onChangeText = (e) => setText(e.target.value); const onClickOpen = () => setOpen(!open); // 追加 const onClickClose = () => setOpen(false); return ( <div className="App"> <input value={text} onChange={onChangeText} /> <br /> <br /> <button onClick={onClickOpen}>表示</button> <ChildArea open={open} onClickClose={onClickClose} /> </div> ); }; src/components/ChildArea.jsx import { memo } from "react"; export const ChildArea = memo((props) => { const { open, onClickClose } = props; console.log("ChildAreaレンダリング"); return ( <> {open ? ( <div> <p>子コンポーネント</p> </div> ) : null} <button onClick={onClickClose}>閉じる</button> </> ); }); 今までの機能に表示を閉じるボタンを実装するためにonClickCloseという関数を追加した。 その場合、先程memo化したChildAreaには再度再レンダリングが起きるようになってしまう。 なぜなら、アロー関数で書いた関数は毎回新しい関数を生成しているという判断をされてしまい、propsが違うものとして扱われ、propsが更新された場合と同様に再レンダリングが起きてしまう。 useCallbackを使う useEffectと同様に第二引数に入れた値を監視するので今回はsetOpenを指定して監視しsetOpenの処理が走った時のみレンダリングが起きる。 結果、不要な再レンダリングを防げる。 const onClickClose = useCallback(() => setOpen(false),[setOpen]); 基本コンポーネントはmemo化しmemo化したコンポーネントに関数を使う時は、 その関数にuseCallbackを使うことで不要な再レンダリングを防げるのでレンダリングを最適化できる。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Three.jsでグリーンバック動画をテクスチャにする

Three.jsでテクスチャに動画を貼り付けて再生することはできるが、 透過情報を持った動画を再生する方法は中々見つからなかったので、 今回はテクスチャをキーイングして動画を透過させる 準備 何でもよいので、GB動画を用意してください。 オブジェクトとテクスチャを作成 Three.jsの基礎は知っている前提で、要所だけ記述します。 const geometry = new THREE.PlaneBufferGeometry(1.6, .9); 比率が動画と合っているならサイズは何でも可 const video = document.createElement('video') //動画ファイルをhtmlへ埋め込む video.src = './texture.mp4' video.setAttribute('playsinline', '') video.style.display = 'none' video.muted = 'true' video.play() const videotexture = new THREE.VideoTexture(video) //テクスチャへ貼り付け 読み込んだ動画をテスクチャローダーで指定します。 マテリアルを作成 シェダーマテリアルを作成します vertexShaderとfragmentShaderは特に考えずに定義してください。 const material = new THREE.ShaderMaterial({ transparent: true, uniforms: { map: { value: videotexture }, keyColor: { value: [0.0, 1.0, 0.0] }, similarity: { value: 0.7 }, smoothness: { value: 0 }, }, vertexShader: vertexShader(), fragmentShader: fragmentShader(), }) function vertexShader() { return ` varying vec2 vUv; void main( void ) { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } ` } function fragmentShader() { return ` uniform vec3 keyColor; uniform float similarity; uniform float smoothness; varying vec2 vUv; uniform sampler2D map; void main() { vec4 videoColor = texture2D(map, vUv); float Y1 = 0.299 * keyColor.r + 0.587 * keyColor.g + 0.114 * keyColor.b; float Cr1 = keyColor.r - Y1; float Cb1 = keyColor.b - Y1; float Y2 = 0.299 * videoColor.r + 0.587 * videoColor.g + 0.114 * videoColor.b; float Cr2 = videoColor.r - Y2; float Cb2 = videoColor.b - Y2; float blend = smoothstep(similarity, similarity + smoothness, distance(vec2(Cr2, Cb2), vec2(Cr1, Cb1))); gl_FragColor = vec4(videoColor.rgb, videoColor.a * blend); } ` } // Mesh const sphere = new THREE.Mesh(geometry,material) scene.add(sphere) 結果 表示できました 境界線のジャギが少し気になるので、描写精度を優先するならあまり使い物にはならないかなー Webmなら透過動画が使用できるけど、iOSでは動作しないので... いい方法がありましたら教えてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これからReact始めたい人のための今日だけでできるTODO#23 TODOアプリの作成① ローカルサーバーと用意したサーバーと通信できるようにする

TODOアプリの作成 これまで学んできたことを応用してTODOアプリを作成していきます。 少し長くなると思うので、1日で軽くできるTODOごとに記事にしていきます。 開発環境の準備 まずは開発環境を準備します。 npx create-react-app 任意のディレクトリ名 で環境を構築しましょう。 次に不要なファイルやコードを消していきます。 不要ファイル一覧 App.css App.text.js index.css logo.svg reportWebVitals.js setupTests.js 不要な記述削除 削除したファイルが読み込んでいる記述も削除していきます。 App.js  //修正前----------------------------------------------------------- import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } //修正後----------------------------------------------------------- function App() { return ( <p>適当な文字列を入れます</p> ); } export default App; index.js //修正前----------------------------------------------------------- import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); //修正後----------------------------------------------------------- import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); これでファイル側の準備はOKです。 ローカルサーバーのセッティング Node.jsのライブラリJSON Serverを使います。 yarn add json-server --dev package.jsonと同じ階層にdb.jsonを作成します。 db.json { "todos":[ { "id": 1, "content": "ローカルで使えるサーバーを用意する", "done": true }, { "id": 2, "content": "サーバーで利用するデータを作成する", "done": false }, { "id": 3, "content": "用意したデータの表示を確認する", "done": false } ] } データができたらyarn startで起動させます。 次にサーバーを起動させます。 npx json-server --watch db.json --port 3100 --watchをつけるとdb.jsonの更新を監視してくれます。 --port 3100で任意のポート番号を指定できます。 http://localhost:3100/todosに接続することでdb.jsonに作成したJSONデータがブラウザに表示されると思います。 サーバーと通信できるようにする HTTP通信を行うためのNode.jsのライブラリaxiosをインストールします。 GETやPOSTのHTTPリクエストを使ってデータの取得や更新ができます。 yarn add axios インストールが終わったあと下記のように記述することで利用できます。 定義したtodoDataUrlを利用してデータの取得や更新追加を行います。 App.js import axios from 'axios'; const todoDataUrl = 'http://localhost:3100/todos';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QiitaAPIでVRタグが付いた記事の投稿推移を調べてみた。

今回が初投稿になります! つたない記事ではございますが、最後までお付き合いいただけると嬉しいです。。 さて、初めて投稿するにあたって何を書こうかとなったのですが、そもそも自分があまりQiitaを知らない!ということで、QiitaAPIで情報取得しながら遊んでみたいと思います。 何をするのか? 今回は、QiitaAPIで「VR」のタグが付いた記事の投稿日時を取得し、「VR」タグが付いた記事がどのような頻度で投稿されているのかを可視化します。また可視化したうえで、投稿頻度が高かった時期に「VR」関連のイベントはどんなことがあったのか、影響はあるのか考察していきたいと思います。 環境 環境については以下の通りです。 ◆実行環境 Node.js 16.10.0 ◆パッケージ npm 7.24.0 axios 0.23.0 ◆QiitaAPI ・GET /api/v2/tags ・GET /api/v2/tags/:tag_id ・GET /api/v2/tags/:tag_id/items ◆コーディング ・Visual Studio Code 1.61.0 いざ実施! まずはタグに関する情報をQiitaAPIから引っ張ってみたい。。 ということで調べてみた結果、以下のコードを実行してみました。 tags.js const axios = require('axios'); async function main() { let response = await axios.get("https://qiita.com/api/v2/tags"); console.log(response); } main(); 結果はこんな感じになりました笑 { status: 200, statusText: 'OK', headers: { date: 'Wed, 13 Oct 2021 12:09:20 GMT', 'content-type': 'application/json; charset=utf-8', 'transfer-encoding': 'chunked', connection: 'close', server: 'nginx', 'x-frame-options': 'SAMEORIGIN', 'x-xss-protection': '1; mode=block', 'x-content-type-options': 'nosniff', 'x-download-options': 'noopen', 'x-permitted-cross-domain-policies': 'none', 'referrer-policy': 'strict-origin-when-cross-origin', link: '<https://qiita.com/api/v2/tags?page=1>; rel="first", <https://qiita.com/api/v2/tags?page=2>; rel="next", <https://qiita.com/api/v2/tags?page=7684>; rel="last"', 'total-count': '153676', etag: 'W/"1c4a8a4b35dda6ecd5e3273ab3291f4b"', 'cache-control': 'max-age=0, private, must-revalidate', 'rate-limit': '60', 'rate-remaining': '57', 'rate-reset': '1634129821', vary: 'Origin', 'x-request-id': 'f7a5c892-64fa-4a1f-b664-f5b644d2c5fa', 'x-runtime': '0.342109', 'strict-transport-security': 'max-age=2592000' }, config: { transitional: { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }, adapter: [Function: httpAdapter], transformRequest: [ [Function: transformRequest] ], transformResponse: [ [Function: transformResponse] ], timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, validateStatus: [Function: validateStatus], headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/0.22.0' }, method: 'get', url: 'https://qiita.com/api/v2/tags', data: undefined }, request: <ref *1> ClientRequest { _events: [Object: null prototype] { abort: [Function (anonymous)], aborted: [Function (anonymous)], connect: [Function (anonymous)], error: [Function (anonymous)], socket: [Function (anonymous)], timeout: [Function (anonymous)], prefinish: [Function: requestOnPrefinish] }, _eventsCount: 7, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, destroyed: false, _last: true, chunkedEncoding: false, shouldKeepAlive: false, maxRequestsOnConnectionReached: false, _defaultKeepAlive: true, useChunkedEncodingByDefault: false, sendDate: false, _removedConnection: false, _removedContLen: false, _removedTE: false, _contentLength: 0, _hasBody: true, _trailer: '', finished: true, _headerSent: true, _closed: false, socket: TLSSocket { _tlsOptions: [Object], _secureEstablished: true, _securePending: false, _newSessionPending: false, _controlReleased: true, secureConnecting: false, _SNICallback: null, servername: 'qiita.com', alpnProtocol: false, authorized: true, authorizationError: null, encrypted: true, _events: [Object: null prototype], _eventsCount: 10, connecting: false, _hadError: false, _parent: null, _host: 'qiita.com', _readableState: [ReadableState], _maxListeners: undefined, _writableState: [WritableState], allowHalfOpen: false, _sockname: null, _pendingData: null, _pendingEncoding: '', server: undefined, _server: null, ssl: [TLSWrap], _requestCert: true, _rejectUnauthorized: true, parser: null, _httpMessage: [Circular *1], [Symbol(res)]: [TLSWrap], [Symbol(verified)]: true, [Symbol(pendingSession)]: null, [Symbol(async_id_symbol)]: 3, [Symbol(kHandle)]: [TLSWrap], [Symbol(kSetNoDelay)]: false, [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: null, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kCapture)]: false, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0, [Symbol(connect-options)]: [Object], [Symbol(RequestTimeout)]: undefined }, _header: 'GET /api/v2/tags HTTP/1.1\r\n' + 'Accept: application/json, text/plain, */*\r\n' + 'User-Agent: axios/0.22.0\r\n' + 'Host: qiita.com\r\n' + 'Connection: close\r\n' + '\r\n', _keepAliveTimeout: 0, _onPendingData: [Function: nop], agent: Agent { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, defaultPort: 443, protocol: 'https:', options: [Object: null prototype], requests: [Object: null prototype] {}, sockets: [Object: null prototype], freeSockets: [Object: null prototype] {}, keepAliveMsecs: 1000, keepAlive: false, maxSockets: Infinity, maxFreeSockets: 256, scheduling: 'lifo', maxTotalSockets: Infinity, totalSocketCount: 1, maxCachedSessions: 100, _sessionCache: [Object], [Symbol(kCapture)]: false }, socketPath: undefined, method: 'GET', maxHeaderSize: undefined, insecureHTTPParser: undefined, path: '/api/v2/tags', _ended: true, res: IncomingMessage { _readableState: [ReadableState], _events: [Object: null prototype], _eventsCount: 3, _maxListeners: undefined, socket: [TLSSocket], httpVersionMajor: 1, httpVersionMinor: 1, httpVersion: '1.1', complete: true, rawHeaders: [Array], rawTrailers: [], aborted: false, upgrade: false, url: '', method: null, statusCode: 200, statusMessage: 'OK', client: [TLSSocket], _consuming: true, _dumped: false, req: [Circular *1], responseUrl: 'https://qiita.com/api/v2/tags', redirects: [], [Symbol(kCapture)]: false, [Symbol(kHeaders)]: [Object], [Symbol(kHeadersCount)]: 44, [Symbol(kTrailers)]: null, [Symbol(kTrailersCount)]: 0, [Symbol(RequestTimeout)]: undefined }, aborted: false, timeoutCb: null, upgradeOrConnect: false, parser: null, maxHeadersCount: null, reusedSocket: false, host: 'qiita.com', protocol: 'https:', _redirectable: Writable { _writableState: [WritableState], _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _options: [Object], _ended: true, _ending: true, _redirectCount: 0, _redirects: [], _requestBodyLength: 0, _requestBodyBuffers: [], _onNativeResponse: [Function (anonymous)], _currentRequest: [Circular *1], _currentUrl: 'https://qiita.com/api/v2/tags', [Symbol(kCapture)]: false }, [Symbol(kCapture)]: false, [Symbol(kNeedDrain)]: false, [Symbol(corked)]: 0, [Symbol(kOutHeaders)]: [Object: null prototype] { accept: [Array], 'user-agent': [Array], host: [Array] } }, data: [ { followers_count: 0, icon_url: null, id: '満足,柔軟性', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'gfgs', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'chem', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'Shaquille', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'レプリケーションファクタ', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'suttonbankcashapp', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'nhavuonto', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'アカシックエンジン', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'Akashic-Engine', items_count: 2 }, { followers_count: 0, icon_url: null, id: 'ニコ生ゲーム', items_count: 2 }, { followers_count: 0, icon_url: null, id: '二重ルーター', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'ブリッジモード', items_count: 1 }, { followers_count: 0, icon_url: null, id: 'PetriNet', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'フレキシブルサーバー', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'Soundproofing', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'Soundproofing,', items_count: 0 }, { followers_count: 0, icon_url: null, id: 'Chobiit', items_count: 0 }, { followers_count: 0, icon_url: null, id: '#QiitaAPI', items_count: 1 }, { followers_count: 0, icon_url: null, id: '#COBOL', items_count: 1 } ] } ずらっと並んだ文字の羅列にびっくりしましたが、よくよく調べてみると叩いたAPIは「タグ一覧を作成日時の降順で返す。」とのこと。 確かに新しい記事についているタグ情報が返ってきているようです。 また、responseを出力するときに返ってきた情報をすべて指定したため、余計な情報も出力してしまいました。 気を取り直して、、「VR」タグに関する記事だけ取ってこれないか調べた結果 ・GET /api/v2/tags/:tag_id を見つけました。 「:tag_id」の部分は取得したいタグ名を記載します。 それを踏まえて先ほどのコードを修正したものがこちらです。 VR_tags.js const axios = require('axios'); async function main() { let response = await axios.get("https://qiita.com/api/v2/tags/VR"); console.log(response.data); } main(); 先ほどのコードと比べ、responseの出力部分も修正しています。 console.log(response.data); 結果はこんな感じに。 { followers_count: 561, icon_url: 'https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/61c94f5abe866a439f9f3574153ceb1a3ea84d47/medium.jpg?1591884698', id: 'VR', items_count: 1035 } だいぶすっきりしました笑 こちらは「VR」のフォロー人数とタグ付けされた記事数(item_count)が取得できるみたいです。 以下のURLに記載された情報と同じものになります。 さて、タグを絞り込んで情報を取得することはできましたが、これだけでは何もできません。。 当初の目的であった、「VR」タグが付いた記事の投稿頻度を調べるため試行錯誤いたしました。 最後はこちらのAPIを使用してみたいと思います。 ・GET /api/v2/tags/:tag_id/items VR_tags.js const axios = require('axios'); const fs = require("fs"); async function main() { var csv = ""; let response1 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=1&per_page=100"); for (var dataA of response1.data){ csv += dataA.created_at; csv += ","; } let response2 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=2&per_page=100"); for (var dataB of response2.data){ csv += dataB.created_at; csv += ","; } let response3 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=3&per_page=100"); for (var dataC of response3.data){ csv += dataC.created_at; csv += ","; } let response4 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=4&per_page=100"); for (var dataD of response4.data){ csv += dataD.created_at; csv += ","; } let response5 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=5&per_page=100"); for (var dataE of response5.data){ csv += dataE.created_at; csv += ","; } let response6 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=6&per_page=100"); for (var dataF of response6.data){ csv += dataF.created_at; csv += ","; } let response7 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=7&per_page=100"); for (var dataG of response7.data){ csv += dataG.created_at; csv += ","; } let response8 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=8&per_page=100"); for (var dataH of response8.data){ csv += dataH.created_at; csv += ","; } let response9 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=9&per_page=100"); for (var dataI of response9.data){ csv += dataI.created_at; csv += ","; } let response10 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=10&per_page=100"); for (var dataJ of response10.data){ csv += dataJ.created_at; csv += ","; } fs.writeFile('out.csv', csv, (err, data) => { if(err) console.log(err); else console.log('write end'); } ); } main(); だいぶ変わりましたがざっと変更点についてお伝えします。 let response1 = await axios.get("https://qiita.com/api/v2/tags/VR/items?page=1&per_page=100"); こちらは「GET /api/v2/tags/:tag_id/items」のAPIをたたくためのコードになります。 APIの機能として「指定されたタグが付けられた記事一覧を、タグを付けた日時の降順で返します。」とのことであり、まさしく私が求めていたAPIでした! また「?page=1&per_page=100」の部分ですがオプション扱いになります。 Qiita APIでは結果に対して、ページという単位で分割して返すようになっています。 この時に使うオプションが「per_page」と「page」で、「per_page」では1ページ辺りにいくつの結果を含むかを最大100まで指定できます。 「page」ではその中の何ページ目を取得するかを指定します。 今回は1000件分の結果を取得するために10回繰り返しています。 fs.writeFile('out.csv', csv, (err, data) => { if(err) console.log(err); else console.log('write end'); } ); こちらは結果出力用のコードになります。 「out.csv」というファイルに結果を出力します。 1000件分の結果を取得するのと同時に結果をグラフ化したかったのでファイル出力できるように対応してみました! 結果&考察 最後のコードを実行した結果は無事「out.csv」ファイルに出力されました! 結果をグラフ化し、私が調べたVR関連の主要イベントを記載したものが以下の画像になります。 こうしてみると、極端に投稿数が増えている月があるのが分かります。 しかし主要なVR関連のイベントの時期とはズレていたので、投稿数と主要イベントとの間には相関関係は見られませんでした。 まとめ 今回、初めてJavaScriptやAPIといったものに触ってみました。 APIという言葉はよく耳にしていましたが、それがどういったものなのかわからず今まで来てしまいました。ただ実際に触ってみるととても実感がわきました。各サービスが提供しているAPIを上手に使うことで、さまざまなデータが容易に手に入るこの機能は汎用性や発展性が高く、話題になるのも納得です。 またQiitaでの記事投稿は初めてでしたが、Markdown記法による執筆も初めてでとても新鮮でした。 今後も記事投稿を行っていきたいと思いますので、このMarkdown記法にも慣れていきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiita APIから取得したJsonをPower BIに読み込ませる

Qiita API を使って記事を取得し、記事をAPIで取得してJSON形式で出力。 そのファイルをPower BI というMicrosoftのBIツールにインポートして分析してみました。 なお、今回はQiitaのトップコントリビューターの10名の神々を対象に実行しています。 利用したもの API API Qiita API v2 実行環境 Windows 10 Home Node.js axios BIツール Power BI Desktop 書いたコード //ライブラリの読み込み const axios = require('axios'); //APIをたたくaxios const fs = require("fs"); //標準出力のfs // 文字コード const charset = 'utf8'; //出力ファイルの文字コード const result = './Result/'; //出力先のフォルダ var response;   //GETの受け皿 var jsonArray = new Array(); var i = 0; //神々 const Gods = [ "jnchito", "rana_kualu", "hirokidaichi", "suin", "drken", "icoxfog417", "baby-degu", "Yametaro", "mpyw", "opengl-8080" ]; //メイン処理 async function main() { //forをネストするのも手間だし、1人あたり3回たたけば全件取得できるので、ヨシッ!! for(let userId of Gods){ //page1 response = await axios.get( 'https://qiita.com/api/v2/users/'+ userId +'/items?page=1&per_page=100' ); jsonArray.push(response); //page2 response = await axios.get( 'https://qiita.com/api/v2/users/'+ userId +'/items?page=2&per_page=100' ); jsonArray.push(response); //page3 response = await axios.get( 'https://qiita.com/api/v2/users/'+ userId +'/items?page=3&per_page=100' ); jsonArray.push(response); } //json形式でファイル出力!! for(let res of jsonArray){ fs.writeFile(result + i +".json" , JSON.stringify(res.data, null, " "), charset, (err, data) => { if(err) console.log(err); else console.log('write end'); } ); i++; } } //メイン実行 main(); 結果 json形式での出力はしっかりできました ツリーマップ きれいだなぁ。。。(棒) 散布図と回帰直線 コメントとLGTMとの間の相関は強くはなさそう? 躓きポイント 短いコードながらJavaScript初心者の私には難しいポイントがいくつかありました。 1. 型推論 Javascirptの型推論に悪戦苦闘。便利なようで使いこなすには時間がかかりそうです。 axios.get()の戻り値の型がわからず、メソッドをうまく呼び出せませんでした。 Typeof()で調べて公式リファレンスと小一時間ほど格闘しました。 2. Itterable いてらぶる....。「反復可能」と訳すようです。Google大先生にご教授いただきました。 要は配列っぽいオブジェクトのことですよね。 配列ではないものをfor...of文に渡そうとしてTypeErrorが発生してしまいました。 javascriptの組み込み型では、String, Array, TypedArray, Map, Setが該当するそうです。 3. 標準出力 Power BI やその他のツールに連携するためにも、やはりファイルへの書き出しはマスターしておきたいところ。 しかし、fn.writefile()メソッドの使い方から始まり、文字コード、Json形式...と調べることはてんこ盛りでした。 4.Power BI に複数のファイルを取り込む Power BI は、SQLやExecelファイルなど多くのデータソースを取り込むことができます。 しかし、複数のファイルの一括で読み込むには工夫が必要です。 終わりに 今回はJavaScriptを学習したアウトプットとして、Qiita APIを利用したデータの取得と分析にチャレンジしてみました。 分析はイマイチですが、「APIで情報を取得し分析の準備を整える」まではできたのでご容赦ください。 また、Power BIは基本的に無料で提供されています。 使いきれないほどの豊富な機能が備わっていて、なおかつ無料ですのでぜひ触ってみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】 親子コンポーネント間のライフサイクルフック

Vue.js 親子コンポーネント間のライフサイクルフック はじめに ・Vue.jsの単一コンポーネントのライフサイクルフックはこちらの記事などにもあるように、実装をするうえで避けては通れない概念です。 ただ、親子間のライフサイクルについて、あまり言及されている記事が無く、実装中に詰まった箇所がありましたので、こちらでまとめようと思います。 前提 Vue.jsのライブラリとしてVuetityを使用。 起こった事象 下記のような親子コンポーネントを想定する。 parent.ts <template> : <v-row> <v-expansion-panels accordion> <v-expansion-panel @click="openAcordion()"> <v-expansion-panel-header>XXXX</v-expansion-panel-header> <v-expansion-panel-content> <Child :reportText.sync="reportText" :parentsParams="parentsParams" /> </v-expansion-panel-content> </v-expansion-panel> </v-expansion-panels> </v-row> : </template> <script> private mounted (): void { this.parentsParams = 'thisIsTest' } </script> Child.ts <script> mounted (): void { this.getTextArea() } //バック側のAPIにパラメータをpostする処理を記述 ~~~~~ </script> ■改修点 親コンポーネントを、アコーディオンメニューではなく、普通に表示をしたい。 v-row以下を下記のように修正したい parent.ts <v-row> <v-col> <Child :reportText.sync="reportText" :parentsParams="parentsParams" /> </v-col> </v-row> ■エラーの内容 バック側にpostしたとき、親コンポーネントで定義しているはずのparentsParamsがnullだとエラーが出る。 (実際にAPIへpostをしているのはchildコンポーネント側) ■エラーの原因 子コンポーネントではparentsParamが定義されていないため なぜ、parentsParamが定義されていなかったのか? →親子間のライフサイクルの違い ◆親子間のライフサイクルフック 親created ↓ 子created ↓ 子mount ↓ 親mount 上のように、親のmount時に定義しても、それより早く子の方がmountされるため、上記のようなエラーが出た。 改修時にこのエラーが起こったのは、 改修前はアコーディオンメニューを使っており、アコーディオンメニューボタンクリック時に 子コンポーネントが読み込まれる。つまり初期表示時のライフサイクルの影響を受けていなかった。 対策 子コンポーネントでmountされるよりも前に(つまり、親のcreated時など)値を定義する。 親子間でのデータ受け渡しがされている値は、どのタイミングで定義されているか、を常に意識しておく必要がある。 定義されているよりも前に使うとエラーになる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む