新しい言語を学ぶ時に一番初めにやることがTODOアプリの作成だと思います。
制作に1カ月以上かかった理由は、「育児のスキマ時間でやったから」「TypeScriptやTailWindwCSSの知識不足」の2点です。
どうすれば効率が良かったかなど勉強する際に参考になれば幸いです。
【こんな方にオススメ!】
・TypeScriptを勉強しようと考えているが何からしていいのかわからない方
・TypeScriptの理解を深めたい方
TypeScriptと一緒にNext.jsは学ぶな
前提知識がある場合は問題ありません。
ただ、どちらも初めて学ぶ場合は一緒に学習するのをやめた方がいいと考えています。
セットで語られることが多い2つの言語ですが、全く初めての言語を2つ以上学ぶとエラーの修正に時間がかかります。
前提知識がないと何が原因でエラーになっているのかを特定するのにも時間がかかってしまうためです。
絶対にダメな理由はありません。
エラーの解消など開発に時間がかかっていると感じてからでもいいので学習方法を切り替えることをオススメします!
なぜTODOアプリ作るのか?
理由は「基本機能を実装できるから」です。
具体的には、「追加」「削除」「更新」「絞り込み」「並べ替え」などのプログラミングにおいて基本的な処理を全て記述することができるからです。
高度な機能を追加することも可能なので、自分のレベルに合わせて実装要件をカスタマイズできることも良い点です。
私は下記の要件で作成しました。
- TODO の追加
- TODO の削除
- TODO の完了の有無(チェック)
- TODO の編集
- サブタスクの追加
- 詳細の記述
- 期限の設定
加えて、画面をリロードしても状態が保持されるようにしました。
TypeScriptを勉強して良かった
TypeScriptだけに絞って勉強したことでNext.jsと一緒に学習した時よりも勉強率や理解度が上がりました。
副次的な効果としてJavaScriptの解像度があがりました。
TypeScriptはJavaScriptの型があるバージョンぐらいにしか考えてなかったのですが、もっと早くTypeScriptを勉強しておけば良かったと後悔しました…
詳しい話は別の記事にまとめましたので興味があればご覧ください!
実際の実装
実装した制作物の詳細を紹介します。
見慣れるのが恥ずかしくなるコードですが、githubに上げておきましたので詳細はそちらからご覧ください。
今回CSSには時間をかけたくなかったのでTailWindCSSを利用しました。
TailWindCSSについては別の記事にまとめましたのでよろしければご覧ください!
ディレクトリ構造
.
├── dist
│ ├── bundle.js
│ └── style.css
├── node_modules
├── src
│ ├── script
│ │ └── common.ts
│ └── stylesheet
│ └── style.css
├── webpack
│ ├── webpack.config.js
│ ├── webpack.dev.js
│ └── webpack.prod.js
└── package.json
└── index.html
└── tailwind.config.js
設定
各種設定ファイルの記述は以下のとおりです。
{
"devDependencies": {
"autoprefixer": "^10.4.11",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss": "^8.4.16",
"postcss-loader": "^7.0.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.8",
"ts-loader": "^9.3.1",
"typescript": "^4.8.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.0",
"webpack-merge": "^5.8.0"
},
}
モジュールは最低限のものだけで開発しました。
TypeScriptで開発したいだけならstyle関連のモジュールすら必要ないと思います。
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
entry: './src/script/common.ts',
// output先はdistフォルダ以下にします
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/'
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
}
]
},
optimization: {
minimizer: [new CssMinimizerPlugin()]
},
resolve: {
extensions: ['.ts', '.js']
},
plugins: [
new CleanWebpackPlugin(),
// mini-css-extract-pluginでCSSを別ファイルとしてコンパイルします
new MiniCssExtractPlugin({
filename: '.src/stylesheet/style.css'
})
]
};
基本設定はwebpack.config.js
に記述していきます。
webpack-merge
を利用し、webpack.dev.js
とwebpack.prod.js
にconfigで設定した内容をマージします。
const { merge } = require('webpack-merge');
const config = require('./webpack.config.js');
module.exports = merge(config, {
mode: 'development'
});
const { merge } = require('webpack-merge');
const config = require('./webpack.config.js');
module.exports = merge(config, {
mode: 'production'
});
module.exports = {
content: ['./index.html', './src/script/common.ts'],
}
webpackの詳細な設定については下記の記事にまとめたのでよろしければご覧ください。
後悔したところ
いくつか後悔した点がありますので紹介させてください。
リファクタリングもしっかりやっておく
個人開発でもチーム開発と同じぐらいの気合いで挑むことを強くオススメします。
とにかく前に進めるすタイルで実装していたのですが、不備が見つかって修正が大変でした。
vscodeのプラグインは調べておく
vscodeにはTailWindCSSにはコード記述するときに補完してくれるプラグインがあります。
当然あるでしょ!ぐらいの気持ちで開発前に探してください。
その後の開発効率が段違いです!
ハマって時間を使ったところ
わからず時間を使ってしまったところも共有します。
頭の片隅にでも記憶しておいてもらえると役に立つかもしれません。
TailWindCSSが反映されない
classを追加しても反映されない状況が発生しました。
TailWindCSSの検査の範囲設定ができてなかったことが原因でした。
module.exports = {
- content: ['./index.html'],
+ content: ['./index.html', './src/script/common.ts'],
}
JSのファイルも解析対象にすることでscriptによって動的に生成されるコンテンツもスタイルが当たるようになりました。
リテラル文の型付け
リテラル構文を要素に持つ変数の型付けに苦戦しました。
下記のように技術するのが正解かはわかりませんが下記のように記述することでいったん解決しました。
const modalBody: string = `
<label
class="block text-gray-700 text-sm font-bold mb-1"
data-id="${id}"
id="js-edit-label"
>
現在のタイトル
</label>
`;
最初は下記のように定義していました。
type modalBody = `
<label class="block text-gray-700 text-sm font-bold mb-1">
${string}
</label>
`;
const modalBody: modalBody = `
<label
class="block text-gray-700 text-sm font-bold mb-1"
data-id="${id}"
id="js-edit-label"
>
現在のタイトル
</label>
`;
当然ですが、classが変更になるために型定義を修正することになり型定義の意味がなくなります。
string
として定義してしまうのは微妙な気がしましたが、これ以上良い方法が思いつかなかったのでアドバイスしただけると幸いです。
結局これで良いのかわからない「!」での要素取得の定義
最後まで一番後ろに「!」をつけて定義して良いものかを迷っていました。
この要素は確実にあります!って補償をコードでしてしまうのは正直良くないと思いつつ先に進めないのでこれで…
const clearButton: HTMLElement = document.getElementById('js-clear-todo')!;
該当の要素が特定の条件で消えたり、出たりするわけではないので諦めましたが納得はいってません。
まとめ
とにかく完成まで持っていくことを意識したためコードの処理が複雑になってしまいました…
知識がまだ浅いのでBadケースも記述してしまっているとも感じています。
コードの複雑性に関しては、1機能ができたらリファクタリングするのを個人開発でもこれからは徹底したいと思います!
今回は比較的簡単なローカルで動くものでしたが、もう少し複雑なものでTypeScriptを記述して知見やスキルを高めたいと思います。