Webフレームワークが解決するものとそれらなしで行う方法(パート1)
公開: 2022-03-10私は最近、フレームワークをバニラJavaScriptと比較することに非常に興味を持っています。 それは、私がフリーランスのプロジェクトのいくつかでReactを使用していたことへの不満の後で、そして仕様エディターとしてのWeb標準についての最近のより親密な知識から始まりました。
フレームワーク間の共通点と相違点、よりスリムな代替手段としてWebプラットフォームが提供するもの、そしてそれで十分かどうかを知りたいと思いました。 私の目的は、フレームワークを打ち負かすことではなく、コストとメリットを理解し、代替案が存在するかどうかを判断し、フレームワークを使用することにした場合でも、そこから学ぶことができるかどうかを確認することです。
この最初のパートでは、フレームワーク間で共通するいくつかの技術的機能と、いくつかの異なるフレームワークがそれらをどのように実装するかについて深く掘り下げます。 また、これらのフレームワークを使用するコストについても見ていきます。
フレームワーク
私は4つのフレームワークを検討するために選択しました。今日支配的なフレームワークであるReactと、Reactとは異なることを行うと主張する3つの新しい候補です。
- React
「Reactを使用すると、インタラクティブなUIを簡単に作成できます。 宣言型ビューにより、コードがより予測可能になり、デバッグが容易になります。」 - SolidJS
「SolidはReactと同じ哲学に従います…しかし、仮想DOMの使用をやめた完全に異なる実装を持っています。」 - Svelte
「Svelteは、ユーザーインターフェースを構築するための根本的な新しいアプローチです。アプリを構築するときに発生するコンパイルステップです。 Svelteは、仮想DOM差分などの手法を使用する代わりに、アプリの状態が変化したときにDOMを外科的に更新するコードを記述します。」 - 点灯
「Webコンポーネント標準に基づいて構築されたLitは、反応性、宣言型テンプレート、およびいくつかの思慮深い機能を追加します。」
フレームワークが差別化要因について何を言っているかを要約すると、次のようになります。
- Reactを使用すると、宣言型ビューを使用してUIを簡単に構築できます。
- SolidJSはReactの哲学に従いますが、異なる手法を使用します。
- Svelteは、UIに対してコンパイル時のアプローチを使用します。
- Litは既存の標準を使用し、いくつかの軽量機能が追加されています。
フレームワークが解決するもの
フレームワーク自体は、宣言型、反応性、および仮想DOMという言葉に言及しています。 それらの意味を詳しく見ていきましょう。
宣言型プログラミング
宣言型プログラミングは、制御フローを指定せずにロジックを定義するパラダイムです。 そこにたどり着くまでのステップではなく、結果がどうあるべきかを説明します。
宣言型フレームワークの初期の2010年頃、DOM APIははるかにむき出しで冗長であり、必須のJavaScriptを使用してWebアプリケーションを作成するには、多くの定型コードが必要でした。 そのとき、「model-view-viewmodel」(MVVM)の概念が普及し、当時の画期的なKnockoutおよびAngularJSフレームワークが使用され、ライブラリ内でその複雑さを処理するJavaScript宣言型レイヤーが提供されました。
MVVMは今日広く使用されている用語ではなく、以前の用語「データバインディング」のバリエーションのようなものです。
データバインディング
データバインディングは、モデルとユーザーインターフェイスの間でデータがどのように同期されるかを表現する宣言的な方法です。
人気のあるUIフレームワークはすべて、何らかの形式のデータバインディングを提供し、チュートリアルはデータバインディングの例から始まります。
JSX(SolidJSおよびReact)でのデータバインディングは次のとおりです。
function HelloWorld() { const name = "Solid or React"; return ( <div>Hello {name}!</div> ) }
Litでのデータバインディング:
class HelloWorld extends LitElement { @property() name = 'lit'; render() { return html`<p>Hello ${this.name}!</p>`; } }
Svelteでのデータバインディング:
<script> let name = 'world'; </script> <h1>Hello {name}!</h1>
反応性
反応性は、変化の伝播を表現するための宣言的な方法です。
データバインディングを宣言的に表現する方法がある場合、フレームワークが変更を伝播するための効率的な方法が必要です。
Reactエンジンは、レンダリングの結果を前の結果と比較し、その違いをDOM自体に適用します。 変更の伝播を処理するこの方法は、仮想DOMと呼ばれます。
SolidJSでは、これはストアと組み込み要素を使用して、より明示的に行われます。 たとえば、 Show
要素は、仮想DOMではなく、内部で何が変更されたかを追跡します。
Svelteでは、「リアクティブ」コードが生成されます。 Svelteは、どのイベントが変更を引き起こす可能性があるかを認識しており、イベントとDOMの変更の間に線を引く簡単なコードを生成します。
Litでは、反応性は要素のプロパティを使用して実現され、基本的にHTMLカスタム要素の組み込みの反応性に依存します。
論理
フレームワークがデータバインディングの宣言型インターフェイスを提供し、反応性を実装する場合、従来は必須に記述されていたロジックの一部を表現する方法も提供する必要があります。 ロジックの基本的な構成要素は「if」と「for」であり、すべての主要なフレームワークがこれらの構成要素の表現を提供します。
条件付き
数値や文字列などの基本データをバインドする以外に、すべてのフレームワークは「条件付き」プリミティブを提供します。 Reactでは、次のようになります。
const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true);
SolidJSは、組み込みの条件付きコンポーネントであるShow
:を提供します。
<Show when={state.error}> <label>Message</label> </Show>
Svelteは#if
ディレクティブを提供します:
{#if state.error} <label>Message</label> {/if}
Litでは、 render
関数で明示的な三項演算を使用します。
render() { return this.error ? html`<label>Message</label>`: null; }
リスト
他の一般的なフレームワークプリミティブはリスト処理です。 リストはUIの重要な部分(連絡先のリスト、通知など)であり、効率的に機能するには、1つのデータ項目が変更されたときにリスト全体を更新するのではなく、事後対応型である必要があります。
Reactでは、リスト処理は次のようになります。
contacts.map((contact, index) => <li key={index}> {contact.name} </li>)
Reactは、特別なkey
属性を使用してリストアイテムを区別し、リスト全体がすべてのレンダリングで置き換えられないようにします。
SolidJSでは、 for
およびindex
の組み込み要素が使用されます。
<For each={state.contacts}> {contact => <DIV>{contact.name}</DIV> } </For>
内部的には、SolidJSは、 for
およびindex
と組み合わせて独自のストアを使用して、アイテムが変更されたときに更新する要素を決定します。 Reactよりも明示的であるため、仮想DOMの複雑さを回避できます。
Svelteは、アップデーターに基づいてトランスパイルされるeach
ディレクティブを使用します。
{#each contacts as contact} <div>{contact.name}</div> {/each}
Litは、Reactのkey
ベースのリストマッピングと同様に機能するrepeat
関数を提供します。
repeat(contacts, contact => contact.id, (contact, index) => html`<div>${contact.name}</div>`
コンポーネントモデル
この記事の範囲外の1つは、さまざまなフレームワークのコンポーネントモデルと、カスタムHTML要素を使用してそれを処理する方法です。
注:これは大きなテーマです。これは長くなりすぎるため、今後の記事で取り上げたいと思います。 :)
コスト
フレームワークは、宣言型のデータバインディング、制御フロープリミティブ(条件付きおよびリスト)、および変更を伝播するためのリアクティブなメカニズムを提供します。
また、コンポーネントを再利用する方法など、他の主要な機能も提供しますが、それは別の記事の主題です。
フレームワークは役に立ちますか? はい。 それらは私たちにこれらの便利な機能のすべてを提供します。 しかし、それは正しい質問ですか? フレームワークの使用にはコストがかかります。 それらの費用が何であるか見てみましょう。
バンドルサイズ
バンドルサイズを見るとき、私は縮小された非Gzipサイズを見るのが好きです。 これは、JavaScript実行のCPUコストに最も関連するサイズです。
- ReactDOMは約120KBです。
- SolidJSは約18KBです。
- 点灯は約16KBです。
- Svelteは約2KBですが、生成されるコードのサイズは異なります。
今日のフレームワークは、バンドルサイズを小さく保つというReactよりも優れた仕事をしているようです。 仮想DOMには多くのJavaScriptが必要です。
ビルド
どういうわけか、私たちはWebアプリを「構築」することに慣れました。 Node.jsとWebpackなどのバンドラーをセットアップし、Babel-TypeScriptスターターパックの最近の構成変更に対処せずにフロントエンドプロジェクトを開始することは不可能です。
フレームワークの表現力が高く、バンドルサイズが小さいほど、ビルドツールとトランスパイル時間の負担が大きくなります。
Svelteは、仮想DOMは純粋なオーバーヘッドであると主張しています。 私は同意しますが、おそらく「構築」(SvelteやSolidJSのように)とカスタムクライアント側テンプレートエンジン(Litのように)も、別の種類の純粋なオーバーヘッドですか?
デバッグ
構築とトランスパイルには、別の種類のコストがかかります。
Webアプリを使用またはデバッグするときに表示されるコードは、作成したコードとはまったく異なります。 現在、さまざまな品質の特別なデバッグツールを使用して、Webサイトで発生することをリバースエンジニアリングし、独自のコードのバグと結び付けています。
Reactでは、コールスタックが「あなたのもの」になることはありません—Reactがあなたに代わってスケジューリングを処理します。 これは、バグがない場合にうまく機能します。 しかし、無限ループの再レンダリングの原因を特定してみてください。そうすれば、苦痛の世界に陥ることになります。
Svelteでは、ライブラリ自体のバンドルサイズは小さいですが、アプリのニーズに合わせてカスタマイズされた、Svelteの反応性の実装である暗号化されたコードの全体を出荷してデバッグします。
Litを使用すると、構築はそれほど重要ではありませんが、効果的にデバッグするには、テンプレートエンジンを理解する必要があります。 これが、フレームワークに対する私の感情が懐疑的である最大の理由かもしれません。
カスタムの宣言型ソリューションを探すと、より面倒な命令型デバッグが必要になります。 このドキュメントの例では、API仕様にTypescriptを使用していますが、コード自体はトランスパイルを必要としません。
アップグレード
このドキュメントでは、4つのフレームワークについて説明しましたが、数えきれないほど多くのフレームワークがあります(AngularJS、Ember.js、Vue.jsなど)。 フレームワーク、その開発者、そのマインドシェア、およびそのエコシステムが進化するにつれてあなたのために働くことを期待できますか?
独自のバグを修正するよりも苛立たしいことの1つは、フレームワークのバグの回避策を見つける必要があることです。 また、フレームワークのバグよりも苛立たしいのは、コードを変更せずにフレームワークを新しいバージョンにアップグレードしたときに発生するバグです。
確かに、この問題はブラウザにも存在しますが、発生するとすべての人に発生し、ほとんどの場合、修正または公開された回避策が差し迫っています。 また、このドキュメントのパターンのほとんどは、成熟したWebプラットフォームAPIに基づいています。 常に最先端を行く必要はありません。
概要
データバインディング、反応性、条件、リストに焦点を当てて、フレームワークが解決しようとしている主要な問題と、それらをどのように解決するかについて、もう少し深く理解しました。 コストも調べました。
第2部では、フレームワークをまったく使用せずにこれらの問題にどのように対処できるか、そしてフレームワークから何を学ぶことができるかを見ていきます。 乞うご期待!
技術的なレビューをしてくれた次の個人に特に感謝します:Yehonatan Daniv、Tom Bigelajzen、Benjamin Greenbaum、Nick Ribal、LouisLazaris。