Wowgineer Notes

WowTechエンジニアの徒然開発日記

Wowgineer Notes

〜新たな"Wow"を生み出す〜

サンクス新機能の開発を通して新しく勉強になったこと

こんにちは!新卒2期生として7月からWeb開発チームに配属されました。河原です(^^)
今回の記事が初投稿です!!入社前に読んでいたブログをまさか自分が執筆する側になるとは思っていなかったので、今すごく嬉しいです。よろしくお願いいたします!

今回WowTalkのサンクス新機能の開発に携わらせていただいたので、そこで勉強になったことを初学者の視点で、まとめていきたいと思います。

はじめに

開発に携わったサンクス新機能ですが、実はどんな機能なのかまだ発表できません...!ですので、今回は代わりにWowTalkのサンクス機能についてご紹介します!

WowTalkのサンクス機能について

WowTalkのサンクス機能は主に3つの機能から成り立っています。

  1. レター機能
    日頃のちょっとした場面や大事な場面で仲間にありがとうを送る仕組みです。レターを読むと何だかほっこりします。

  2. サンクススコア機能
    WowTalk内でありがとうの気持ちがどれほど伝えられているか、自分を中心としてサンクススコアとして可視化されます。

  3. ポイント機能
    自分がありがとうを送った分だけポイントが貯まります。現在貯めたポイントは新しいレターのデザインと交換できます。すごく可愛いデザインのレターがたくさんあり、どれと交換しようか迷ってしまいます。

「ありがとう」で幸せな働き方の実現をサポートする
心あたたまる機能がこのサンクス機能です!

サンクス機能開発を通して勉強になったこと

それでは、今回サンクス機能の開発で勉強になったことを、自身の復習も兼ねてフロントエンド、API実行部分、バックエンド開発に分けて、まとめていきたいと思います!(勉強になったことがたくさんありすぎて、どれを取り上げて記事にしようか非常に迷いました(笑))

フロントエンド部分での学び

【Redux・Redux-toolkitについて】

学生時代に本当に少しだけReactをかじっただけの私。stateの管理といえば、useStateuseStateはとっても便利!という感覚でしたが、いざ既存コードを見ると、Redux-toolkitというものが。なんだこれは。いきなりパニック。

useStateでもこんなに便利なものがあるのか〜と感動していましたが、Redux-toolkitは更にデータを管理するのに便利な仕組みが盛りだくさんでした!

【ReduxとRedux-toolkitの違い】

弊社のサンクス機能の開発で使われているのはRedux-toolkitです。これはReduxをより簡単に扱えるようにしたものです。

【useStateを用いたデータ操作のイメージ】

useStatestateを簡単に管理することができる機能です。statepropsと異なり、自由にデータを書き換えることができます。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の引数について】

Promiseresolveと、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.jsAWS 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

薬草のpriceundefinedになってしまっています。 この状態でもし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);

先ほどのコードよりもすっきりしたかなと思います!よかったです!
お客様にご利用していただくサービスだからこそ、処理の止まらない、エラーが起きた際はエラーが発生したのだとわかるようなコードを書くことの大切さを学びました。

終わりに

最後までお読みいただきありがとうございました! たくさん挑戦できる環境をいただけ、すごく嬉しかったです! いつもたくさんご指導いただきありがとうございます!もっともっと知識を吸収して、早く一人前のエンジニアになるのが目標です。これからも頑張ります!(^^)