こんにちは!新卒2期生として7月からWeb開発チームに配属されました。河原です(^^)
今回の記事が初投稿です!!入社前に読んでいたブログをまさか自分が執筆する側になるとは思っていなかったので、今すごく嬉しいです。よろしくお願いいたします!
今回WowTalkのサンクス新機能の開発に携わらせていただいたので、そこで勉強になったことを初学者の視点で、まとめていきたいと思います。
はじめに
開発に携わったサンクス新機能ですが、実はどんな機能なのかまだ発表できません...!ですので、今回は代わりにWowTalkのサンクス機能についてご紹介します!
WowTalkのサンクス機能について
WowTalkのサンクス機能は主に3つの機能から成り立っています。
レター機能
日頃のちょっとした場面や大事な場面で仲間にありがとうを送る仕組みです。レターを読むと何だかほっこりします。サンクススコア機能
WowTalk内でありがとうの気持ちがどれほど伝えられているか、自分を中心としてサンクススコアとして可視化されます。ポイント機能
自分がありがとうを送った分だけポイントが貯まります。現在貯めたポイントは新しいレターのデザインと交換できます。すごく可愛いデザインのレターがたくさんあり、どれと交換しようか迷ってしまいます。
「ありがとう」で幸せな働き方の実現をサポートする
心あたたまる機能がこのサンクス機能です!
サンクス機能開発を通して勉強になったこと
それでは、今回サンクス機能の開発で勉強になったことを、自身の復習も兼ねてフロントエンド、API実行部分、バックエンド開発に分けて、まとめていきたいと思います!(勉強になったことがたくさんありすぎて、どれを取り上げて記事にしようか非常に迷いました(笑))
フロントエンド部分での学び
【Redux・Redux-toolkitについて】
学生時代に本当に少しだけReact
をかじっただけの私。state
の管理といえば、useState
!useState
はとっても便利!という感覚でしたが、いざ既存コードを見ると、Redux-toolkit
というものが。なんだこれは。いきなりパニック。
useState
でもこんなに便利なものがあるのか〜と感動していましたが、Redux-toolkit
は更にデータを管理するのに便利な仕組みが盛りだくさんでした!
【ReduxとRedux-toolkitの違い】
弊社のサンクス機能の開発で使われているのはRedux-toolkit
です。これはRedux
をより簡単に扱えるようにしたものです。
【useStateを用いたデータ操作のイメージ】
useState
はstate
を簡単に管理することができる機能です。state
はprops
と異なり、自由にデータを書き換えることができます。useState
の状態管理はそのコンポーネント内で完結するという特徴があります。
graph TB id1((ItemModal<br/><br/>itemModalVisible<br/>true/false)) id2((MyMenu<br/><br/>LoginCount<br/>1,2...))
【propsを用いたデータの受け渡しのイメージ】
useState
は同じコンポーネント内のみでデータを保持するため、コンポーネント間でのデータの受け渡しではprops
を用います。props
の特徴として、親コンポーネントから子コンポーネントに渡される値であり、子コンポーネントから親コンポーネントの値を直接操作することはできないといった制約があります。
graph TB A("App")--props-->B("ShopMenu")--props-->D("ItemModal") A("App")--props-->C("MyMenu")--props-->E("PointModal")
この図の場合、MyMenu
からItemModal
へのデータの受け渡しや、ItemModal
からPointModal
へといった、親子関係のないデータ同士の受け渡しはできません。
またprops
は親コンポーネントから値を受け取っているだけなので、子コンポーネントで値を変更して、親コンポーネントに値を返す...といったことはできません。
graph BT D("ItemModal")--X NG X-->B("ShopMenu") E("PointModal")--X NG X-->C("MyMenu")
【Reduxを用いたデータの受け渡しのイメージ】
しかし、Redux
はコンポーネントの外で状態管理をするため、どのコンポーネントからでもデータがアクセスでき、簡単にデータを管理することができます!!コンポーネント間の関係性を考えなくて良いのはとても便利でした。
【Reduxを用いたデータの受け渡しのイメージ】
graph TB A("Redux")--useSelecter-->B("ShopMenu")-->A("Redux") A("Redux")-->C("MyMenu")-->A("Redux") A("Redux")-->D("ItemModal")-->A("Redux") A("Redux")-->E("PointModal")--useDispatch-->A("Redux")
今回のサンクス新機能での開発では、フロントに表示させるデータを親子関係のないコンポーネント間で変更させるという作業が多かったのですが、Redux-toolkit
を利用したことで、コンポーネントの繋がりを気にすることなく、どのコンポーネントからでもuseSelecter()
やuseDispatch()
を使ってアクセスできました!これは自分の中で革命だったと思います。
Redux
を組み合わせることで、React
がまたさらに使いやすくなるのですね...!!すごいです!
★Redux・Redux-toolkitの使い方を執筆するにあたり以下の記事を参考にさせていただきました。ありがとうございました。
API実行部分開発での学び
フロント部分の開発を進め、新しい画面ができていくことに喜んでいた私ですが、その後先輩に作成していただいたAPI
を使って、サーバーからデータを取得することを先輩に勧められました。
Fetch API
?非同期処理
?Promise
?なんだそれは???わからない言葉だらけで、またまたパニックになりました(笑)色々と試行錯誤を繰り返し、先輩に作っていただいたAPI
から初めてデータを取ることができた、あの何とも言えない感動はすごかったです...!
ですのでこの項ではFetch API
の使い方についてまとめ直し、得た知識を改めて確認しようと思います。
【Promiseについて】
ではここで、Fetch API
の説明で出てくるPromise
について復習しておきます。Promise
は、非同期処理の最終的な完了、もしくは失敗を表すオブジェクトです!
【そもそも非同期処理とは何か?】
非同期処理とは、処理結果を待たずして次の処理に進める処理のことです。
もし仮にコードの途中に非常に処理に時間のかかるコードがあったとします。順番通りに行う同期処理では、その処理が終わるのを待たなくてはいけないため時間がかかります。
しかし、非同期処理はその処理が終わる前に次の処理を実行できるため、時間短縮につながりユーザビリティ向上が期待できます。
【Promiseの引数について】
Promise
はresolve
と、reject
の2つの関数を引数に取ります。
- resolve:処理が成功した時のメッセージを表す
- reject:処理が失敗した時のメッセージを表す
その後、then
を使ってコールバック処理を実行します。
【then(),catch()について】
- then():処理が成功した場合(
resolve
)の処理を実行する。 - catch():処理が失敗した場合(
reject
)の場合の処理を実行する。何だか、try...catch文
みたいです!
ここまでの流れを図にしてみると...
graph LR A("Promise")--"成功"-->B("resolve(data)")--"dataを渡せる"-->D(".then(data)") A("Promise")--"失敗"-->C("reject(error)")--"errorを渡せる"-->E(".catch(error)")
ここまでの流れをコードで見てみると...
let getItemsInfo = new Promise((resolve, reject) => { //このコードは3000ミリ秒後、つまり3秒後に実行される setTimeout(function(){ const data = [ {name: "炎の剣", price:500}, {name: "氷の剣", price:500}, {name: "雷の剣", price:500}, {name: "鉄の鎧", price:1000}, {name: "薬草", price:50}, {name: "すごい薬草", price:100}, ] //コメントを外すとエラー処理が実行される // if(data.length > 5){ // //今回手動でエラーを投げているため、rejectを使わなくても自動的に // // catchに飛ぶのでこのような表記になっている。基本的にはreject()を記載すること // throw "アイテムの数が多すぎるよ!持ちきれない!"; // } resolve(data); }, 3000); }).then((data) => { console.log(data); //【成功したら】dataの中身が出力 }).catch((err)=> { console.log(err); //【失敗したら】Uncaught アイテムの数が多すぎるよ!持ちきれない!と出力 }); // console.log("先に出力されるよ");
★Promiseの使い方を執筆するにあたりこちらの記事を参考にさせていただきました。ありがとうございました。
【Fetch APIについて】
Fetch
の意味を和訳すると、読み込む、とってくるという意味がヒットします。Fetch API
はその意味の通り、外部のAPI
にリクエストを送信し、データを取得することができるインターフェイスです。
では早速、API
からデータを取得してみましょう!
まずはリクエストを発行します。
//先輩に作っていただいたAPIからデータをとるぞー! fetch("https://〇〇〇.sample.php");
み...見えない...!!(´·ω·`)ショボーン
Promise
が結果で出力されているため、中身が見えません。
ですので後ろにthen
を繋げて必要な処理を書いていきましょう!
エラー処理もされるようにcatch
も書いて...と。
fetch("https://〇〇〇.sample.php") // Response オブジェクトから JSON の本文の内容を抽出する .then(response => { return response.json(); }) // Promiseのデータの中身を取り出す .then(data => { console.log(data); //おまけ for(const element of data){ console.log(`${element.name}は${element.age}歳です!`); } }) // エラーを検出したら、catchに処理が入る .catch(error => { console.log(error); });
先輩のAPI
から無事data
が取れました!わーい!!(^○^)
これがFetch API
でのデータ取得の流れです!
★Fetch APIについて執筆するにあたりこちらの記事を参考にさせていただきました。ありがとうございました。
バックエンド部分での学び
当初の目標としてはフロントエンドのみの開発だったのですが、せっかくAPI
からデータを取得できたので、今度はAPI
の開発にもチャレンジしてみないかと先輩からご提案をいただき、なんと今回、バックエンドの開発にも少しだけ携わらせていただきました!(うれしい!)
フロント以外の部分はほとんど触れたことがなかったので、わからないことだらけでしたが、こうしてアウトプットする機会をいただけたので、少しでも多く学びとして残せるよう、頑張って執筆してみようと思います!チャレンジです!
バックエンドはnode.js
でAWS Lambda関数
を書きました!
【エラー処理について】
念願の技術開発部での開発の初仕事ということもあり、舞い上がっていた私。とにかく何としてもまずは動くようにということにばかり意識を取られてしまい、最初先輩にコードレビューをいただいた際にエラー処理に不十分な部分がたくさんあるとご指摘いただきました。まさしく私の性格を体現したような猪突猛進コードだったと思います...(笑)
ここからは開発途中に特に躓いた部分を取り上げます。
【アクセス途中でのエラー】
例えばデータベースに接続してItemdata
を取得するとします。
【Itemdata
のイメージ】
itemId | name | price |
---|---|---|
1 | 炎の剣 | 500 |
2 | 氷の剣 | 500 |
3 | 雷の剣 | 500 |
4 | 鉄の鎧 | 1000 |
5 | 薬草 | 50 |
6 | すごい薬草 | 100 |
このとき、データベースにアクセスする際に、エラーが起きてしまい正しくデータが取れないかもしれません。 もしこの時にエラーメッセージを出さないと、なぜアプリが動かないのかがわからないため、お客様が混乱してしまいます。
const getItemdataHandler = () => { // getItemdata()でItemdataを取得できるとする const res = await getItemdata(); if (res !== false) { return createResponse(res); } else { //resの中にきちんと値が入っていない場合はエラーを出す return createErrorResponse("getItemdataHandler err"); } };
これでもし通信が失敗しても、エラーメッセージが表示されるようになりました!
【予期していないデータが入る可能性】
ここで先ほどのItemdata
ですが、このように当初の予定とは異なったデータが入ってしまう可能性もあります。
【予期しない値が入ったItemdata
のイメージ】
itemId | name | price |
---|---|---|
1 | 炎の剣 | 500 |
2 | 氷の剣 | 500 |
3 | 雷の剣 | 500 |
4 | 鉄の鎧 | 1000 |
5 | 薬草 | undefined |
6 | すごい薬草 | 100 |
薬草のprice
がundefined
になってしまっています。
この状態でもしitem
を購入しようとしてしまうと...
let yourMoney = 1000; const buy = (id) => { const item = Itemdata.find(item => item.itemId === id); console.log(`${item.name}を購入した!`); yourMoney = yourMoney - item.price console.log(`今の所持金は${yourMoney}円です`); //薬草を購入した! //今の所持金はNaN円です と出力 } buy(5);
本来数値が入って欲しい部分にNaN
が入ってしまいました...!!(T T)
このまま処理を進めてしまうと、さまざまなエラーが起きてしまいます。
また、今回はundefined
でしたが、もしnull
が入った状態でこのコードを走らせてしまうと
薬草を購入した! 今の所持金は1000円です
と表示されてしまいます。今度はタダで買えてしまう...!これはまずいです(^^;
ですので、適切な値が入っているかチェックするコードを追加します。
let yourMoney = 1000; const buy = (id) => { const item = Itemdata.find(item => item.itemId === id); if(item.price === undefined || item.price === null){ console.log("priceの値が不適切です!"); }else{ console.log(`${item.name}を購入した!`); yourMoney = yourMoney - item.price console.log(`今の所持金は${yourMoney}円です`); } } buy(5); // priceの値が不適切です!と出力
【そもそもデータがない可能性】
Itemdataそのものが空の場合
今Itemdata
の中にデータがあるものだと仮定してコードを書いていましたが、Itemdata
に、もしもデータが入っていなかった場合にもエラーが起きてしまいます。
let yourMoney = 1000; const buy = (id) => { // lengthで空かどうかを判定、エラー処理を追加。 if(Itemdata.length === 0){ console.log("Itemデータがありません"); }else{ const item = Itemdata.find(item => item.itemId === id); if(item.price === undefined || item.price === null){ console.log("priceの値が不適切です!"); }else{ console.log(`${item.name}を購入した!`); yourMoney = yourMoney - item.price; console.log(`今の所持金は${yourMoney}円です`); } } } buy(3);
これで万が一Itemdata
の中身が空でも処理は止まりませんね!よかった、よかった。ε-(´∀`*)ホッ
指定されたitemId
のデータがない場合
Itemdata
のデータは6つ、つまりitemId
は6までしかありませんが、もしここで存在しないitemId
を指定した時にもエラーが起きてしまいます!(こちら先輩にご指摘いただき、気がつきました。気をつけていてもやはり見落としがありますね!勉強になります(^^)!)
それでは早速エラー処理を書いていきましょう!
const buy = (id) => { if(Itemdata.length === 0){ console.log("Itemデータがありません"); }else{ const item = Itemdata.find(item => item.itemId === id); //指定されていないidを指定した場合、itemの中身はundefinedになるのでエラー処理を追加 if(item === undefined){ console.log("指定したデータがありません"); }else{ if(item.price === undefined || item.price === null){ console.log("priceの値が不適切です!"); }else{ console.log(`${item.name}を購入した!`); yourMoney = yourMoney - item.price; console.log(`今の所持金は${yourMoney}円です`); } } } } buy(7); // 範囲外のitemIdを指定した場合は指定したデータがありませんと表示。
できました!と思いきやあれ...?なんだかコードが読みにくくなってきたような...?(゜∀゜;)コンナハズデハ
それもそのはず。ネストが深くなってしまったのです...!ガード節を使い修正します。
コードを修正!
const buy = (id) => { if(Itemdata.length === 0){ console.log("Itemデータがありません"); return; } const item = Itemdata.find(item => item.itemId === id); if(item === undefined){ console.log("指定したデータがありません"); return; } if(item.price === undefined || item.price === null){ console.log("priceの値が不適切です!"); return; } console.log(`${item.name}を購入した!`); yourMoney = yourMoney - item.price; console.log(`今の所持金は${yourMoney}円です`); } buy(4);
先ほどのコードよりもすっきりしたかなと思います!よかったです!
お客様にご利用していただくサービスだからこそ、処理の止まらない、エラーが起きた際はエラーが発生したのだとわかるようなコードを書くことの大切さを学びました。
終わりに
最後までお読みいただきありがとうございました! たくさん挑戦できる環境をいただけ、すごく嬉しかったです! いつもたくさんご指導いただきありがとうございます!もっともっと知識を吸収して、早く一人前のエンジニアになるのが目標です。これからも頑張ります!(^^)