つくる ポートフォリオアプリを仕上げる
このレッスンで分かること
- 覚えておく値は 1 か所に置き、描き直しは 1 本の関数にまとめます
- しぼり込みと並び替えを、元の配列を壊さずに掛け合わせます
同じことを 2 か所が覚えると、必ずどちらかが古くなる
このコースの総仕上げです。API の応答を再現した固定データを取り、公開ずみだけを選び、タグでしぼり、並び替え、問い合わせフォームの入力を確かめるところまでを 1 枚に集めます。既習の処理を多く使い、タグの判定に使う書き方を短い例で確認します。ばらばらに練習したものを同居させても壊れない形に置くことが主題です。
まず、このアプリが覚えておく必要のあるものは 3 つだけです。届いた作品の全部と、いま選ばれているタグと、いま選ばれている並び順。ただし後ろの 2 つは select 要素そのものが覚えているので、別の変数に写す必要はありません。
JavaScript
// 危ない形。選択が変わるたびに 2 か所を直さないと食い違う
let currentLevel = "all";
levelSelect.addEventListener("change", function () {
currentLevel = levelSelect.value;
});写した瞬間に、正しい値がどちらなのか分からなくなります。select の値がほしいときは、そのつど .value を読めば済みます。
今回の決まりは次のとおりです。
- 公開ずみの作品だけを、選ばれたタグでしぼる
- 表示回数の多い順、少ない順、タイトル順の 3 通りで並べ替える
- しぼり込みか並び順が変わったら、一覧と件数を出し直す
- 通信に失敗したら、一覧を空にして理由を出す
- 送信されたら、名前が空でないことと、メールアドレスの形を確かめる
どちらが変わっても、描き直しは 1 本
しぼり込みと並び替えを別々のイベントで別々に画面へ反映すると、タグを変えたら並び順が既定に戻った、という組み合わせの不具合が出ます。どちらが変わっても同じ 1 本を呼ぶ形にすると、この種の不具合はそもそも起きません。
JavaScript
levelSelect.addEventListener("change", paintSkills);
orderSelect.addEventListener("change", paintSkills);その 1 本が呼ぶ「いま出すぶんを決める関数」は、画面を触りません。覚えている配列と 2 つの select を読んで、配列を返すだけにします。しぼるのは filter、並べるのは sort ですが、sort は元の配列を書き換えるので、写しを作ってから掛けます。
JavaScript
const picked = allSkills.filter(function (skill) {
return skill.visible;
});
const sorted = [...picked];
sorted.sort(function (a, b) { return b.months - a.months; });filter が新しい配列を返しているので上の例は無事ですが、覚えている配列に直接 sort を掛けると、もとの順番は二度と戻りません。写しを作る癖を付けておくほうが安全です。日本語の名前順にしたいときは a.name.localeCompare(b.name, "ja") を使います。演習の比較関数は用意済みです。
失敗した画面と、入力を止める画面
fetch は 404 でも 500 でも catch に入りません。response.ok を自分で見て、false なら投げます。そして catch の中では、一覧を空にし、件数を 0 に戻してから理由を出します。前回の表示が残っていると、利用者には古いデータが最新に見えるからです。
入力の検証は送信の直前に 1 回だけ行います。見つけたエラーは配列にためて、最後に 1 回で出してください。
JavaScript
const errors = [];
if (name === "") {
errors.push("お名前を入力してください");
}1 つ見つけた時点で return すると、直すたびに次のエラーが出てくる形になります。名前も空でメールも変、という場合に両方を伝えられるほうが親切です。
よくある間違い
- 描き直しの前に空にし忘れる — しぼり込みを切り替えるたびにカードが下へ積み上がります。件数の表示だけが正しく、画面と食い違います
awaitを書かずにresponseを使う —responseは箱なのでokはundefinedです。undefinedは falsy なので、通信が成功していても必ずエラー側に入ります。いつも読み込めませんでしたと出るときは、まずここを疑ってください
今回は 2 か所だけをつなぐ
既習の処理は完成済みです。まず visibleWorks で選択と並び替えを組み合わせ、次に loadWorks で届いた配列を保存して共通の render を呼びます。フォームの検証と表示は既存コードを読んで試します。
配列の includes は値が含まれるかを返し、|| はどちらかの条件が true なら true になります。別の題材なら次の形です。
JavaScript
const level = "all";
const picked = courses.filter((course) => level === "all" || course.levels.includes(level));
function compareCourses(a, b) { return b.hours - a.hours; }
picked.sort(compareCourses);sort には比較関数そのものを渡せます。用意済みの compareWorks が画面の並び順を読み、比較に使う数値を返します。render は選ばれた配列からカードと件数を描き直します。
固定の応答データ
API_DATA は API が返す { count, works } の形です。用意済みの fetchWorks は 300 ミリ秒後にこのデータを返し、外部通信は行いません。以前のデータと同じく「学習ログ」は非公開です。非公開の表示回数 0 を少ない順の先頭に含めません。
| 作品 | 表示回数 | 公開 | タグ |
|---|---|---|---|
| プロフィールサイト | 120 | true | HTML、CSS |
| 自己紹介カード | 48 | true | HTML |
| スキル一覧の表 | 32 | true | HTML |
| 作品ギャラリー | 76 | true | JavaScript |
| 学習ログ | 0 | false | JavaScript |
取得したのは 5 件、初めに描くのは公開中の 4 件です。CSS にしぼると 1 件、すべてに戻して表示回数の少ない順にすると「スキル一覧の表」が先頭になります。
課題
- visibleWorks の仮の return [] を置き換える。allWorks から published が true のものを選び、tag が all ならすべて、そうでなければ tags.includes(tag) の作品だけ残す
- filter が作った新しい配列を sort(compareWorks) で並べ替えて返す。比較の補助は views-desc、views-asc、title を画面の選択値から判断する
- loadWorks の TODO で data.works を allWorks に保存し、そのあと render() を呼ぶ。render は最新の visibleWorks の結果を描き、件数を更新する
- 固定データは 5 件中 4 件が公開中。学習ログは非公開で出さない。最初はプロフィールサイトが先頭、少ない順ではスキル一覧の表が先頭、CSS ではプロフィールサイト 1 件になる
- カード生成、取得、失敗画面、フォーム検証、change と submit の接続は完成済みなのでそのまま使う