getDerivedStateFromState-または単純な問題を困難にする方法

Reactが大好きです。 それが機能する方法が大好きです。 「正しい」ことをするために。 HOC、構成、RenderProps、ステートレス、ステートフル-刈り取りを減らすのに役立つ無数のパターンとアンチパターン。



そしてつい最近、Reactが私たちにもう一つの贈り物をもたらしました。 刈り取りを減らすもう1つの機会は、getDeviredStateFromPropsです。



技術的には、小道具から状態への静的マッピングを使用して、アプリケーションロジックをよりシンプルに、より理解しやすく、テストしやすくする必要があります。 実際、多くの人々が足を踏みつけ、prevPropsを要求し始めましたが、アプリケーションのロジックを作り直すことができませんでした(またはあまり欲求がありません)。



一般的に、地獄の深さは明らかになりました。 以前は、単純なタスクがより困難になりました。







最初の議論はgithub / reactjs.orgページ始まり 、ログを記録するために小道具がどのように変化したかを知る必要がありました。

componentWillReceivePropsを削除することで、現在よりも悪いコードを書くことを奨励するシナリオを発見しました。
// OLD WAY componentWillReceiveProps(newProps){ if (this.props.visible === true && newProps.visible === false) { registerLog('dialog is hidden'); } } // NEW WAY static getDerivedStateFromProps(nextProps, prevState){ if (this.state.visible === true && nextProps.visible === false) { registerLog('dialog is hidden'); } return { visible : nextProps.visible }; }
      
      





PS:しかし、そのような操作は `componentDidUpdate`で実行する必要があることを知っていますか?



しかし、それはほんの始まりに過ぎませんでした。 同じ日、getDerivedStateFromPropsの変更に関する問題が発生しました。これは、prevPropsがないと生命がないからです。 まったく同じ問題がWontの修正で既に1回クローズされましたが、今回は長い口頭の戦いの後、Wontの修正で再びクローズされました。 だから彼はそれが必要です。

しかし、出口と、なぜ問題が閉じられたのかを議論する前に、実例となる推論のためのいくつかの便利な例を思いつく方が良いです。

テーブル。 並べ替えとページナビゲーションを使用



私たちはTDDに目を向け、最初に問題とそれを解決する方法を定義します



  1. テーブルを描くには何が必要ですか?

    1. データを表示する
    2. それらを並べ替える
    3. 現在のページのデータのみを含むスライスを取得します
    4. 注文アイテムを混在させないでください


  2. データが変更された場合の対処方法

    1. 最初からやり直す


  3. そして、ページのみが変更された場合はどうなりますか?

    1. パラグラフ1.3以降に従ってください。


  4. ページを変更する方法

    1. this.setState({page})


  5. state.pageの変更に対応する方法

    1. まさか




それが問題です-小道具の変更に対応できますが、状態を変更する機能はありません(この記事のタイトルで読んでも)。



正しい決定番号1



より正確には、「正しい」決定。 ステートマシンである必要があります。 最初は、 アイドル状態です。 setState({page})シグナルが到着すると、別の状態変更ページに切り替わります 。 この状態に入ると、彼はそこで必要なものを考慮し、 setState({temporalResult})にシグナルを送信します。 さらに良いことに、マシンは「次のステップ」状態に移行する必要があります。この状態では、現在のステップの後のステップからすべてがカウントされ、最終的にcommitになり、データをtemporaryResultからdataに転送し 、その後idleになります。



技術的には、これは正しい決定であり、おそらくそれはすべて、鉄の奥深くのどこか、または一枚の紙のように機能します。 そのままにしておきます。



正しい決定番号2



しかし、現在の要素から状態と小道具をpropsの形式で渡し、getDerivedStateFromPropsを使用する別の要素を作成するとどうなりますか?



つまり、「最初の」コンポーネントは「スマート」コントローラーであり、そこではsetState({page})が実行され、そのダムはそのようなダンプではなく、外部パラメーターを変更するときに必要なデータを計算します。

何も問題ありませんが、「getDerivedStateFromPropsと呼ばれる人がいるから」何かが変わったことを知っているため、「必要なものだけを数える」項目を認識していませんが、WHATを知りません。

この点で、何も変わっていません。



正しい決定番号3(「公式」)



問題を解決するための議論の役割を果たした「決定」の基礎は、1つの簡単な声明でした。

redux getDerivedStateFromPropsは必要ないかもしれません。 メモが必要です。
 // base - https://github.com/reactjs/rfcs/pull/40#discussion_r180818891 import memoize from "lodash.memoize"; class Example { getSortedData = memoize((list, sortFn) => list.slice().sort(sortFn)) getPagedData = memoize((list, page) => list.slice(page*10, (page+1)*10)) render() { const sorted = this.getSortedData(this.props.data, this.props.sort); const pages = this.getPagedData(sorted, this.props.page); // Render with this.props, this.state, and derived values ... } }
      
      





メモ化は「変更」を追跡します。これは単に「古い」意味を認識し、値が変更された場合にのみメモ化された関数を呼び出すためです。



