配列操作でよく使うメソッドまとめ04
前回の配列操作でよく使うメソッドまとめ03つづき。
今回も前回までと同じJSONデータを使ってObject.assignの使い方についてまとめます。
JSONデータの中身は配列操作でよく使うメソッドまとめ01のページ下部に載っています。
Object.assignを使えば簡単に対象のオブジェクトのシャローコピーを行うことができます。
ディープコピーをしたい場合はObject.assignではない方法で行うのでそれについても記載します。
各チームメンバーの配列をシャローコピーする
import data from '../EasternConference.json'
let menberArry = data.map(v => v["menbers"]) console.log(Object.assign({}, menberArry)) return Object.assign({}, menberArry)
Object.assignは第一引数に空のオブジェクト、第二引数にターゲットオブジェクトを設定すれば、ターゲットオブジェクトをシャローコピーしたオブジェクトを返してくれます。
第一引数に空オブジェクトではないオブジェクトを指定した場合は、オブジェクト同士がマージされますし、オブジェクト同士に同一のキーがあれば、第二引数側の値が使われます。(ただ上書きしてるだけ)
気を付けるポイントとしては、Object.assignはundefinedやnullを許容するので例外は投げられません。
ただ、実際にこのメソッドを使う場面では元のオブジェクト自体にundefinedやnullが含まれていることは少ないんじゃないかなぁと思います。(元のオブジェクト生成時点で例外処理や置換処理が行われていてほしい)
各チームメンバーの配列をディープコピーする
import data from '../EasternConference.json'
let menberArry = data.map(v => v["menbers"]) const deepCopyObj = JSON.parse(JSON.stringify(menberArry)) console.log(deepCopyObj) return deepCopyObj
ディープコピーするには組み込みオブジェクトであるJSONオブジェクトを使用して以下の手順を踏めばディープコピーできます。
まずstringifyメソッドでJavaScriptのオブジェクトをJSON形式の文字列に変換します。(シリアライズ化)
つぎにparseメソッドでJavaScriptのオブジェクトに戻します。(デシリアライズ化)
参考資料
Object.assign()
配列操作でよく使うメソッドまとめ03
前回の配列操作でよく使うメソッドまとめ02つづき。
今回も前回までと同じJSONデータを使ってfilterの使い方についてまとめます。
JSONデータの中身は配列操作でよく使うメソッドまとめ01のページ下部に載っています。
各チームのUS出身の選手のみ取り出す
import data from '../EasternConference.json'
let menberArry = data.map(v => v["menbers"]) let countryFilterArry = [] for(let i = 0; i < menberArry.length; i++) { countryFilterArry.push(menberArry[i].filter(el => el.from == "US")) } console.log(countryFilterArry) // フィルタリングした結果 console.log(menberArry) // フィルタリングする前の配列のまま return countryFilterArry
まずフィルタリングした結果を格納するための配列を準備しておきます。
let countryFilterArry = []
for文の中でfilter関数を実行しています。filter関数の引数には各チームのメンバーオブジェクトが入ります。
filter関数はfromがUSと一致するメンバーオブジェクトのみを返すので、それを最初に準備しておいたcountryFilterArryに格納して最後にreturnすれば完了です。
ソース内だと以下の部分
for(let i = 0; i < menberArry.length; i++) { countryFilterArry.push(menberArry[i].filter(el => el.from == "US")) } console.log(countryFilterArry) // フィルタリングした結果 console.log(menberArry) // フィルタリングする前の配列のまま return countryFilterArry
filterメソッドは破壊的なメソッドではないので元の配列のデータには影響を及ぼしません。
配列操作でよく使うメソッドまとめ02
前回の配列操作でよく使うメソッドまとめ01つづき。
今回は前回のJSONデータを使ってsortの使い方についてまとめます。
JSONデータの中身は前回のページ下部に記載しています。
各チームの選手を年齢順にソート
import data from '../EasternConference.json'
let menberArry = data.map(v => v["menbers"]) for(let i =0; i < menberArry.length; i++) { menberArry[i].sort((a, b) => a.age - b.age) console.log(menberArry[i]) }
for文の中でsort関数を実行しています。sort関数は引数に比較関数を渡してその結果に応じて昇順/降順が決まります。
ソース内だと以下の部分
sort((a, b) => a.age - b.age)
これは単純にオブジェクトのキーを指定して、引き算をしているだけです。この引き算の戻り値が正か負かを気にすれば良いだけです。
戻り値が負なら昇順、正なら降順になります。
今回は昇順で良いのでこの順番ですが、降順にしたければ以下のようにすれば良いです。
sort((a, b) => b.age - a.age)
名前の長さ順なら以下
sort((a, b) => a.name.length - b.name.length)
ちなみにsortは破壊的メソッドになるため元データの並び順が変わります。
元データを壊さずソートした配列を使いたい場合は以下のようにsliceを使って元のデータのコピーをソートすれば良いです。
sliceで配列をコピーするのでコピー用の配列sliceArryを作ってそれにpushしていきます。
let menberArry = data.map(v => v["menbers"]) let sliceArry = [] for(let i =0; i < menberArry.length; i++) { sliceArry.push(menberArry[i].slice().sort((a, b) => a.name.length - b.name.length)) }
これでmenberArryを壊すことなくsortした結果をsliceArryに格納することができました。
また、sliceを使う方法はシャローコピーになるためsliceArryのデータを変更すると元データも影響を
受けます。
それを避けたい場合はディープコピーして元データに影響がないようにする必要がありますので、シャローコピーされていることを頭の片隅に置いておくと良いかもしれません。
配列操作でよく使うメソッドまとめ01
仕事で配列を扱うことが多くなったので配列の操作方法をまとめました。
現場ではAPIで取得したデータをフロント側で扱いやすいようにJSONにパースしたり加工することが多いと思うので、今回は基本的な計算処理についてまとめます。
今回は実際にNBAの複数のチーム全体の平均年齢を算出、各チームの平均年齢を算出の2点を実際に行います。
ページ下部に今回使っているJSONデータを載せておきます。
JSONをインポートさせて実際にソース書いていきます。
import data from '../EasternConference.json'
複数のチーム全体の平均年齢を算出
let ageArry = data.map((v) => v["menbers"]).reduce((a, b) => a.concat(b), []) let result = Math.round(ageArry.map((v) => v["age"]).reduce((a,b) => a + b, 0) / ageArry.length) return result // 26
まずmapでmenberのみの配列を作って(二次元配列になるので注意)、扱いやすいようにreduceで二次元配列を一次元配列に戻します。
一次元配列に戻した配列を今度はageのみの配列にして、reduceで足しあげて平均を出せば完了です。
各チームの平均年齢を算出
let ageArry = data.map(v => v["menbers"]) let perAgeArry = [] for(let i = 0; i < ageArry.length; i++ ) { perAgeArry.push({...data[i], "ageAverage": Math.round(ageArry[i].map(v => v["age"]).reduce((a, b) => a + b, 0) / ageArry[i].length)}) } return perAgeArry // // 元のJSONに"ageAverage": n を追加した配列
全体の平均を求めるときと違うのは、スプレッド構文を使って元のJSONデータに「チームごとの平均年齢」を追加していることです。
そのために新しい配列をひとつ準備しておき、for文で回す際にその配列にチームの平均年齢をpushしていきます。
最後にその配列を返せば完了です。
一番下にJSON載せておきます。(今回これを用意するのに一番時間かかりました...国コードも載せてあるので出身地ごとに割合出したりとか後でやれたらと思います)
参考資料
Array.prototype.map()
Array.prototype.reduce()
[ { "id": 1, "team": "Boston Celtics", "menbers": [ { "name": "Kadeem Allen", "age": 25, "from": "US" }, { "name": "Aron Baynes", "age": 32, "from": "AU" }, { "name": "Jabari Bird", "age": 24, "from": "US" }, { "name": "Jaylen Brown", "age": 22, "from": "US" }, { "name": "Jonathan Gibson", "age": 31, "from": "US" }, { "name": "Gordon Hayward", "age": 28, "from": "US" }, { "name": "Al Horford", "age": 32, "from": "DM" }, { "name": "Kyrie Irving", "age": 26, "from": "US" }, { "name": "Shane Larkin", "age": 26, "from": "US" }, { "name": "Marcus Morris", "age": 29, "from": "US" }, { "name": "Semi Ojeleye", "age": 24, "from": "US" }, { "name": "Terry Rozier", "age": 24, "from": "US" }, { "name": "Xavier Silas", "age": 30, "from": "US" }, { "name": "Marcus Smart", "age": 24, "from": "US" }, { "name": "Jayson Tatum", "age": 20, "from": "US" }, { "name": "Daniel Theis", "age": 26, "from": "DE" }, { "name": "Guerschon Yabusele", "age": 23, "from": "FR" } ] }, { "id": 2, "team": "Brooklyn Nets", "menbers": [ { "name": "Quincy Acy", "age": 28, "from": "US" }, { "name": "Jarrett Allen", "age": 20, "from": "US" }, { "name": "DeMarre Carroll", "age": 32, "from": "US" }, { "name": "Allen Crabbe", "age": 26, "from": "US" }, { "name": "Spencer Dinwiddie", "age": 25, "from": "US" }, { "name": "Milton Doyle", "age": 25, "from": "GH" }, { "name": "Jared Dudley", "age": 33, "from": "US" }, { "name": "Kenneth Faried", "age": 29, "from": "US" }, { "name": "Joe Harris", "age": 27, "from": "US" }, { "name": "Rondae Hollis-Jefferson", "age": 23, "from": "US" }, { "name": "Caris LeVert", "age": 24, "from": "US" }, { "name": "Timofey Mozgov", "age": 32, "from": "RU" }, { "name": "Jahlil Okafor", "age": 23, "from": "US" }, { "name": "D'Angelo Russell", "age": 22, "from": "US" }, { "name": "Nik Stauskas", "age": 25, "from": "CA" }, { "name": "James Webb III", "age": 25, "from": "US" }, { "name": "Isaiah Whitehead", "age": 23, "from": "US" } ] }, { "id": 3, "team": "New York Knicks", "menbers": [ { "name": "Ron Baker", "age": 25, "from": "US" }, { "name": "Trey Burke", "age": 26, "from": "US" }, { "name": "Damyean Dotson", "age": 24, "from": "US" }, { "name": "Tim Hardaway, Jr.", "age": 26, "from": "US" }, { "name": "Isaiah Hicks", "age": 24, "from": "US" }, { "name": "Jarrett Jack", "age": 35, "from": "US" }, { "name": "Enes Kanter", "age": 26, "from": "TR" }, { "name": "Luke Kornet", "age": 23, "from": "US" }, { "name": "Courtney Lee", "age": 33, "from": "US" }, { "name": "Emmanuel Mudiay", "age": 22, "from": "CD" }, { "name": "Frank Ntilikina", "age": 20, "from": "FR" }, { "name": "Kyle O'Quinn", "age": 28, "from": "US" }, { "name": "Kristaps Porziņģis", "age": 23, "from": "LV" }, { "name": "D'Angelo Russell", "age": 22, "from": "US" }, { "name": "Lance Thomas", "age": 30, "from": "US" }, { "name": "Noah Vonleh", "age": 23, "from": "US" }, { "name": "Troy Willams", "age": 24, "from": "US" } ] }, { "id": 4, "team": "Philadelphia 76ers", "menbers": [ { "name": "Jimmy Butler", "age": 29, "from": "US" }, { "name": "Wilson Chandler", "age": 31, "from": "US" }, { "name": "Joel Embiid", "age": 24, "from": "CM" }, { "name": "Markelle Fultz", "age": 20, "from": "US" }, { "name": "Amir Johnson", "age": 31, "from": "US" }, { "name": "Furkan Korkmaz", "age": 21, "from": "TR" }, { "name": "Mike Muscala", "age": 27, "from": "US" }, { "name": "T. J. McConnell", "age": 26, "from": "US" }, { "name": "Justin Patton", "age": 21, "from": "US" }, { "name": "Jacob Pullen", "age": 29, "from": "US" }, { "name": "J. J. Redick", "age": 34, "from": "US" }, { "name": "Ben Simmons", "age": 22, "from": "AU" } ] }, { "id": 5, "team": "Toronto Raptors", "menbers": [ { "name": "OG Anunoby", "age": 21, "from": "GB" }, { "name": "Lorenzo Brown", "age": 28, "from": "US" }, { "name": "Danny Green", "age": 31, "from": "US" }, { "name": "Serge Ibaka", "age": 29, "from": "ES" }, { "name": "Kawhi Leonard", "age": 27, "from": "US" }, { "name": "Kyle Lowry", "age": 32, "from": "US" }, { "name": "Alfonzo McKinnie", "age": 26, "from": "US" }, { "name": "C. J. Miles", "age": 31, "from": "US" }, { "name": "Malcolm Miller", "age": 25, "from": "US" }, { "name": "Greg Monroe", "age": 28, "from": "US" }, { "name": "Norman Powell", "age": 25, "from": "US" }, { "name": "Malachi Richardson", "age": 22, "from": "US" }, { "name": "Pascal Siakam", "age": 24, "from": "CM" }, { "name": "Jonas Valančiūnas", "age": 26, "from": "LT" }, { "name": "Fred VanVleet", "age": 24, "from": "US" }, { "name": "Delon Wright", "age": 26, "from": "US" } ] } ]
HTTP通信のこと
今までよくわかっていなかったHTTP通信周りの知識を得るためにこちらの本を読みました。
初版が2010年なのでほんの一部だけ古い内容などもありますが、全体を通してすごく面白くて一気読みできましたし、とても勉強になりました。
よくわからないところは流し読みしてだいたい5時間程度で読めた感じです。
自分が知りたかった内容が網羅されていたので個人的にはとても満足できる一冊でした。
こちらの本で学習したことを備忘録として簡単にまとめていきます。
※分かりやすく説明するために簡単にまとめていますので、誤解を招く表現などありましたらご指摘いただけると嬉しいです!!
Webを支える技術 ―― HTTP,URI,HTML,そしてREST WEB+DB PRESS plus
- 作者: 山本陽平
- 出版社/メーカー: 技術評論社
- 発売日: 2018/11/14
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
URIってなに?
- Web上にあるリソースを一意に識別するためのIDのこと
- Uniform Resource Identifierの略
URIの構成
- スキーム + ホスト名 + パス + クエリ
- この構成は代表的な構成で、ほかにも色々な構成がある
/* スキーム http:// ホスト名 news.google.com パス / クエリ ?hl=ja&gl=JP&ceid=JP:ja */ https://news.google.com/?hl=ja&gl=JP&ceid=JP:ja
HTTPってなに?
- Web上でリソースのやりとりをするときに使うプロトコル
- これを使えばHTML、XML、画像、Javascript、各種オフィス、PDFなど何でも転送できる
- TCP/IPベースのプロトコル
- TCP/IPについてはこのあとに書く
- アーキテクチャがクライアントサーバ
- クライアントからリクエストを投げる→サーバがそれを受けてレスポンスを返す
- ステートレス
- TCP/IPの説明のときに書く
TCP/IPとは
ネットワーク層(ここで使用されるプロトコルはIP)でパケットを送信する。IPを使って宛先を特定する。
トランスポート層(ここで使用されるプロトコルはTCPまたはUDP)でコネクション張る。(ネットワーク上の接線)
コネクションとセッションを混同しないための以下を覚えておく。
- コネクションはネットワークレイヤーの話でセッションはWebの話。
- ネットワークレイヤーの上位階層がWebのレイヤーになる。
つまりHTTPとセッションは関係ない(=HTTPにはセッションの概念がない)
ここでステートレスの話とつながってくる。
そもそもステートレスが何かというと「アプリケーションの状態を持たないこと(=セッション状態を持たないこと)」
セッションについて分かりやすく言うとECサイトでログインして買い物してログアウトするまでが1セッション。
ログイン~ログアウトまでが1セッションなので、この一連の流れ(クライアントが何をリクエストしたかという情報)を持たないってこと。
この間の「ログイン情報」とか「買い物かごの中身」を保持するためにECサイト(ECサイトのサーバ)がセッションIDを作ってCookieとしてHTTPリクエストに含める。
これによりHTTPはステートレスでも「状態」を保持したリクエストをサーバに送れるので問題なくクライアントサーバでやり取りできる。
このおかげでクライアントが「情報を保持してリクエスト」できるし、サーバは「ステートレスなのでスケールが簡単に行える」。(=クライアントが情報を保持しているため、どのサーバにリクエストが送られても関係なく対応できる)
クライアントサーバとは
HTTPメッセージ
リクエストとレスポンスはHTTPメッセージと総称される。
HTTPメッセージ(リクエスト)の構成
- リクエストライン
- ヘッダ
- ボディ
HTTPメッセージ(レスポンス)の構成
- ステータスライン
- ヘッダ
- ボディ
後日HTTPメッセージのヘッダについてまとめようと思います!
ここまで読んでいただいてありがとうございました!!!
Promise基礎
仕事でNode.jsを使うことになって非同期処理でPromiseやasync/awaitをよく使うので今回はPromiseについてまとめました。
async/awaitについては別の機会にまとめようと思います。
Promiseの特徴は4つあります。
- Promiseオブジェクトは3つの状態を持っている(Pending, fulfilled, rejected)
- 状態は変化する(Pending -> fulfilled or rejected)
- Promiseの状態によってその後の処理が変化する
- then()で処理をチェーンできる(callback地獄にならない)
Promiseの使い方
return new Promise((resolve, reject) => {do something})
でPromiseオブジェクトを作ってreturnさせる関数を用意する(作ったときは状態はPending)- その関数を呼び出して状態変化させたPromiseオブジェクトを次の処理に渡す
- 次の処理ではPromiseオブジェクトの状態によって処理が分岐する(以下のソースでは
console.log(result)
orconsole.log(error)
)
※Promiseの使い方補足
Promiseオブジェクトはresolve
の処理が行われるとfulfilled
になる
Promiseオブジェクトはreject
の処理が行われるとrejected
になる
以下のソースのpmFnc(true)
が呼ばれるとPromiseオブジェクトはresolve
を実行してfulfilled
になる
Promise(pmFnc(true)
)の結果はコールバック関数の引数(.then(result => ...
)で受け取れる(resultのこと)
function pmFnc(result) { return new Promise ((resolve, reject) => { if(result) resolve('success!') reject('failure...') }) } pmFnc(true) .then(result => { console.log(result) }) .catch( error => { console.log(error) }) console.log('1st comment')
結果は以下
1st comment success!
簡単にですがPromiseについてまとめました!
次はasync/awaitについてまとめたいと思います。
読んでいただいてありがとうございました!!
Express基礎
復習しながら自分用にmdでメモってましたがせっかくなので備忘録としてこちらに残します。
2018.11月からB2BのSaaSに転職したことでNode.jsを書く機会があるのでNodeの学習を再開しました。
前に学習していたときから時間が空いて色々忘れている部分も多かったのでexpress-generatorの雛形+過去に自分が書いた記事を見て復習中です。
Node.jsについて書いた過去のエントリはこちら
- Node.jsを触ってみた話
- Node.jsを触ってみた話2
- Node.jsを触ってみた話3
- Node.jsを触ってみた話4
- Node.jsの基本構文1
- Node.jsの基本構文2
- Node.js+Expressでgetを使う!
- Node.js+Expressでpostを使う!
- Node.jsでファイルを読む
- Node.jsの標準モジュールquerystringについて
- Node.jsのストリームAPIについて
- Microsoft Azureの無料アカウントの作成
- Node.js+Express+Azureでチャットアプリをデプロイしてみる
- Node.js+Express+Azureでチャットアプリをデプロイしてみる2
- Cloud Speech API + node.jsで音声認識をさせてみる1
- Cloud Speech API + node.jsで音声認識をさせてみる2
- Cloud Speech API + node.jsで音声認識をさせてみる3
Express自体
ルーティングとミドルウェアのWebフレームワーク
Expressはリクエストが来たら上から評価される
Expressアプリケーション
一連のミドルウェア関数呼び出し
(連続してミドルウェア関数を呼び出すことで成り立っている)
ミドルウェア関数
以下の3つ所有
- requestオブジェクト
- responseオブジェクト
- request-responseサイクルにおける次のミドルウェア関数に対するアクセス権
※次のミドルウェア関数はnext
という変数名であらわされる
実行できる4つのタスク
- 任意のコードを実行
- requestオブジェクト、responseオブジェクトを変更
- request-responseサイクルを終了する
- スタック内の次のミドルウェアを呼び出す
※現在のミドルウェア関数がrequest-responseサイクルを終了しない場合はnext()
を使って次のミドルウェア関数に制御を渡さなければならない。そうしないとrequestはハングしたままになりガーベジコレクションの対象にならない
Expressアプリケーションで使用できる4タイプのミドルウェア
- アプリケーション・レベル / ルーター・レベルのミドルウェア → 引数にマウントパスとミドルウェア関数を渡せる。
- エラー処理ミドルウェア
- 標準装備のミドルウェア
- サード・パーティー・ミドルウェア
※アプリケーション・レベル / ルーター・レベルの違いはミドルウェアがどのインスタンスにマウントされるかだけ。
アプリケーション・レベル →const app = express();
ルーター・レベル →const router = express.Router();
アプリケーション・レベルのミドルウェアについて
以下の例はapp.use()
を使って/
にミドルウェアをマウントしている
* /
にミドルウェアのサブスタックが形成されたことになる
* /
のパスは設定しなくてもよくて、設定しない場合はアプリケーションがリクエストを受け取るたびに実行される
* next('route')
を使うことで次のルートに制御を渡している
※app.use()だとnext('route')使えないので注意する(app.METHOD()もしくはrouter.METHOD()で使用可能)
/* GET home page. */ app.get('/', function (req, res, next) { if (req.params.id === undefined) { next('route') }else { next() } }, function (req, res, next) { res.render('index', { title: 'Express02' }); }) app.get('/', function (req, res, next) { res.render('index', { title: 'Express' }); })
エラー処理ミドルウェアについて
- 引数は
(err, req, res, next)
の4つでなければいけない - 他の
app.use()
とroutes呼び出し後に定義する(最後に定義するということ) - エラーハンドリング用に
app.use()
を使ってエラー処理関数を定義する - エラー処理関数で
next
を呼ばないときはレスポンスを記述する必要がある
※next(err)
を呼び出した時点で、エラー処理関数だけ実行され他のミドルウェア関数は実行されない
エラーを発生させる
var createError = require('http-errors');
を使ってエラーを発生させる
エラーコードは以下ページ参照
https://www.npmjs.com/package/http-errors
app.use('/', function(req, res, next) { next(createError(404)); }); app.use(function(err, req, res, next) { console.log(err) // NotFoundError: Not Found console.log(err.message) // Not Found console.log(err.status) // 404 // request-responseサイクルのローカル変数(res.locals)はクライアントにレスポンスを返すまで参照することができる // req.app.get('env')で環境を検出 res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; //レスポンスのステータスコードを指定 res.status(err.status || 500); res.render('error'); });
また、express-generatorの雛形のapp.jsのソースを色々調べて備忘録コメント残したものが以下です。
// Create HTTP errors for Express, Koa, Connect, etc. with ease. var createError = require('http-errors'); // The path module provides utilities for working with file and directory paths var path = require('path'); var express = require('express'); var cookieParser = require('cookie-parser'); // サード・パーティー・ミドルウェア var logger = require('morgan'); // HTTP request logger middleware for node.js. サード・パーティー・ミドルウェア var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); console.log(__dirname) // D:\localhost\node\www app.use(logger('dev')); // ログの出力フォーマットを指定 app.use(express.json()); // This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser.JSONペイロードで受信したリクエストを解析する app.use(express.urlencoded({ extended: false })); // This is a built-in middleware function in Express. It parses incoming requests with urlencoded payloads and is based on body-parser.URLエンコードされたペイロードで受信したリクエストを解析する app.use(express.static(path.join(__dirname, 'public'))); // This is a built-in middleware function in Express. It serves static files and is based on serve-static.HTMLファイルや画像などの静的リソースを提供する app.use(cookieParser()); app.use('/', indexRouter); app.use('/users', usersRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app;
以下のサイトを参考にさせていただきました。
Express - Node.js web application framework
ミドルウェアの使用 | Express 入門 | CODE Q&A 問題解決 [日本語]
一通り復習が終わったらVue+Nodeで何かwebアプリを作りはじめたいと思います。
ここまで読んでいただいてありがとうございました!!