フレームワークなしのプログレッシブWebアプリケーションの設計と構築(パート2)
公開: 2022-03-10この冒険の存在理由は、あなたの謙虚な作者をビジュアルデザインとJavaScriptコーディングの分野に少し押し込むことでした。 私が構築することに決めたアプリケーションの機能は、「やること」アプリケーションと同じでした。 これは本来の考え方の練習ではなかったことを強調することが重要です。 目的地は旅よりもはるかに重要ではありませんでした。
アプリケーションがどのように完成したかを知りたいですか? https://io.benfrain.comで携帯電話のブラウザをポイントします。
この記事で取り上げる内容の概要は次のとおりです。
- プロジェクトの設定と、ビルドツールとしてGulpを選択した理由。
- アプリケーションデザインパターンとその実際の意味。
- アプリケーションの状態を保存および視覚化する方法。
- CSSがコンポーネントにどのようにスコープされたか。
- 物事をより「アプリのように」するために採用されたUI / UXの優れた点。
- 反復によって送金がどのように変化したか。
ビルドツールから始めましょう。
ビルドツール
TypeSciptとPostCSSの基本的なツールを稼働させ、適切な開発エクスペリエンスを作成するには、ビルドシステムが必要です。
私の日常業務では、過去5年ほどの間、HTML / CSSと、程度は少ないがJavaScriptでインターフェイスのプロトタイプを作成してきました。 最近まで、私はかなり謙虚なビルドのニーズを達成するために、ほぼ排他的に任意の数のプラグインでGulpを使用していました。
通常、CSSを処理し、JavaScriptまたはTypeScriptをより広くサポートされているJavaScriptに変換し、場合によっては、コード出力の縮小やアセットの最適化などの関連タスクを実行する必要があります。 Gulpを使用することで、aplombでこれらの問題を解決することができました。
なじみのない人のために、Gulpを使用すると、ローカルファイルシステム上のファイルに対して「何か」を実行するJavaScriptを記述できます。 Gulpを使用するには、通常、プロジェクトのルートに1つのファイル( gulpfile.js
と呼ばれる)があります。 このJavaScriptファイルを使用すると、タスクを関数として定義できます。 特定のタスクを処理する、本質的に追加のJavaScript関数であるサードパーティの「プラグイン」を追加できます。
Gulpタスクの例
Gulpタスクの例としては、プラグインを使用して、オーサリングスタイルシート(gulp-postcss)を変更するときにPostCSSを利用してCSSに処理する場合があります。 または、TypeScriptファイルを保存するときにバニラJavaScript(gulp-typescript)にコンパイルします。 これは、Gulpでタスクを作成する方法の簡単な例です。 このタスクでは、「del」gulpプラグインを使用して、「build」というフォルダー内のすべてのファイルを削除します。
var del = require("del"); gulp.task("clean", function() { return del(["build/**/*"]); });
require
は、 del
プラグインを変数に割り当てます。 次に、 gulp.task
メソッドが呼び出されます。 最初の引数として文字列を使用してタスクに名前を付け(「clean」)、関数を実行します。この場合、関数は「del」メソッドを使用して、引数として渡されたフォルダーを削除します。 アスタリスク記号には、ビルドフォルダの「任意のフォルダ内の任意のファイル」を本質的に示す「glob」パターンがあります。
gulpタスクはヒープをより複雑にする可能性がありますが、本質的には、それが処理方法のメカニズムです。 真実は、Gulpを使用すれば、JavaScriptウィザードである必要はありません。 必要なのは、グレード3のコピーアンドペーストスキルだけです。
私はここ数年、デフォルトのビルドツール/タスクランナーとしてGulpを使い続けていましたが、それが壊れていない場合は 'というポリシーを採用していました。 それを修正しようとしないでください」。
しかし、行き詰まってしまうのではないかと心配でした。 陥りやすい罠です。 まず、毎年同じ場所で休暇を取り始め、その後、新しいファッショントレンドを採用することを拒否し、最終的には新しいビルドツールを試すことを断固として拒否します。
インターネットで「Webpack」についてたくさんのおしゃべりを聞いたことがあり、フロントエンドの開発者であるクールキッズの新しい乾杯を使ってプロジェクトを試すのが私の義務だと思いました。
Webpack
興味を持ってwebpack.js.orgサイトにスキップしたことをはっきりと覚えています。 Webpackとは何か、そして実際に行われていることの最初の説明は、次のように始まりました。
import bar from './bar';
何だって? イーブル博士の言葉によれば、「スコット、ここに骨を折ってくれ」。
私はそれが対処するための私自身のハングアップであることを知っていますが、「foo」、「bar」、または「baz」に言及するコーディングの説明に対する嫌悪感を開発しました。 それに加えて、Webpackが実際に何のためにあるのかを簡潔に説明することが完全に欠如しているため、おそらく私には向いていないのではないかと思いました。
Webpackのドキュメントをもう少し掘り下げて、少し不透明度の低い説明が提供されました。「基本的に、webpackは最新のJavaScriptアプリケーション用の静的モジュールバンドラーです」。
うーん。 静的モジュールバンドラー。 それは私が欲しかったものでしたか? 私は確信していませんでした。 私は読み続けましたが、読むほど、はっきりしなくなりました。 当時、依存関係グラフ、ホットモジュールのリロード、エントリポイントなどの概念は基本的に失われていました。
後でWebpackを調査した数晩、私はそれを使用するという考えを放棄しました。
私は正しい状況とより経験豊富な手で、Webpackは非常に強力で適切であると確信していますが、私の謙虚なニーズには完全にやり過ぎのように見えました。 モジュールのバンドル、ツリーの揺れ、およびホットモジュールのリロードは素晴らしいサウンドでした。 私は自分の小さな「アプリ」にそれらが必要だと確信していませんでした。
では、Gulpに戻りましょう。
変更のために物事を変更しないというテーマで、私が評価したかったもう1つのテクノロジーは、プロジェクトの依存関係を管理するためのYarn overNPMでした。 その時点まで、私は常にNPMを使用していましたが、Yarnはより優れたより高速な代替手段として宣伝されていました。 現在NPMを使用していて、すべてがOKである場合を除いて、Yarnについて言うことはあまりありません。わざわざYarnを試す必要はありません。
到着が遅すぎてこのアプリケーションを評価できないツールの1つは、Parceljsです。 設定がゼロで、ブラウザのリロードのようなBrowserSyncが戻ってきたので、それ以来、すばらしいユーティリティを見つけました。 さらに、Webpackの防御では、Webpack以降のv4は構成ファイルを必要としないと言われています。 ちなみに、私がTwitterで行った最近の世論調査では、87人の回答者のうち、半数以上がGulp、Parcel、GruntよりもWebpackを選択しました。
Gulpファイルを起動して実行するための基本的な機能で開始しました。
「デフォルト」タスクは、スタイルシートとTypeScriptファイルの「ソース」フォルダーを監視し、基本的なHTMLおよび関連するソースマップとともにbuild
フォルダーにコンパイルします。
BrowserSyncをGulpでも動作させました。 Webpack構成ファイルをどうすればよいかわからないかもしれませんが、それは私が何らかの動物であるという意味ではありませんでした。 HTML / CSSで反復している間にブラウザを手動で更新する必要があるのは、すっごく2010年です。BrowserSyncは、フロントエンドのコーディングに非常に役立つ短いフィードバックと反復ループを提供します。
これが11.6.2017現在の基本的なgulpファイルです
出荷の終わり近くでGulpfileを微調整し、ugilifyで縮小を追加した方法を確認できます。
プロジェクト構造
私のテクノロジーの選択の結果として、アプリケーションのコード編成のいくつかの要素がそれ自体を定義していました。 プロジェクトのルートにあるgulpfile.js
、 node_modules
フォルダー(Gulpがプラグインコードを格納する場所)、オーサリングスタイルシート用のpreCSS
フォルダー、TypeScriptファイル用のts
フォルダー、およびコンパイルされたコードを使用するためのbuild
フォルダー。
アイデアは、非ダイナミックHTML構造を含む、アプリケーションの「シェル」を含むindex.html
を作成し、アプリケーションを機能させるスタイルとJavaScriptファイルへのリンクを作成することでした。 ディスク上では、次のようになります。
build/ node_modules/ preCSS/ img/ partials/ styles.css ts/ .gitignore gulpfile.js index.html package.json tsconfig.json
そのbuild
フォルダーを参照するようにBrowserSyncを構成するということは、ブラウザーをlocalhost:3000
に向けることができ、すべてが順調だったことを意味します。
基本的なビルドシステムが整っていて、ファイルの編成が決まっていて、最初にいくつかの基本的なデザインが整ったので、実際にビルドするのを防ぐために合法的に使用できる先延ばしの飼料が不足していました!
アプリケーションの作成
アプリケーションがどのように機能するかの原則はこれでした。 データのストアがあります。 JavaScriptが読み込まれると、そのデータが読み込まれ、データ内の各プレーヤーをループして、各プレーヤーをレイアウトの行として表すために必要なHTMLを作成し、適切なin / outセクションに配置します。 次に、ユーザーからの対話により、プレーヤーが1つの状態から別の状態に移動します。 単純。
実際にアプリケーションを作成する場合、理解する必要のある2つの大きな概念上の課題は次のとおりです。
- 簡単に拡張および操作できる方法でアプリケーションのデータを表現する方法。
- ユーザー入力からデータが変更されたときにUIを反応させる方法。
JavaScriptでデータ構造を表現する最も簡単な方法の1つは、オブジェクト表記を使用することです。 その文は少しコンピュータサイエンスを読みます-y。 もっと簡単に言えば、JavaScript用語の「オブジェクト」はデータを保存する便利な方法です。
ioState
(In / Out Stateの場合)と呼ばれる変数に割り当てられたこのJavaScriptオブジェクトについて考えてみます。
var ioState = { Count: 0, // Running total of how many players RosterCount: 0; // Total number of possible players ToolsExposed: false, // Whether the UI for the tools is showing Players: [], // A holder for the players }
JavaScriptをよく知らない場合は、少なくとも何が起こっているのかを把握できます。中括弧内の各行は、プロパティ(またはJavaScriptの用語では「キー」)と値のペアです。 いろいろなものをJavaScriptキーに設定できます。 たとえば、関数、他のデータの配列、またはネストされたオブジェクト。 次に例を示します。
var testObject = { testFunction: function() { return "sausages"; }, testArray: [3,7,9], nestedtObject { key1: "value1", key2: 2, } }
最終的な結果として、この種のデータ構造を使用すると、オブジェクトの任意のキーを取得および設定できます。 たとえば、ioStateオブジェクトのカウントを7に設定する場合は、次のようにします。
ioState.Count = 7;
テキストをその値に設定する場合、表記は次のように機能します。
aTextNode.textContent = ioState.Count;
JavaScript側では、値の取得とその状態オブジェクトへの値の設定が簡単であることがわかります。 ただし、これらの変更をユーザーインターフェイスに反映することはそれほど重要ではありません。 これは、フレームワークとライブラリが苦痛を抽象化しようとする主要な領域です。
一般的に、状態に基づいてユーザーインターフェイスを更新する場合は、DOMのクエリを回避することをお勧めします。これは、一般的に最適ではないアプローチと見なされているためです。
In / Outインターフェースについて考えてみましょう。 通常、ゲームの潜在的なプレーヤーのリストが表示されます。 それらは、ページの下に上下に縦にリストされています。
おそらく、各プレーヤーは、チェックボックスinput
をラップするlabel
でDOMに表されます。 このように、プレーヤーをクリックすると、ラベルが入力を「チェック」するため、プレーヤーが「イン」に切り替わります。
インターフェイスを更新するために、JavaScriptの各入力要素に「リスナー」がある場合があります。 クリックまたは変更すると、関数はDOMにクエリを実行し、チェックされたプレーヤー入力の数をカウントします。 その数に基づいて、DOM内の他の何かを更新して、チェックされているプレーヤーの数をユーザーに示します。
その基本的な操作のコストを考えてみましょう。 複数のDOMノードで入力のクリック/チェックをリッスンし、DOMにクエリを実行して、チェックされている特定のDOMタイプの数を確認します。次に、DOMに何かを書き込んで、ユーザー、UI、プレーヤーの数を表示します。数えたところです。
別の方法は、アプリケーションの状態をJavaScriptオブジェクトとしてメモリに保持することです。 DOMのボタン/入力クリックは、JavaScriptオブジェクトを更新するだけで、JavaScriptオブジェクトの変更に基づいて、必要なすべてのインターフェイス変更のシングルパス更新を実行できます。 JavaScriptオブジェクトがすでにその情報を保持しているため、DOMのクエリをスキップしてプレーヤーをカウントすることができます。
それで。 状態にJavaScriptオブジェクト構造を使用することは単純に見えましたが、いつでもアプリケーションの状態をカプセル化するのに十分な柔軟性がありました。 これをどのように管理できるかという理論も十分に正しいように思われました。これは、「一方向のデータフロー」のようなフレーズがすべてであったに違いありません。 ただし、最初の本当のトリックは、そのデータへの変更に基づいてUIを自動的に更新するコードを作成することです。
良いニュースは、私がすでにこのことを理解しているよりも賢い人々がいるということです(良さに感謝します! )。 アプリケーションの黎明期から、人々はこの種の課題へのアプローチを完成させてきました。 このカテゴリの問題は、「デザインパターン」のパンとバターです。 モニカの「デザインパターン」は、最初は難解に聞こえましたが、少し掘り下げた後、すべてがコンピュータサイエンスではなく、常識に聞こえるようになりました。
デザインパターン
コンピュータサイエンス用語集のデザインパターンは、一般的な技術的課題を解決するための事前定義された実証済みの方法です。 デザインパターンは、料理レシピに相当するコーディングと考えてください。
おそらく、デザインパターンに関する最も有名な文献は、1994年の「デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素」です。これはC ++とsmalltalkを扱っていますが、概念は移転可能です。 JavaScriptについては、AddyOsmaniの「LearningJavaScriptDesignPatterns」が同様の分野をカバーしています。 こちらから無料でオンラインで読むこともできます。
オブザーバーパターン
通常、デザインパターンは、作成、構造、動作の3つのグループに分けられます。 私は、アプリケーションのさまざまな部分に関する変更の伝達に対処するのに役立つ行動的なものを探していました。
最近では、Gregg Pollackによるアプリ内での反応性の実装に関する非常に深い詳細を見て、読んでいます。 ここにあなたの楽しみのためのブログ投稿とビデオの両方があります。
「 Learning JavaScript Design Patterns
」の「オブザーバー」パターンの冒頭の説明を読んだとき、それが私にとってのパターンであると確信していました。 このように説明されています:
オブザーバーは、オブジェクト(サブジェクトと呼ばれる)がオブジェクト(オブザーバー)に依存するオブジェクトのリストを維持し、状態の変化を自動的に通知するデザインパターンです。
サブジェクトが何か面白いことが起こっていることをオブザーバーに通知する必要がある場合、オブザーバーに通知をブロードキャストします(通知のトピックに関連する特定のデータを含めることができます)。
私の興奮の鍵は、これが必要なときに自分自身を更新する何らかの方法を提供しているように見えることでした。
ユーザーが「Betty」という名前のプレーヤーをクリックして、彼女がゲームに「参加」していることを選択したとします。 UIでいくつかのことが必要になる場合があります。
- 再生回数に1を加える
- プレーヤーの「アウト」プールからベティを削除します
- プレーヤーの「イン」プールにベティを追加します
アプリは、UIを表すデータも更新する必要があります。 私が避けたいと思っていたのはこれでした:
playerName.addEventListener("click", playerToggle); function playerToggle() { if (inPlayers.includes(e.target.textContent)) { setPlayerOut(e.target.textContent); decrementPlayerCount(); } else { setPlayerIn(e.target.textContent); incrementPlayerCount(); } }
目的は、中央データが変更されたときに、DOMで必要なものを更新するエレガントなデータフローを作成することでした。
Observerパターンを使用すると、状態の更新を送信できるため、ユーザーインターフェイスが非常に簡潔になります。 これは、リストに新しいプレーヤーを追加するために使用される実際の関数の例です。
function itemAdd(itemString: string) { let currentDataSet = getCurrentDataSet(); var newPerson = new makePerson(itemString); io.items[currentDataSet].EventData.splice(0, 0, newPerson); io.notify({ items: io.items }); }
io.notify
メソッドがあるオブザーバーパターンに関連する部分。 これは、アプリケーション状態のitems
部分を変更することを示しているので、「items」の変更をリッスンしたオブザーバーを示します。
io.addObserver({ props: ["items"], callback: function renderItems() { // Code that updates anything to do with items... } });
データに変更を加えるnotifyメソッドがあり、関心のあるプロパティが更新されたときに応答するそのデータにオブザーバーが応答します。
このアプローチでは、アプリは、データのプロパティの変更を監視し、変更が発生するたびに関数を実行するオブザーバブルを持つことができます。
私が選んだオブザーバーパターンに興味がある場合は、ここで詳しく説明します。
状態に基づいてUIを効果的に更新するためのアプローチがありました。 ピーチー。 しかし、これでも2つの明白な問題が残りました。
1つは、ページのリロード/セッション全体で状態を保存する方法と、UIが機能しているにもかかわらず、視覚的には「アプリのような」ものではなかったという事実です。 たとえば、ボタンが押された場合、UIは画面上で即座に変更されます。 それは特に魅力的ではありませんでした。
まず、ストレージ側について説明します。
状態の保存
これに取り組む開発側からの私の主な関心は、アプリのインターフェースを構築してJavaScriptと対話させる方法を理解することに集中しました。 サーバーからデータを保存および取得する方法、またはユーザー認証とログインに取り組む方法は「範囲外」でした。
したがって、データストレージのニーズに合わせてWebサービスに接続する代わりに、すべてのデータをクライアントに保持することを選択しました。 クライアントにデータを保存するためのWebプラットフォームの方法はいくつかあります。 localStorage
を選択しました。
localStorageのAPIは非常にシンプルです。 次のようにデータを設定して取得します。
// Set something localStorage.setItem("yourKey", "yourValue"); // Get something localStorage.getItem("yourKey");
LocalStorageには、2つの文字列を渡すsetItem
メソッドがあります。 1つ目はデータを保存するキーの名前で、2つ目の文字列は実際に保存する文字列です。 getItem
メソッドは、localStorageのそのキーの下に格納されているものをすべて返す引数として文字列を取ります。 素晴らしくてシンプル。
ただし、localStorageを使用しない理由の中には、すべてを「文字列」として保存する必要があるという事実があります。 つまり、配列やオブジェクトなどを直接格納することはできません。 たとえば、ブラウザコンソールで次のコマンドを実行してみてください。
// Set something localStorage.setItem("myArray", [1, 2, 3, 4]); // Get something localStorage.getItem("myArray"); // Logs "1,2,3,4"
'myArray'の値を配列として設定しようとしましたが、 取得したときは、文字列として保存されていました(「1,2,3,4」の前後の引用符に注意してください)。
確かにlocalStorageを使用してオブジェクトと配列を格納できますが、文字列との間で変換する必要があることに注意する必要があります。
したがって、状態データをlocalStorageに書き込むために、次のようなJSON.stringify()
メソッドを使用して文字列に書き込まれました。
const storage = window.localStorage; storage.setItem("players", JSON.stringify(io.items));
データをlocalStorageから取得する必要がある場合、文字列は次のようなJSON.parse()
メソッドを使用して使用可能なデータに戻されました。
const players = JSON.parse(storage.getItem("players"));
localStorage
を使用するということは、すべてがクライアント上にあることを意味し、サードパーティのサービスやデータストレージの問題がないことを意味します。
データは現在、更新とセッションを永続化しています—やった! 悪いニュースは、localStorageはユーザーがブラウザデータを空にしても生き残れないということでした。 誰かがそれをしたとき、彼らのすべてのイン/アウトデータは失われるでしょう。 それは深刻な欠点です。
「localStorage」が「適切な」アプリケーションに最適なソリューションではないことを理解するのは難しいことではありません。 前述の文字列の問題に加えて、「メインスレッド」をブロックするため、深刻な作業にも時間がかかります。 KV Storageのような代替案が登場していますが、今のところ、適合性に基づいてその使用に注意するように心に留めておいてください。
ユーザーのデバイスにローカルにデータを保存することは脆弱ですが、サービスまたはデータベースへの接続は抵抗されました。 代わりに、「ロード/保存」オプションを提供することで問題を回避しました。 これにより、In / Outのすべてのユーザーが、必要に応じてアプリにロードして戻すことができるJSONファイルとしてデータを保存できるようになります。
これはAndroidではうまく機能しましたが、iOSではそれほどエレガントではありませんでした。 iPhoneでは、次のように画面に大量のテキストが表示されます。
ご想像のとおり、この欠点についてWebKitを介してAppleを殴打したのは、私だけではありませんでした。 関連するバグはここにありました。
これを書いている時点では、このバグには解決策とパッチがありますが、iOSSafariにはまだ組み込まれていません。 伝えられるところでは、iOS13はそれを修正しますが、私が書いているようにそれはベータ版です。
ですから、私の最小限の実行可能な製品の場合、それはストレージに対処することでした。 今度は、物事をより「アプリのような」ものにすることを試みる時が来ました!
App-I-Ness
多くの人々との多くの議論の結果、「アプリのような」が何を意味するのかを正確に定義することは非常に困難です。
最終的に、私は「アプリのような」というのは、通常Webにはない視覚的な滑らかさの代名詞であることに決めました。 使い心地の良いアプリを考えると、すべてモーションが特徴です。 無償ではありませんが、あなたの行動の物語に追加する動き。 これは、画面間のページ遷移、メニューが表示される方法である可能性があります。 言葉で説明するのは難しいですが、私たちのほとんどはそれを見るとそれを知っています。
必要な最初の視覚的才能は、プレイヤー名を「イン」から「アウト」に、またはその逆に選択したときに上下にシフトすることでした。 プレーヤーをあるセクションから別のセクションに即座に移動させることは簡単でしたが、確かに「アプリのような」ものではありませんでした。 プレーヤー名がクリックされたときのアニメーションは、そのインタラクションの結果、つまりプレーヤーがあるカテゴリから別のカテゴリに移動することを強調することを願っています。
これらの種類の視覚的相互作用の多くのように、それらの見かけの単純さは、実際にそれをうまく機能させることに伴う複雑さを信じています。
動きを正しくするために数回の反復が必要でしたが、基本的なロジックは次のとおりです。
- 「プレーヤー」をクリックしたら、そのプレーヤーがページ上の幾何学的にどこにあるかをキャプチャします。
- 上昇する場合(「イン」)、エリアの上部がどれだけ離れているかを測定し、下降する場合(「アウト」)、下部がどれだけ離れているかを測定します。
- 上がる場合は、プレーヤーが上に移動するときにプレーヤーの列の高さに等しいスペースを残す必要があり、上のプレーヤーは、プレーヤーがスペースに着陸するのにかかる時間と同じ速度で下に倒れる必要があります既存の「In」プレーヤー(存在する場合)がダウンすることで空になります。
- プレーヤーが「アウト」して下に移動する場合、他のすべては左のスペースまで上に移動する必要があり、プレーヤーは現在の「アウト」プレーヤーの下に移動する必要があります。
ふぅ! 英語で思ったよりもトリッキーでした—JavaScriptを気にしないでください!
移行速度など、検討および試行する必要のある追加の複雑さがありました。 最初は、一定の移動速度(たとえば、20ミリ秒あたり20ピクセル)、または一定の移動時間(たとえば、0.2秒)のどちらが見栄えがよいかは明らかではありませんでした。 前者は、プレーヤーが移動する必要のある距離に基づいて「オンザフライ」で速度を計算する必要があるため、少し複雑でした。距離が長くなると、移行時間が長くなります。
ただし、一定の遷移期間は、コードで単純なだけではないことが判明しました。 実際には、より好ましい効果が得られました。 違いは微妙でしたが、これらは両方のオプションを確認した後でのみ決定できる種類の選択肢です。
この効果を釘付けにしようとしている間、視覚的なグリッチが目を引くことがよくありましたが、リアルタイムで分解することは不可能でした。 最良のデバッグプロセスは、アニメーションのQuickTime記録を作成し、それを一度に1フレームずつ実行することであることがわかりました。 常にこれにより、コードベースのデバッグよりも迅速に問題が明らかになりました。
今コードを見ると、私の謙虚なアプリを超えた何かで、この機能はほぼ確実により効果的に記述できることを理解できます。 アプリがプレーヤーの数とスラットの固定高さを知っていることを考えると、DOMを読み取らずに、JavaScriptだけですべての距離計算を行うことが完全に可能であるはずです。
出荷されたものが機能しないというわけではありません。インターネットで紹介するようなコードソリューションではないというだけです。 あ、待って。
他の「アプリのような」インタラクションは、はるかに簡単に実行できました。 メニューを単に表示プロパティを切り替えるような単純なものでスナップインおよびスナップアウトするのではなく、メニューをもう少し精巧に公開するだけで多くのマイレージが得られました。 それでも単純にトリガーされましたが、CSSはすべての面倒な作業を行っていました。
.io-EventLoader { position: absolute; top: 100%; margin-top: 5px; z-index: 100; width: 100%; opacity: 0; transition: all 0.2s; pointer-events: none; transform: translateY(-10px); [data-evswitcher-showing="true"] & { opacity: 1; pointer-events: auto; transform: none; } }
そこで、親要素でdata-evswitcher-showing="true"
属性が切り替えられると、メニューがフェードインし、デフォルトの位置に戻り、ポインターイベントが再度有効になり、メニューがクリックを受け取ることができるようになります。
ECSSスタイルシートの方法論
以前のコードでは、オーサリングの観点から、CSSオーバーライドが親セレクター内にネストされていることに気付くでしょう。 それが私がいつもUIスタイルシートを書くことを好む方法です。 各セレクターの信頼できる唯一の情報源と、単一の中括弧のセット内にカプセル化されたそのセレクターのオーバーライド。 これはCSSプロセッサ(Sass、PostCSS、LESS、Stylusなど)の使用を必要とするパターンですが、ネスト機能を利用する唯一の前向きな方法だと思います。
私はこのアプローチを私の本「EnduringCSS」に固めました。インターフェイス要素のCSSを作成するために利用できるより複雑なメソッドがたくさんあるにもかかわらず、ECSSは、アプローチが最初に文書化されて以来、私と私が協力している大規模な開発チームに役立ちました。 2014年に戻って! この場合も同様に効果的であることが証明されました。
TypeScriptの部分化
CSSプロセッサやSassのようなスーパーセット言語がなくても、CSSにはimportディレクティブを使用して1つ以上のCSSファイルを別のファイルにインポートする機能があります。
@import "other-file.css";
JavaScriptを始めたとき、同等のものがないことに驚きました。 コードファイルが画面より長くなるか、非常に高くなるときはいつでも、それをより小さな部分に分割することが有益であると常に感じます。
TypeScriptを使用するもう1つの利点は、コードをファイルに分割し、必要に応じてインポートするという、美しくシンプルな方法があることです。
この機能は、ネイティブJavaScriptモジュールよりも古いものであり、非常に便利な機能でした。 TypeScriptがコンパイルされると、すべてが1つのJavaScriptファイルに戻されます。 これは、アプリケーションコードを管理可能な部分ファイルに簡単に分割してオーサリングし、メインファイルに簡単にインポートできることを意味します。 メインのinout.ts
の上部は次のようになりました。
/// <reference path="defaultData.ts" /> /// <reference path="splitTeams.ts" /> /// <reference path="deleteOrPaidClickMask.ts" /> /// <reference path="repositionSlat.ts" /> /// <reference path="createSlats.ts" /> /// <reference path="utils.ts" /> /// <reference path="countIn.ts" /> /// <reference path="loadFile.ts" /> /// <reference path="saveText.ts" /> /// <reference path="observerPattern.ts" /> /// <reference path="onBoard.ts" />
この単純なハウスキーピングと整理のタスクは非常に役立ちました。
複数のイベント
当初は、機能性の観点から、「火曜日の夜のサッカー」のような1つのイベントで十分だと感じました。 そのシナリオでは、イン/アウトをロードした場合、プレーヤーを追加/削除または移動しただけで、それがそれでした。 複数のイベントの概念はありませんでした。
私はすぐに(最小限の実行可能な製品を選んだとしても)これはかなり限られた経験になるだろうと決めました。 誰かが異なる日に異なるプレーヤーの名簿で2つのゲームを組織した場合はどうなりますか? 確かにイン/アウトはそのニーズに対応できる/すべきでしょうか? これを可能にし、別のセットにロードするために必要なメソッドを修正するために、データの形状を変更するのにそれほど時間はかかりませんでした。
最初は、デフォルトのデータセットは次のようになりました。
var defaultData = [ { name: "Daz", paid: false, marked: false, team: "", in: false }, { name: "Carl", paid: false, marked: false, team: "", in: false }, { name: "Big Dave", paid: false, marked: false, team: "", in: false }, { name: "Nick", paid: false, marked: false, team: "", in: false } ];
各プレーヤーのオブジェクトを含む配列。
複数のイベントを考慮した後、次のように修正されました。
var defaultDataV2 = [ { EventName: "Tuesday Night Footy", Selected: true, EventData: [ { name: "Jack", marked: false, team: "", in: false }, { name: "Carl", marked: false, team: "", in: false }, { name: "Big Dave", marked: false, team: "", in: false }, { name: "Nick", marked: false, team: "", in: false }, { name: "Red Boots", marked: false, team: "", in: false }, { name: "Gaz", marked: false, team: "", in: false }, { name: "Angry Martin", marked: false, team: "", in: false } ] }, { EventName: "Friday PM Bank Job", Selected: false, EventData: [ { name: "Mr Pink", marked: false, team: "", in: false }, { name: "Mr Blonde", marked: false, team: "", in: false }, { name: "Mr White", marked: false, team: "", in: false }, { name: "Mr Brown", marked: false, team: "", in: false } ] }, { EventName: "WWII Ladies Baseball", Selected: false, EventData: [ { name: "C Dottie Hinson", marked: false, team: "", in: false }, { name: "P Kit Keller", marked: false, team: "", in: false }, { name: "Mae Mordabito", marked: false, team: "", in: false } ] } ];
新しいデータは、各イベントのオブジェクトを含む配列でした。 次に、各イベントには、以前と同じようにプレーヤーオブジェクトを含む配列であるEventData
プロパティがありました。
インターフェイスがこの新しい機能をどのように最適に処理できるかを再検討するのに、はるかに長い時間がかかりました。
当初から、デザインは常に非常に無菌的でした。 これもデザインの練習になるはずだったので、勇気が足りないと感じました。 そのため、ヘッダーから始めて、もう少し視覚的なセンスが追加されました。 これは私がSketchでモックアップしたものです:
それは賞を獲得するつもりはありませんでしたが、それは確かにそれが始まった場所よりももっと逮捕されました。
美学はさておき、ヘッダーの大きなプラスアイコンが非常に混乱していることを私が理解したのは、他の誰かがそれを指摘するまではありませんでした。 ほとんどの人は、それが別のイベントを追加する方法だと思っていました。 実際には、イベント名が現在あったのと同じ場所にプレーヤーの名前を入力できるようにする、凝ったトランジションを備えた「プレーヤーの追加」モードに切り替わりました。
これは、新鮮な目がかけがえのない別の例でした。 それは手放すことの重要な教訓でもありました。 正直なところ、ヘッダーの入力モードの遷移はクールで賢いと感じたので、それを保持していました。 しかし、実際には、それは設計、したがってアプリケーション全体に役立っていませんでした。
これはライブバージョンで変更されました。 代わりに、ヘッダーはイベントのみを処理します—より一般的なシナリオです。 一方、プレーヤーの追加はサブメニューから行われます。 これにより、アプリの階層がよりわかりやすくなります。
ここで学んだもう1つの教訓は、可能な限り、同僚から率直なフィードバックを得ることが非常に有益であるということでした。 彼らが善良で正直な人々であるならば、彼らはあなたにパスを与えさせません!
概要:私のコードは悪臭を放つ
右。 これまでのところ、通常のハイテクアドベンチャーの回顧展です。 これらのものはミディアムで10ペニーです! 公式は次のようになります。開発者は、すべての障害を打ち破り、微調整されたソフトウェアをインターネットにリリースしてから、Googleで面接を受けるか、どこかで買収された方法を詳しく説明します。 しかし、問題の真実は、私がこのアプリ構築マラーキーに初めて参加したことです。そのため、コードは最終的に「完成した」アプリケーションが天国に悪臭を放つように出荷されました。
たとえば、使用されたObserverパターンの実装は非常にうまく機能しました。 私は最初は組織的で系統だったが、物事を終わらせるためにもっと必死になったので、そのアプローチは「南に行った」。 シリアルダイエットのように、昔からの馴染みのある習慣が戻ってきて、コードの品質が低下しました。
出荷されたコードを見ると、これは、クリーンなオブザーバーパターンと関数を呼び出すボグ標準のイベントリスナーの理想的な寄せ集めではありません。 メインのinout.ts
ファイルには、20をquerySelector
メソッド呼び出しがあります。 hardly a poster child for modern application development!
I was pretty sore about this at the time, especially as at the outset I was aware this was a trap I didn't want to fall into. However, in the months that have since passed, I've become more philosophical about it.
The final post in this series reflects on finding the balance between silvery-towered code idealism and getting things shipped. It also covers the most important lessons learned during this process and my future aspirations for application development.