しかし、2つの問題があります。 そして、両方とも私は元の問題に関する2番目のコメントから取りました



問題番号1



奇妙な複数の深さのWeakMapに頼り、さまざまなレベルのキャッシュをいつドロップするかを決定する必要があります。
曲がった手で乗算された値の変更の同じ「重要な」順序。 キャッシングには、WeakMapsといういくつかのレベルがあります。 ああ、何してるの、やめて!



発行番号2



1つの解決策は、その計算をメモして毎回呼び出すことを提案しましたが、これは良いアイデアですが、実際にはキャッシュを管理することを意味し、複数の引数を取る関数を扱う場合、潜在的なバグの表面積を大幅に増加させ、間違い。

そして、これはすべてのメモ化ライブラリの主な問題の1つです。つまり、「最終」値を関数の引数として使用する必要があります。 一般に、それは単に不便ですが、同時に変数を混同する可能性があります。



最初の問題には、再選択の解決策があります。 カスケード再選択では 、入力用に2つのメモ化された値がある場合、出力用に3つ目のメモ化された値を作成できます。



さらに良いのは、単純に実行順序を決定し、特定の(有限)オートマトンが次々に実行するメモ化関数の構成です...一般に、再選択カスケードも「構成」されますが、そこにはツリーがあり、ここで線形プロセス-ウォーターフォールが必要です。

うーん、私はこの記事の発表で滝を見ました。 これはなぜですか?
  const input = {...this.state, ...this.props }; const resultOfStep1 = {...input, sorted:this.getSortedData(input.data, input.sort); const resultOfStep1 = {... resultOfStep1, sorted:this.getPagedData(resultOfStep1.sorted, resultOfStep1.page);
      
      





「すべてのゴミ」がヘプラーで取り出されると、かなりきれいなコードが得られます



 const Flow = (input, fns) => fns.reduce( (acc,fn) => ({...acc, ...fn(acc)}), input); const result = Flow({...this.state, ...this.props },[ ({ data, sort }) => ({data: this.getSortedData(data, sort) }); ({ data, page }) => ({data: this.getPagedData(data, page) ]);
      
      





問題番号1のクリーンでシンプルで非常に美しいソリューション。最終的な値の形成順序を明確に定義します。これは完全に記憶することは不可能です。



実行の「ステップ」には引数が1つしかなく、入力を変更する場合は最初の段階から開始する必要があるため、これを覚えることは完全に不可能です。ページのみが変更され、最後のステップのみを再起動する必要があることを理解できません。



それとも可能ですか?



 import {MemoizedFlow} from "react-memoize"; class Example { getSortedData = (list, sortFn) => list.slice().sort(sortFn) getPagedData = (list, page) => list.slice(page*10, (page+1)*10)) render() { return ( <MemoizedFlow input={this.props} flow = [ ({data, sort}) => ({ data: this.getSortedData(data, sort)}), ({data, page}) => ({ data: this.getPagedData(sorted, page)}); ] >{ ({data}) => <table>this is data you are looking for {data}</table> } </MemoizedFlow> ) } }
      
      





奇妙なことではありません-今回はすべてが時計のように機能します。 そして、最終値を計算するために使用されるフロー関数でさえ、以前とまったく同じになります。

全体の秘密-1か月前に話した別のメモ化関数memoize-stateでは、特定の段階で状態のどの部分が使用されたかがわかるため、メモ化されたウォーターフォールを実装できます。

遊んでいるより複雑な例はcodesandbox.io/s/23ykx5z5jpです
その結果、静的関数getDerivedStateFromPropsは(ある意味)静的に定義されたコンポーネントに置き換えられます。その設定により、結果、より正確にはソースデータのセットから最終結果取得する「方法と方法」を明確に定義できます。



getDerivedStateFromProps、getDerivedStateFromState、getDerivedPropsFromProps-何でも構いません。 副作用を開始することもできます(動作しますが、しない方が良いです)。



そして最も重要なこと-このアプローチにより、パラメーターの変化に対する反応を正確に決定することができます。 また、「正しい」形式で正確に判断することができます

データまたはページが変更された場合、データを更新する必要があります。 「ページ」の場合だけではありません。
特定のフローを中断することは不可能です。 主なことは、古い意味を知りたがらないことです。



おわりに



一般的に、Reactは最近、たわごとや非同期レンダリングの問題につながるさまざまなアプローチを望まないことを教えてきました。 しかし、人々は人々のままであり、古くから実績のあるアプローチを放棄したくありません。 それがまさに問題です。



実際、文字通り2週間前に反応を準備し、BACとレシピが変更されたため、今日の反応を準備することが「正しい」方法を理解するのが非常に難しい場合があります。



しかし、絶望しないでください-それに基づいて構築されたmemoize-statereact-memoizeは痛みをわずかに鈍らせます。 すべての問題は解決できます。主なことは、問題を異なる角度から見ようとすることです。



PS: その結論と同じ元の問題

PS: memoize-stateが機能する方法と理由について少し説明します。



All Articles