React における命令的なクリープ

反応する
反応する は、任意の時点でコンポーネントがどのように見えるかを記述できる宣言型フレームワークであり、基になるデータが変更された場合にすべての UI 更新を管理します。 UI に必要な最小限の状態セットのみを保持し、その他すべてをオンデマンドで計算するなど、状態管理のベスト プラクティスに従えば、通常は命令型コードの記述が大幅に少なくなります。 この実践に従うことには、一貫性のない UI 状態に関連する一連のバグ全体を排除できるという利点もあります。 ただし、コンポーネントの状態のプロパティが、常に別のプロパティから計算されるわけではない形で相互に依存する状況があり、これらの制約内で状態を維持しようとする直観的な試みにより、恐ろしい命令型コードが導入される可能性があります。 この記事では、そのようなシナリオを示し、状態の一貫性を保つためのより宣言的な方法を提案します。

項目のリストを表示するコンポーネントが必要です。 項目を選択して、チェック状態を切り替えることができます。 いずれかの項目がチェックされている場合、コンポーネントには「選択済み」タブが表示され、クリックするとチェックされた項目のみが表示されます。 これを XNUMX つの状態プロパティを持つコンポーネントで実装します。 XNUMX つ目はチェックされた項目のセットを保持するcheckedSet で、XNUMX つ目はonlyShowChecked です。 後者のプロパティが true の場合、コンポーネントにはチェックされた項目のみが表示され、[選択済み] タブが「アクティブ」になります。 簡単そうに聞こえますが、コードは次のとおりです。

var Selector = React.createClass({ getInitialState() { return {checkedSet:Immutable.Set(this.props.init),onlyShowChecked:false, }; },setShowChecked(bool) { this.setState({onl​​yShowChecked:bool }) ; }, toggleChecked(o) { varcheckedSet = this.state.checkedSet;checkedSet =checkedSet[checkedSet.contains(o) ? 'delete' : 'add'](o); this.setState({checkedSet }); } , render() { var selectedSet = this.state.checkedSet; varonlyShowChecked = this.state.onlyShowChecked; var someChecked =checkedSet.size > 0; var items = this.props.items.toSeq(); if (onlyShowChecked) { items = items.filter(o =>checkedSet.contains(o)); } return (

{いくつかチェック済み&&

}

{this.props.title} {items.map((o, i) => ( )).toArray()}
    ); } }); var items = Immutable.List(['Angular', 'Backbone', 'Ember', 'Knockout', 'React']); React.render( , document.body);

JSビン
見た目は良いですが、望ましくない状態があります。 [選択済み] タブですべての項目のチェックを外すと、UI には項目が表示されず、アクティブなタブも存在しません。 おそらく、最後の項目のチェックを外すたびに自動的に [すべて表示] タブに戻ることで、この問題を修正したいと思うでしょう。 その動作を実装する最も明白な場所は、もちろん、この状態になったアクション内です。 つまり、toggleChecked では、最後の項目のチェックを外したかどうかを確認し、チェックを外した場合は、onlyShowChecked を false に設定します。 変更された toggleChecked は次のようになります。

  toggleChecked(o) { varcheckedSet = this.state.checkedSet; チェックセット = チェックセット[チェックセット.contains(o) ? '削除' : '追加'](o); varonlyShowChecked = this.state.onlyShowChecked; if (checkedSet.isEmpty())onlyShowChecked = false; this.setState({checkedSet,onlyShowChecked}); }、

案の定、そのシナリオを再実行すると、期待通りに動作します。 JSビン
ただし、このアプローチには問題がいくつかあります。 toggleChecked 関数には複数の役割があるようになりました。 項目のチェック状態を切り替えることに加えて、onlyShowChecked プロパティも設定されるようになりました。 次にこのコードを読む開発者は、前のバージョンと比べて、一目では理解できないでしょう。 彼らは、なぜこのチェックが導入されたのかを考える必要があり、機能を変更または追加するときは常にこのエッジケースを頭の中に入れておく必要があります。
ここで、すべてのチェックを一度に解除するボタンを追加すると何が起こるかを考えてみましょう。 このボタンは、checkedSet をクリアする uncheckAll 関数を呼び出します。
JSビン
実際、この新しいボタンを使用すると、[選択済み] タブ内ですべての項目のチェックを外すことができるため、再びその望ましくない状態に陥る可能性があります。 現時点ではこれはバグとみなされており、これを修正する直感的な方法がいくつかあります。
明らかな修正の XNUMX つは、toggleChecked に導入したのと同じチェックをインライン化することですが、これも uncheckAll の責任が増えることになり、ロジックの重複は保守性にとって決して良くありません。
もう XNUMX つの方法は、チェックされた項目をループして、それぞれの項目に対して toggleChecked を呼び出すことです。 これでもうまくいきますが、必要以上の作業をしているように感じられます。 そして、関数呼び出しで setState を何度も呼び出したときのレンダリング動作を誰が予測できるでしょうか? また、私たちがそうする唯一の理由は、 検討する このようにするのは、私たちが 知っています toggleChecked には必要な副作用があり、これにより XNUMX つの関数が緊密に結合されます。
いずれにせよ、今から数か月後に、項目を切り替える XNUMX 番目の方法、たとえば各項目のチェック状態を反転するボタンを追加する方法を導入したいとします。 これは、すべての項目がチェックされている場合、事実上、すべてのチェックを解除することになります。 この時点では、コンポーネントが望ましくない状態になるのを防ぐためにコードを追加しており、バグの再導入が非常に簡単であることを忘れるのは自然なことです。
根本的な問題は、React には状態プロパティ間の依存関係を宣言的に記述する明確な方法がないことです。 つまり、この場合、checkedSet が空の場合、onlyShowChecked が true になることはありません。 したがって、その不変条件を維持するために、jQuery で作成していた命令型 DOM 操作を彷彿とさせる命令型状態操作を作成することになります。これは、将来的に保守性の問題につながることは避けられません。 これを私は命令型クリープと呼んでいます。
Angular またはオブザーバブルを提供する別のフレームワークでは、showSelected を正しい状態に保つために、checkedSet プロパティを監視するだけです。 React で依存状態を管理するための最善の方法は、レンダリング関数の先頭で状態を直接変更することです。

  render() { if (this.state.checkedSet.isEmpty() && this.state.onlyShowChecked) { this.state.onlyShowChecked = false; } // ... }

今では、これがどの React アプリケーションにおいても悪い習慣であることを認めていますが、私たちはこの結論に軽々しく到達したわけではありません。 shouldComponentUpdate ライフサイクル メソッドで状態を管理しようとしましたが、それによって、すでに shouldComponentUpdate を提供するミックスインを使用する場合、state がプレーンな JavaScript オブジェクトではなく Immutable である場合、forceUpdate が shouldComponentUpdate を完全にバイパスする場合など、多くの非互換性が導入されました。
レンダリング関数の先頭で依存状態を管理することで、これらの非互換性に対処する必要がなくなります。 利点は、UI バグの根本原因が排除され、開発者が依存する状態プロパティのすべてのルールを XNUMX か所に保管することが奨励されることです。

類似の投稿