ちょっとした失敗の話
その1
Next.jsで開発中に.env.localファイルでアクセスポイントを管理するようにしたのだが、
全然うまくいかない。
NEXT_PUBLIC_はついてるから読み込めるはずなのだが・・・?
結論はyarn devでサーバーを立て直ししていなかったため、環境変数が読み込まれていなかった。
その2
これは失敗というか知らなかった事実。
herokuは無料かつ簡単にデプロイできるすごいやつ。
その無料プランのちょっとした落とし穴。
なんと30分利用が無いとスリープモードに入ってしまい、
再度アクセスした際に初回は時間がかかってしまうとのこと。
ちょっと前に参加したハッカソンイベントの発表で本番の発表時に上手くいかなかったのは
恐らくこのスリープモードに入ってしまっていたのだろう・・・
環境
- "ts-loader": "^7.0.3",
- "typescript": "^3.8.3",
- "webpack": "^4.43.0",
- "webpack-cli": "^3.3.11",
- "webpack-dev-server": "^3.10.3"
やること
React環境ではdotenvという環境変数を読み込むものがデフォルトで用意されている為特に気にしたことはなかったがTypeScript(JavaScript)のみで簡単なプロジェクトを作成する際にdotenvを使用してAPIキー等を隠匿する。
手順
- dotenv-webpackをインストールする。
- ルートディレクトリに.envファイルを作成し、隠匿したいものを記述する。
- webpack.config.jsに追記する。
- 対象のファイルで読み込む。
- .gitignoreに.envを追記する。
1.dotenv-webpackをインストールする
webpack使用環境ではdotenv-webpackというものがあるようなのでこちらをインストールする。
コマンドはこちら。
npm install dotenv-webpack --save-dev
package.jsonに無事追加される。
"devDependencies": {
"dotenv-webpack": "^7.0.3", //追加
"ts-loader": "^7.0.3",
"typescript": "^3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
2.ルートディレクトリに.envファイルを作成し、隠匿したいものを記述する
API_KEY ="xxxxxxxxxxxxxxxxxxxxxxxxx"
こんな感じで.envファイルを作成して記述する。
3.webpack.config.jsに追記する
const Dotenv = require('dotenv-webpack'); //追記
module.exports = {
plugins: [new Dotenv()], //追記
};
4.対象のファイルで読み込む
例としてapp.tsで読み込む
const API_KEY = process.env.API_KEY; //環境変数から読み込んでAPI_KEYという変数に格納
console.log(API_KEY)
あとはfetchやaxiosでリクエストを送るだけ。
github等にプッシュしないのであれば必要はないがpushする場合は.gitignoreに追記する。しないと.envもpushしてしまい隠匿する意味がなくなってしまう恐れあり。
たいそうなタイトルをつけている割には全然大した内容ではないです。
学習で取り組んでいるTypeScriptを実際に使って何かを作るってのと、テキストベースで設計を行ってから何かを作るのを何かに記録する事はなかったので練習に。
どんなアプリを作るか
もちろんこれが一番大事ですね。
今回はとても単純なもので、ガソリン給油をしたときにカードでの割引が適用されたのでこの仕組みをTypeScriptで書いてみようかな?と思いました。
アプリの概要
ガソリンの単価とカード使用時の割引額が決まっていて給油量を入力すると支払い金額と割引額を表示してくれる。
こだわると金額を指定して何リットル給油できるかとか、カードを使用する・しないのチェックなども盛り込めるけど、最低限にした。
表示するもの
- ガソリン単価
- 割引額
- 給油量入力フォーム
- 支払い金額計算ボタン
- 支払い金額
- 割引合計金額
この中でいくつかに分類していく。
固定で表示されるもの
これに関してはHTMLに直接書き込んでしまって良いものとする。
- ガソリン単価
- 割引額
この2つについては日によって変動するのでタイトルのみをHTMLで表示して値は定数としてどこかから入手して表示するものとする。
(今回はTSファイル内で定数に入れている。)
<span>
ガソリン単価(/1L)
</span>
こんな感じでHTMLにタイトル部分は表示しておく。
- 給油量表示
- 給油量入力フォーム
- 支払い金額計算ボタン
これはHTMLに直接書いてしまおう。
変化するもの
- ガソリン単価
- 割引額
この2つは先ほど記述した通り、日によって変動するが、変動する頻度は低いものなので定数で宣言してしまい、その定数をDOM操作で表示するようにすればよいかと。
- ガソリン給油量
これに関しては人によって毎回バラバラなので、入力された値を毎回取得する必要がありそう。
機能を実現するために何をすればよいかを考える
結論から言ってしまうと計算ボタンを押した時に入力された値を取得してそれをもとに計算して表示すればいい。
実装
コードと一緒にコメント
// 入力された給油量を入力する要素
const gasVolume: HTMLInputElement = document.getElementById(
"gas-volume"
) as HTMLInputElement;
// 金額表示ボタン クリックイベントを追加する
const priceButton = document.getElementById("price-button");
// 合計金額を表示する要素
const totalPrice = document.getElementById("total-price");
// ガソリン単価を表示する要素
const gasUnitPrice = document.getElementById("gas-unit_price");
// 割引額を表示する要素
const priceDown = document.getElementById("price-down");
// 割引金額の合計を表示する要素
const totalDisCountPrice = document.getElementById("total-discount-price");
// ガソリン自体の価格 1Lの値段
const gasPrice: number = 163;
// ガソリンの割引価格 1Lあたり3円割引
const disCount: number = 3;
// 各定数を取得して表示する為の処理
gasUnitPrice!.innerHTML = `${gasPrice}円`;
priceDown!.innerHTML = `${disCount}円`;
// ガソリン給油の合計金額をreturnする関数
// 給油量は変化するので引数として受け取る
function gasTotalPrice(volume: number): string {
return `支払い金額:${(gasPrice - disCount) * volume}円`;
}
// ガソリンの割引合計額をreturnする関数
// 給油量は変化するので引数として受け取る
function gasTotalDisCountPrice(volume: number) {
return `割引価格:${disCount * volume}円`;
}
// 入力された給油量を取得して上記の関数を実行する関数
function viewPrice() {
const volume = +gasVolume.value;
totalPrice!.innerHTML = gasTotalPrice(volume);
totalDisCountPrice!.innerHTML = gasTotalDisCountPrice(volume);
}
// 金額表示ボタンにクリックイベントを追加する。クリック時にviewPriceが実行される。
priceButton!.addEventListener("click", viewPrice);
Typescriptに慣れてないと最初の
// 入力された給油量を入力する要素
const gasVolume: HTMLInputElement = document.getElementById(
"gas-volume"
) as HTMLInputElement;
で???となる。
DOM操作を行うときは基本的に型推論が働き、HTMLElement | null
のユニオン型であると推論されている。
これで特に問題ないのだが、後程入力値(value)を取得する必要があるため、HTMLInputElementである必要がある。
as を使用してHTMLInputElementであるという風にTypeScriptに教えてあげる必要があるよう。
冗長な部分もあるが大体このような感じで何をする必要があるかを設計しながらコードを書いていくことが大事。
自作のジェネリクス関数について少し丁寧に記述してみる。
まずはコードから
function extractAndConvert<T extends object, U extends keyof T>(
obj: T,
key: U
) {
return `Value:${obj[key]}`;
}
extractAndConvert({name: 'taku'},name);
引数であるobjの型はT
Tはobject型を継承している為{ }この形であることが明示されている。
object型であるため、中身はどのような形でもよい。
keyの型はU
UはTのキーを継承している事が明示されているので、引数で渡したobjに含まれるキーの値である必要がある。
このようにジェネリクス型で制約を加えることで、柔軟に対応できるかつエラーが発生しないものが出来上がっていく。
ジェネリクスとは型の追加情報を与えるものである。
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
resolve('終わりました!');
}, 2000);
});
こちらのコードと
const promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('終わりました!');
}, 2000);
});
こちらのコード
意味は同じなのだがどちらもジェネリクスを使用してPeomise型が返すのがstringだという情報を与えているのだが、上のコードではインスタンス生成時にジェネリクスを使用していて下のコードではpromise定数自体をPromise型として定義してジェネリクスで値がstring型だと定義している。
下のコードの方が定義する時にこの型で、値はこれ!と定義している為丁寧な感じがする。というか私は今までこのような定義をしていた気がする。
上記のコードの方が記述量が少なく、より直感的にわかりやすいのでこのような書き方をするようにしたいとは思う。もう少し学習を続けてみよう。