Redux: すべての状態を単一の不変のアトムに格納することの欠点は何ですか?

作成日 2016年02月10日  ·  91コメント  ·  ソース: reduxjs/redux

これがすべてのreduxの根底にある原則であり、今ではかなりよく知られているこれらすべての素晴らしい利点が付属していることを理解しています。

ただし、reduxに欠けている点の1つは、このアーキテクチャを使用することの概念的な欠点を公然と説明していないことだと思います。 ネガティブな理由で人々が恥ずかしがらないようにしたいので、おそらくこれは良い選択です。

それはreduxだけでなく、 Omdatomic 、データベースを裏返しにすることについて話している他のものにもある程度適用されるので、私はただ興味が

しかし、私が行ったすべての検索で、単一の不変ストアに批判的な人々を見つけるのは困難です。 これは一例ですが、実際のアーキテクチャよりもreduxの冗長性に問題があるようです。

私が考えることができる唯一のことは、おそらくあなたの状態のコピーを保存し続けるためにより多くのメモリが必要であるか、またはreduxで迅速にプロトタイプを作成するのが少し難しいということです。

皆さんはおそらく私よりもずっと多くのことを考えているので、私のためにこれを解明するのを手伝ってくれませんか?

discussion docs question

最も参考になるコメント

Reduxは基本的に、消費する単一のプロジェクションがあるイベントソーシングです。

分散システムでは、通常1つのイベントログ(Kafkaなど)があり、このログを異なるサーバーでホストされている複数のデータベース/ストアに投影/削減できる複数のコンシューマーがあります(通常、DBレプリカは実際にはレデューサーです)。 したがって、分散された世界では、負荷とメモリの使用量は分散されますが、Reduxでは、すべてがローカル状態であるウィジェットが数百ある場合、これらはすべて単一のブラウザーで実行され、すべての状態変更には不変のためにオーバーヘッドがありますデータの更新。

ほとんどの場合、このオーバーヘッドは大した問題ではありませんが、テキスト入力をあまり良くない状態の形にマウントし、遅いモバイルデバイスで高速に入力する場合、必ずしも十分なパフォーマンスを発揮するとは限りません。

私の経験では、その状態を一番上からレンダリングすることは便利ではなく、常に十分なパフォーマンスを発揮するとは限りません(少なくとも、おそらく最速のVDom実装ではないReactでは)。 Reduxはconnectこれを解決しますが、それでも接続数が増えると、ボトルネックになる可能性があります。 解決策があります

また、ImmutableJSのような永続データ構造は、大きなリストのランダムインデックスにアイテムを追加するなど、一部の操作で常に最高のパフォーマンスを提供するとは限りません(ここで大きなリストをレンダリングした私の経験を参照して

Reduxには、便利でほとんどのユースケースで問題ないため、イベントログとプロジェクションの両方が含まれていますが、reduxの外部でイベントログを維持し、それを2つ以上のreduxストアにプロジェクションし、これらすべてのreduxストアをに追加することは可能です。異なるキーの下でコンテキストに反応し、接続するストアを指定することでオーバーヘッドを削減します。 これは絶対に可能ですが、ストアとイベントログを明確に区別する必要があるため、apiとdevtoolsの実装が難しくなります。


@jquenseにも同意し

簡単な水分補給、スナップショット、タイムトラベルなどの利点は、重要な州が住んでいる場所が他にない場合にのみ機能します。 Reactのコンテキストでは、これは、ストア内のコンポーネントに適切に属する状態をストアする必要があることを意味します。そうしないと、多くのメリットが失われます。 すべてをReduxに入れたい場合は、多くの場合、煩わしく冗長になり、煩わしいレベルの間接参照が追加されます。

状態をReduxストアにマウントするには、より多くのボイラープレートが必要です。 Elmアーキテクチャはおそらくこれをよりエレガントな方法で解決しますが、多くの定型文も必要とします。

しかし、すべての状態を制御することも不可能または実行可能ではありません。 宣言型インターフェースを構築するのが難しい既存のライブラリーを使用することがあります。 次のような一部の状態は、効率的な方法でreduxストアにマウントするのも困難です。

  • テキスト入力
  • スクロール位置
  • ビューポートサイズ
  • マウスの位置
  • キャレットの位置/選択
  • キャンバスdataUrls
  • シリアル化できない状態
  • ..。

全てのコメント91件

それは素晴らしい質問です。 明日は包括的な回答を書こうと思いますが、まずは何人かのユーザーからの意見を聞きたいと思います。 この議論を後で公式のドキュメントページとして要約するとよいでしょう。 欠点がわからなかったので、最初はこれをしませんでした。

どうもありがとうございました! 私は本当にこれについて聞きたいと思っていました。

ちなみに、このディスカッションに貢献したいすべての人に、プロジェクトの範囲を理解しやすくするために、Reduxで何を構築しているかについてのアイデアも提供することをお勧めします。

OMやredux、その他の単一状態のアトムライブラリなどのb / cプロジェクトのような難しい質問は、マイナス面を積極的に緩和します。 不変性の制約がなく、ゲートされた制御されたアクセスは、すべてのデータをwindowアタッチするのとまったく同じです(欠点はよく知られています)。

ただし、Reduxアプローチに固有の、私たちにとっての最大の欠点は、単一状態のアトムが一種のオールオアナッシングであるということです。 簡単な水分補給、スナップショット、タイムトラベルなどの利点は、重要な州の生活が他にない場合にのみ機能します。 Reactのコンテキストでは、これは、ストア内のコンポーネントに適切に属する状態をストアする必要があることを意味します。そうしないと、多くのメリットが失われます。 すべてをReduxに入れたい場合は、多くの場合、煩わしく冗長になり、煩わしいレベルの間接参照が追加されます。

これらの欠点のどれも私たちにとって特に禁止されていません:)

ですから、私はゲームの状態をモデル化するために、Reduxとこのスタイルのアーキテクチャに注目してきました。 私は、Civスタイルのシミュレートされた世界と、完全に記録されたボードゲームの履歴をブラウザーのコンテキストでより簡単に作成することに興味があります。

その意味で、履歴データ構造のサイズを管理したり、その一部をサーバーやディスクに移動して保存したり、特定のセグメントを復元したりすることに興味があります。

私が苦労したことは、新しいユーザーとして、Reduxでの極端な(imho)コーディングスタイルの変更と概念の変更の両方を同時に克服しようとしていることです。 それは今でも少し気が遠くなるようなままで、1か月間、深く掘り下げようとすると変化します。 次にhttps://github.com/ngrx/storeを調べて概念を確認しますが、より馴染みのあるスタイル/構文と、暗黙/提供されているフレームワークの残りの部分を使用しています。

一部の人にとって、フレームワークは松葉杖であることを理解しています。 しかし、私たちの中にはそれらが必要な人もいます。 有用な強い意見を持つのに十分なほどゲームのトップに立つ人はほとんどいないので、フレームワークは暗闇の中で手探りするよりも優れています。 だから、それは私からのポイントです、中年、中年のプログラマーは概念に新しいです:)

基本的に、Reduxとあなたのビデオは、生のJavaScriptでそれを行う方法を教えてくれましたが、経験の浅いプログラマーであるため、それ以上のガイダンスなしに製品を提供する方法がわかりません。そのため、完成したものの例を見るまで、私は次のように立ってください。

あなたのせいではありませんが、それでも私が解決したい問題です:)

この時点で私がまだ持っている最大の質問は、関数、インスタンス、promiseなどのシリアル化できないアイテムはどこに行くべきかということだと思います。 先日のReactifluxでこれについて疑問に思っていたのですが、良い反応が得られませんでした。 また、誰かがhttp://stackoverflow.com/questions/35325195/should-i-store-function-references-in-redux-storeを投稿しているのを見ました。

複数のストアのユースケース(覚えておいてください、これは私が考えることができる最高のものです):

複数のサブアプリを組み合わせたアプリ。 MyYahooやその他のカスタマイズ可能なホームページ製品のようなものを考えてみてください。 各ウィジェットは、2つのウィジェットが重複することなく独自の状態を維持する必要があります。 製品チームは各ウィジェットに割り当てられている可能性が高いため、他のコードが環境に与える影響を知らない可能性があります。

収集すると、Reduxでいくつかのルールに違反し、Reactのコンテキストのようなものでそれらのストアを伝播することに注意することでこれを達成できる可能性があります。 ただし、デフォルトで複数の状態アトムを処理するフレームワークを使用すると、より簡単になる場合があります。

@gaearon私たちは取引プラットフォームを構築しています。 なぜ1つのストアが私たちに合わないのかを一度説明しました(サンクトペテルブルクでのReact.jsミートアップの後)。 もう一度詳しく説明しようと思いますが(中程度のブログ投稿で?)、英語力の関係で助けが必要かもしれません:)Twitterで直接レビューするためにあなたや他の誰かに送っても大丈夫ですかそれが行われるときのメッセージ? 正確な日付を指定することはできませんが、すぐにこの投稿を書こうと思います(おそらく今月末)。

そして、はい、 @ timdorrが言ったように、ドキュメントのいくつかのルールに違反することで、複数のストアでReduxを使用しています(そのため、さまざまなストアからのデータが必要な場合に、 react-reduxを快適に使用することはできません)単店舗の場合)

Reduxは基本的に、消費する単一のプロジェクションがあるイベントソーシングです。

分散システムでは、通常1つのイベントログ(Kafkaなど)があり、このログを異なるサーバーでホストされている複数のデータベース/ストアに投影/削減できる複数のコンシューマーがあります(通常、DBレプリカは実際にはレデューサーです)。 したがって、分散された世界では、負荷とメモリの使用量は分散されますが、Reduxでは、すべてがローカル状態であるウィジェットが数百ある場合、これらはすべて単一のブラウザーで実行され、すべての状態変更には不変のためにオーバーヘッドがありますデータの更新。

ほとんどの場合、このオーバーヘッドは大した問題ではありませんが、テキスト入力をあまり良くない状態の形にマウントし、遅いモバイルデバイスで高速に入力する場合、必ずしも十分なパフォーマンスを発揮するとは限りません。

私の経験では、その状態を一番上からレンダリングすることは便利ではなく、常に十分なパフォーマンスを発揮するとは限りません(少なくとも、おそらく最速のVDom実装ではないReactでは)。 Reduxはconnectこれを解決しますが、それでも接続数が増えると、ボトルネックになる可能性があります。 解決策があります

また、ImmutableJSのような永続データ構造は、大きなリストのランダムインデックスにアイテムを追加するなど、一部の操作で常に最高のパフォーマンスを提供するとは限りません(ここで大きなリストをレンダリングした私の経験を参照して

Reduxには、便利でほとんどのユースケースで問題ないため、イベントログとプロジェクションの両方が含まれていますが、reduxの外部でイベントログを維持し、それを2つ以上のreduxストアにプロジェクションし、これらすべてのreduxストアをに追加することは可能です。異なるキーの下でコンテキストに反応し、接続するストアを指定することでオーバーヘッドを削減します。 これは絶対に可能ですが、ストアとイベントログを明確に区別する必要があるため、apiとdevtoolsの実装が難しくなります。


@jquenseにも同意し

簡単な水分補給、スナップショット、タイムトラベルなどの利点は、重要な州が住んでいる場所が他にない場合にのみ機能します。 Reactのコンテキストでは、これは、ストア内のコンポーネントに適切に属する状態をストアする必要があることを意味します。そうしないと、多くのメリットが失われます。 すべてをReduxに入れたい場合は、多くの場合、煩わしく冗長になり、煩わしいレベルの間接参照が追加されます。

状態をReduxストアにマウントするには、より多くのボイラープレートが必要です。 Elmアーキテクチャはおそらくこれをよりエレガントな方法で解決しますが、多くの定型文も必要とします。

しかし、すべての状態を制御することも不可能または実行可能ではありません。 宣言型インターフェースを構築するのが難しい既存のライブラリーを使用することがあります。 次のような一部の状態は、効率的な方法でreduxストアにマウントするのも困難です。

  • テキスト入力
  • スクロール位置
  • ビューポートサイズ
  • マウスの位置
  • キャレットの位置/選択
  • キャンバスdataUrls
  • シリアル化できない状態
  • ..。

私がSOで見たこの質問:

http://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store

UI状態の管理、およびUI状態がコンポーネントに属するべきか、ストアに入る必要があるかについて、私が見たいくつかの混乱を反映しています。 #1287はこの質問に対する良い答えを提供しますが、最初はそれほど明白ではなく、議論の余地がある可能性があります。

さらに、このhttps://github.com/ericelliott/react-pure-component-starterのようなものを実装しようとしている場合、これは障害になる可能性があります。この場合、すべてのコンポーネントは純粋であり、独自の状態で発言権がありません。 。

複数のセッションのデータを管理する必要がある場合、reduxの単一の状態ツリーを使用するのは難しいことがわかりました。 同じ形状の任意の数のブランチがあり、それぞれに複数の一意の識別子がありました(データの取得元に応じて、異なるキーを使用します)。 レデューサー関数と再選択関数の単純さは、これらの要件に直面するとすぐに消えました-特定のブランチをターゲットにするためにカスタムゲッター/セッターを作成する必要があり、そうでなければ単純で構成可能な環境では過度に複雑でフラットに感じられました。 複数のストアがその要件に対処するための最良のオプションであるかどうかはわかりませんが、セッション(または他の同じ形状のデータ)を単一の状態ツリーに管理するためのツールがあれば便利です。

レデューサー間の状態キーの衝突の可能性が高くなります。
データの変更と宣言は、データが使用されるコードからはほど遠いです(コードを作成するときは、変数の宣言を使用する場所の近くに配置しようとします)

私の特定のユースケースの前置きをしましょう。私はVirtual-domでReduxを使用しています。ここでは、すべてのUIコンポーネントが純粋に機能しており、さまざまなコンポーネントのローカル状態を設定する方法がありません。

そうは言っても、間違いなく最も難しい部分はアニメーション状態で結ぶことです。 次の例はアニメーションの状態を念頭に置いていますが、これの多くはUIの状態に一般化できます。

アニメーションの状態がReduxにとって厄介であるいくつかの理由:

  • アニメーションデータは頻繁に変更されます
  • アニメーション固有のアクションは、アプリ内のアクションの履歴を汚染し、意味のある方法でアクションをロールバックすることを困難にします
  • コンポーネント固有のアニメーションが多数ある場合、Redux状態ツリーはコンポーネントツリーのミラーリングを開始します
  • animationStartedanimationStoppedような基本的なアニメーションの状態でさえ、状態をUIに結合し始めます。

一方、アニメーション状態を完全にRedux状態ツリーの外側に構築することには間違いなく課題があります。

DOMを操作して自分でアニメーションを作成しようとする場合は、次のようなものに注意する必要があります。

  • virtual-dom diffは、ノードに設定したカスタムスタイルなど、アニメーション操作を考慮しません。DOMノードにいくつかのスタイルを設定した場合、それらを削除するには、自分で削除する必要があります。
  • ドキュメント自体でアニメーションを実行する場合は、virtual-domの更新に注意する必要があります。1つのDOMノードでアニメーションを開始しても、そのDOMノードのコンテンツがアニメーションの途中で変更されたことがわかります。
  • ドキュメント自体でアニメーションを実行する場合は、ノードに設定したvirtual-domの上書きスタイルと属性に注意する必要があり、virtual-domによって設定されたスタイルと属性の上書きに注意する必要があります。

そして、virtual-domにすべてのDOM操作を処理させようとすると(そうすべきです!)、Reduxでアニメーションの状態を維持せずに、次のような難しい問題が発生します。

  • アニメーションの状態をコンポーネントにどのように公開しますか? コンポーネントのローカル状態? 他のグローバルな状態?
  • 通常、状態ロジックはReduxレデューサーにありますが、状態に応じてコンポーネントがどのようにアニメーション化されるかについて、コンポーネントに直接多くのレンダリングロジックを追加する必要があります。 これは非常に困難で冗長になる可能性があります。

現在、この問題を_React_で解決する上で大きな進歩を遂げるreact-motionのような素晴らしいプロジェクトがありますが、ReduxはReactに限定されていません。 また、そうではないはずです。多くの人が独自のビューレイヤーを持ち込み、Reduxと統合しようとしています。

好奇心旺盛な人にとって、Redux + virtual-domで私が見つけた最善の解決策は、2つの状態アトムを保持することです。Reduxはコアアプリケーションの状態を保持し、アクションに応じてその状態を操作するためのコアロジックを保持します。アニメーション状態を保持する可変オブジェクトです(私はmobservableを使用します)。 その場合の秘訣は、Reduxの状態の変化とアニメーションの状態の変化の両方をサブスクライブし、UIを両方の関数としてレンダリングすることです。

/* Patch h for jsx/vdom to convert <App /> to App() */
import h from './h'
import { diff, patch, create } from 'virtual-dom'
import { createStore } from 'redux'
import { observable, autorun } from 'mobservable'
import TWEEN from 'tween.js'
import rootReducer from './reducers'
import { addCard } from './actions'
import App from './containers/App'

// Redux state
const store = createStore()

// Create vdom tree
let tree = render(store.getState())
let rootNode = create(tree)
document.body.appendChild(rootNode)

// Animation observable
let animationState = observable({
  opacity: 0
})

// Update document when Redux state 
store.subscribe(function () {
  // ... anything you need to do in response to Redux state changes
  update()
})

// Update document when animation state changes
autorun(update)

// Perform document update with current state
function update () {
  const state = store.getState()
  let newTree = render(state, animationState)
  let patches = diff(tree, newTree)
  rootNode = patch(rootNode, patches)
  tree = newTree
}

// UI is a function of current state (and animation!)
function render (state, animation = {}) {
  return (
    <App {...state} animation={animationState} />
  )
}

// Do some animations
function animationLoop (time) {
  window.requestAnimationFrame(animationLoop)
  TWEEN.update(time)
}
animationLoop()

new TWEEN.Tween(animationState)
      .to({ opacity: 100 }, 300)
      .start()

// Or when you dispatch an action, also kick off some animation changes...
store.dispatch(addCard())
/* etc... */

素晴らしい反応をありがとうございました! それらを続けてください。

注意すべきことの1つは、Reduxを_all_状態に使用することを意図していないということです。 アプリにとって重要と思われるものは何でも。 入力とアニメーションの状態はReact(または別の一時的な状態の抽象化)で処理する必要があると私は主張します。 Reduxは、フェッチされたデータやローカルで変更されたモデルなどに適しています。

@taggartbg

複数のセッションのデータを管理する必要がある場合、reduxの単一の状態ツリーを使用するのは難しいことがわかりました。 同じ形状の任意の数のブランチがあり、それぞれに複数の一意の識別子がありました(データの取得元に応じて、異なるキーを使用します)。 レデューサー関数と再選択関数の単純さは、これらの要件に直面するとすぐに消えました-特定のブランチをターゲットにするためにカスタムゲッター/セッターを作成する必要があり、そうでなければ単純で構成可能な環境では過度に複雑でフラットに感じられました。

ユースケースをより詳細に説明する問題を作成していただけませんか? 欠落している状態の形状を別の方法で整理する簡単な方法があるかもしれません。 一般に、同じ状態の形状であるが異なるレデューサーによって管理される複数のブランチはアンチパターンです。

@istarkov

レデューサー間の状態キーの衝突の可能性が高くなります。

これがどのように発生するかを詳しく説明していただけませんか。 通常、どの状態スライスでも単一のレデューサーのみを実行することをお勧めします。 衝突はどのように発生しますか? 州を複数回通過していますか? もしそうなら、なぜですか?

@gaearon @istarkov :おそらく、さまざまなプラグインと関連ライブラリが同じトップレベルの名前空間を

ええ、これが@istarkovの意味ではなくても、これは良い点です。 (わかりません。)一般に、ライブラリはツリーのどこにでもレデューサーをマウントする方法を提供する必要がありますが、一部のライブラリではそれが許可されていないことを知っています。

@gaearon

ユースケースをより詳細に説明する問題を作成していただけませんか? 欠落している状態の形状を別の方法で整理する簡単な方法があるかもしれません。 一般に、同じ状態の形状であるが異なるレデューサーによって管理される複数のブランチはアンチパターンです。

よろしくお願いします! 少し時間があればそれをします。

これらのブランチを管理するための単一の共有レデューサーがありますが、これは間違いなくアンチパターンと見なされていると思います。 それでも、reduxが提供するパラダイムは、同一のブランチを不変の状態としてシリアル化/逆シリアル化するための完璧なユースケースであることにそれほど遠くないように感じます。 あるキーで特定のブランチをターゲットにするための想定されたロジックを使用して再選択のようなものを構築することが、reduxの精神に反する理由がわかりません。

私はそれについてオフスレッドでチャットできれば幸いです、私は非常に間違っている可能性があります。

@taggartbg :ここでコードがどのように見えるかを知らずに完全に話します。 このような状態に対処しようとしているのですか?

{ groupedData : { first : {a : 1, b : 2}, second : {a : 3, b : 4}, third : {a : 5, b, 6} }

各アクションの一部としてグループごとのIDキーを受け取る単一のレデューサー関数を使用することで、レデューサー側でこれに対処できるようです。 実際には、あなたのような何かを見ているhttps://github.com/erikras/multireducerhttps://github.com/lapanoid/redux-delegatorhttps://github.com/reducks/redux-multiplex 、またはhttps://github.com/dexbol/redux-register?

また、再選択側では、React-Reduxがコンポーネントごとのセレクターをサポートするようになったという事実が役立つ可能性があります。これにより、コンポーネントの小道具に基づいて選択を行うシナリオが改善されるためです。

@gaearon

注意すべき点の1つは、Reduxをすべての州で使用することを意図しているわけではないということです。 アプリにとって重要と思われるものは何でも。 入力とアニメーションの状態はReact(または別の一時的な状態の抽象化)で処理する必要があると私は主張します。 Reduxは、フェッチされたデータやローカルで変更されたモデルなどに適しています。

ドキュメントとチュートリアルを読んでいるReact開発者は、この一時的な状態の抽象化を自由に利用できるので、Reduxがプロジェクトにとって意味のある場所を検討するときに、これをすでに念頭に置いている可能性があります。

ただし、UIに機能的なアプローチを取りたいReactの経験がほとんどまたはまったくない開発者の観点からは、どの状態がReduxに属しているかが明確でない場合があります。 私は今、最初はReduxとvirtual-domで十分だったいくつかのプロジェクトを実行しましたが、アプリケーションが複雑になるにつれて、この他の「一時的な状態の抽象化」が必要になりました。 これは、いくつかの基本的なアニメーションフラグを使用してアニメーションレデューサーを初めて追加したときに常に明らかであるとは限りませんが、後でかなり苦痛になります。

どの状態がReduxに適しているか、そしてどの状態が他のツールによってよりよく解決されるかについて、ドキュメントが言及するのは良いことかもしれません。 React開発者にとっては冗長かもしれませんが、他の開発者がReduxを検討し、アプリケーションアーキテクチャを計画するときに念頭に置くことは非常に有益です。

編集:問題のタイトルは「すべての状態を単一の不変のアトムに格納することの欠点は何ですか?」です。 この「単一の不変アトム内のすべての状態」は、まさにReduxツリーで多くの間違った種類の状態を取得することになる種類の表現です。 ドキュメントは、開発者がこの種の罠を回避するのに役立ついくつかの明確な例を提供する可能性があります。

@gaearon Cycle.jsで、単一原子状態とピッピングのアーキテクチャの違いについて興味深い会話がありました。 ここに

これは100%Reduxに関連していないことは知っていますが、アプリ周辺のデータフローとしてObservableストリームを使用する実装とは異なる視点を提供したいと思いました(Cycleの場合、アプリは純粋関数のセットです)。

CycleのすべてがObservableであるため、あるルート変更から別のルート変更に状態を移動するのに苦労していました。 これは、ルートが変更されるとObservableにサブスクライバーがない状態が原因で、単一アトム状態を実装しない理由を考えたため、アプリで状態が変更されると、配管をバイパスしてトップレベルのストアに報告されます(ひどいビューここに図面)。

Cycleを使用すると、ドライバーで副作用が発生するため、通常、トップレベルのドライバーからコンポーネントにループを作成し、そのループを作成する必要があります。そのため、それをスキップして、コンポーネントのリッスンに直接戻りたいと思いました。 インスピレーションの源としてReduxを取り入れていました。

どちらが正しいか間違っているかではありませんが、今私はパイピングを行い、状態ドライバーを見つけました。必要に応じて状態をさまざまなコンポーネントにパイプする機能があり、リファクタリング、プロトタイピング、および強力な理解に非常に柔軟です。それぞれの国家の源、それぞれの消費者、そして彼らがお互いにどのように到達したか。

また、アプリケーションの初心者(もちろんCycleを理解している場合)は、ドットを簡単に接続し、状態の処理方法とパイプ処理方法、およびこれらすべてをコードから非常にすばやく視覚的に表現できます。

これが完全に文脈から外れている場合は事前に申し訳ありませんが、別のパラダイムがどのように同様の会話をしたかを示したいと思いました:smiley:

@timdorr

各ウィジェットは、2つのウィジェットが重複することなく独自の状態を維持する必要があります。 製品チームは各ウィジェットに割り当てられている可能性が高いため、他のコードが環境に与える影響を知らない可能性があります。

ウィジェットが独自の状態(おそらくそれ自体の(redux?)ストアでさえ)を持っていると、任意の(mashup-)アプリでスタンドアロンで動作し、プロパティの一部のみを受け取ることができる方が良いと思います。 天気ウィジェットについて考えてみてください。 それ自体でデータをフェッチして表示し、都市、高さ、幅などのプロパティのみを受け取ることができます。 別の例はTwitterウィジェットです。

@chicoxyzzy関連: https

素晴らしい議論です!

主に、複数のアプリ/コンポーネント/プラグインを組み合わせるのは不便だと思います。

ビルドしたモジュールを取得して別のモジュール/アプリにスローすることはできません。レデューサーをアプリのストアに個別にインポートする必要があり、特定のキーの下に配置する必要があるためです。モジュールは知っています。 これにより、 @connectを使用する場合、モジュールが状態全体に接続されるため、モジュールを複製することも制限されます。

例えば:

私はiMessageのようなメッセンジャーを構築しています。 currentConversationIdなどのredux状態があります。私のMessengerコンポーネントには@connect(state => ({ currentConversationId: state.messenger.currentConversationId }))ます。

このMessengerを新しいアプリに含めたいと思います。 動作させるには、 import { rootReducer as messengerRootReducer } from 'my-messenger'combineReducers({ messenger: messengerRootReducer })に追加する必要があります。

ここで、異なるデータを持つ2つの<Messenger>インスタンスをアプリに入れたい場合、Messengerコンポーネントでstate.messengerにバインドされているため、できません。 作るストアの特定のスライスに対して作業すると、カスタマイズされた@connect使用するようになります。

また、含まれているアプリにmy-messengerモジュールに存在する特定のアクション名があるとすると、衝突が発生します。 これが、ほとんどのreduxプラグインに@@router/UPDATE_LOCATIONようなプレフィックスが付いている理由です。

いくつかのランダム/クレイジーなアイデア:

1

@connectはストアの特定のスライスに接続できるため、 state.messenger[0] / state.messenger[1]など、独自のデータスライスに接続する複数の<Messenger>アプリに含めることができます。 state.messenger[1]<Messenger>に対して透過的である必要がありますが、それでもstate.messengerに接続し続けますが、含まれているアプリは次のような方法でスライスを提供できます。

@connect(state => ({ messengers: state.messengers }))
class App extends Component {
  render() {
    return (
      <div>
        {this.props.messengers.map(messenger =>
          <ProvideSlice slice={{messenger: messenger}}><Messenger /></ProvideSlice>
        }
      </div>
    )
  }
}

グローバルに正規化されたエンティティを使用する場合は問題があります。 state.entitiesも存在する必要があるため、スライスされた状態とともに、状態全体が何らかの形で存在する必要があります。 ProvideSliceは、 Messenger@connectが読み取ることができるコンテキストを設定できます。

もう1つの問題は、 <Messenger>コンポーネントによって起動されたアクションをバインドする方法がそのスライスにのみ影響することです。 たぶん、 ProvideSlice @connect下にmapDispatchToPropsアクションを実行します。

2

storereducer定義を組み合わせて、各レデューサーをスタンドアロンにすることもできます。 レデューサーがビューである間、ストアはコントローラーのように聞こえます。 「すべてがコンポーネントである」というreactのアプローチを採用すると(Angular 1.xのコントローラー/ディレクティブとは異なり)、コンポーネントとその状態/アクションを真にスタンドアロンにすることができます。 正規化されたエンティティ(つまり「グローバル状態」)のようなものには、Reactのコンテキストのようなものを使用でき、すべてのアクション(たとえば、 thunk getState thunk )/接続されたコンポーネントからアクセスできます。

@elado今のところ私が見た中で最も賢いアイデアは、「プロバイダー」を念頭に置いて設計することです: https ://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1

Thx @ sompylasar。 私は少し遠征中ですが、機会があれば、ReactとReduxにすでに精通しているプロバイダーのためにプロバイダーをより簡潔に説明する別の記事を書くことを計画しています。 すでにおなじみの人なら誰でも、それがクレイジーでシンプル/簡単であることに気付くはずです。 :)

@gaearonはまだこれについてのあなたの考えを聞きたいです。

私の最大の悩みの種は、2つの独立した階層(ビューとレデューサー)が同時に存在するため、作成、再利用、ネスト、および一般的にコンテナーコンポーネント内の移動が難しいことです。 また、実装の詳細としてReduxを使用するか、Reduxに適したインターフェイスを提供する再利用可能なコンポーネントを作成する方法も完全には明確ではありません。 (さまざまなアプローチがあります。)また、どこかで短絡するのではなく、すべてのアクションが「ずっと」上に行かなければならないことに感心していません。 言い換えれば、Reactローカル状態モデルのようなものを見てみたいのですが、レデューサーに支えられており、美しい抽象化ではなく、非常に実用的で実際のユースケースに合わせて調整したいと思います。

@gaearonそのようなシステムの実装を開始する方法について何かアイデアはありますか?

@gaearon

言い換えれば、Reactローカル状態モデルのようなものを見たいのですが、レデューサーに支えられています

トリッキーな部分は、Reactローカル状態がコンポーネントのライフサイクルに関連付けられていることだと思います。したがって、Reduxでローカル状態をモデル化しようとすると、「マウント」と「アンマウント」のアカウントがあります。 Elmには、1)コンポーネントにライフサイクルがなく、2)ビューの「マウント」がモデルから派生しているのに対し、Reactでは、ビューがマウントされた後のみローカル状態が存在するため、この問題は発生しません

しかし、あなたがほのめかしているように、Elmアーキテクチャは、「ずっと」上向きになるという独自の欠点があり、Reactの更新と調整のサイクルにうまく適合しません(たとえば、 shouldComponentUpdate()最適化を自由に使用する必要があります) ... render()内のディスパッチメソッドを単純に「転送」すると壊れます。)

ある時点で、reducers + setStateを使用して、それを1日と呼んでいるような気がします:D Reactが状態ツリー ...おそらくそれがここで実際に議論していることですが

これが@threepointonehttps://github.com/threepointone/redux-react-localで解決しようとしていることだと思い

@acdlite Elmの概念モデルはローカルな状態でうまく機能しますが、ライブラリを使用したり、マウントに焦点を合わせたり、視差効果を処理したりする必要がある実際のアプリケーションで使用するのは本当に苦痛のようです... https://を参照して

@slorberそうですね。 それは本質的に私が今言ったことではありませんか? :D

あなたの懸念は少し違うと思います。 _If_(大きな「if」)ReactでElmアーキテクチャを使用することになっていましたが、ライフサイクルやエスケープハッチとしてのsetStateなどに引き続きアクセスできるため、言及した問題はないと思います。

はい、同じページにいます@acdlite
Reactを使用したElmアーキテクチャは、この問題を解決します。
Elmでさえ、vdomフックの使用を許可する可能性があるため、将来的には可能性があります。

ただし、Elmアーキテクチャにはかなりの定型文が必要です。 アクションのラッピング/アンラッピングにFlowまたはTypescriptを使用しない限り、それは実際には持続可能ではないと思います。@ threepointoneのソリューションのように、このローカル状態を処理するためのより簡単な方法がある方がよいでしょう。

Elmアーキテクチャに関する別の懸念は、ネストされたアクションがローカルコンポーネントの状態に対してうまく機能することですが、分離されたコンポーネント間の通信が必要になった場合は、あまり良い考えではないと思います。 widget1がwidget2によって発生したイベントを見つけるために、深くネストされたアクションをアンラップしてイントロスペクトする必要がある場合、結合の問題があります。 私はここでいくつかの考えを表明し、2つの「メールボックス」でElmを使用することでその仕事ができると思います。 これは、redux-sagaがフラットイベントのある非elmアーキテクチャで役立つことでもあります。

私はスケーラブルなreduxアプリを設計する方法について良いアイデアを持ち始め、忙しくないときにそれについて何かを書こうとします

@gaearonここでの最後の投稿でreact-redux-provideについて話しているのですか? あなたがそうなら、あなたの応答に基づいて、あなたがそれを十分に詳しく調べていないことは明らかであるため、私は尋ねています。

また、 @ acdliteが状態ツリー

provideデコレータによって提供されるAPIは確かに抽象化されており、はい、現在reduxに依存していますが、 reduxに依存していると考えるのは間違った方法です。 Reactのcontextはまだ完全には開発されていないため、コンポーネントと状態ツリーの間の単なる「接着剤」であるため、考えてみてください。 actionsreducers (つまり、ストアの状態)を介して状態ツリーを操作および表現する理想的な方法と考えてください。 props基本的に、 actionsreducerspropTypes (および/または将来的にはcontextTypes )として宣言します。これは、他のimportと同じです。アプリ内。 これを行うと、すべてがめちゃくちゃ簡単に推論できるようになります。 Reactのcontextは、これらの基本機能を含むように進化する可能性があります。その場合、 @provideimport provide from 'react-redux-provide'をgrepして削除するのに、2秒かかります。次に、それに依存しなくなった単純なコンポーネントを残しました。 そして、それが決して起こらなければ、すべてが完全かつ予測可能に機能し続けるので、大したことはありません。

あなたは問題を単純化しすぎていると思います。 あなたはまだマウントとアンマウントを解決する必要があります。

@acdlite例がありますか?

あなたが言及しているものの1つが(たとえば) idなどに基づいてデータベースからpropsを取得することである場合、これは(あなたが考えることができる他のほぼすべての実用的なアプリケーションの中で)プロバイダーで簡単に実現できます。 私が取り組んでいる他のいくつかのことを終えた後、私はそれがいかに簡単であるかをあなたに示してうれしいです。 :)

興味深いかもしれません:反応状態の実装を比較しました
Elm、Redux、CycleJS ...(進行中の作業)

複数の店舗が必要になる可能性があるもう1つの理由は、複数の_タイムライン_、_タイムトラベル_、場所の保存、頻度の保存が可能であり、必要な場合です。 たとえば、単一のユーザーに固有のuser datauser actions 、およびユーザーのグループに固有のcommon datagroup actions (共有マルチユーザープロジェクトなど)またはドキュメント)。 これにより、元に戻すことができるもの(個人データへの変更)と修正されたもの(他のユーザーによって既に拡張された編集)を簡単に分離でき、保存や同期のサイクルを変えることもできます。

私たちはAngular1.xアプリケーションの設計/初期開発段階にあり、Angular 2.0の開発マインドセットがあり、reduxの使用を考えています。 これまで、いくつかの一般的なディスパッチといくつかの専用ディスパッチに対応する複数のストアでフラックスを使用してきました。

Reduxは私には素晴らしいように見えますが、アプリケーション全体に対して単一ストアの概念を推論することはできません。 他の人も上記で指摘しているように、さまざまなコンシューマーアプリケーションでホストされることになっている大きなウィジェットがいくつかあり、それぞれに専用のデータストアが必要です。

レデューサーは分割して、それを使用する機能/ウィジェットと一緒に使用できますが、ストアを作成するときは、すべてのレデューサーをクラブ化する必要があります。 ウィジェットが気にしないレデューサーへの直接/間接参照を持たずに存在することを望んでいます。

アプリでreduxを使い始める前に、このような状況でのベストプラクティスの概要を説明するドキュメントやコメントの形でのガイダンスを心から楽しみにしています。 どんな助けでも本当にありがたいです。 ありがとう。

@VivekPMenon :あなたの具体的な懸念は何ですか? 状態ツリーのサイズは? アクションに名前空間を付ける/レデューサーを分離しておくことができますか? レデューサーを動的に追加または削除する機能はありますか?

その価値については、Reduxアドオンエコシステムをカタログ化したリンクリストを完成させました。 これらの分離/複製/スコープの概念のいくつかに対処しようとするライブラリがいくつかあります。 レデューサーページローカル/コンポーネントの状態ページを見て、これらのライブラリのいずれかがユースケースに役立つかどうかを確認することをお勧めします。

@markerikson。 返信ありがとうございます。 私の混乱は主に2番目のポイント(分離)のあたりです。 チームの1つがフラックスベースのウィジェットAを作成し、その状態を管理するためにストアを使用する必要があると想定します。 もし彼らがjspm / npmパッケージを作成し、それを配布すると仮定します。 ウィジェットBを作成する別のチームの場合も同じです。これらは独立したウィジェット/パッケージであり、相互に直接/間接的に依存する必要はありません。

この文脈では、通常のフラックスの世界で。 ウィジェットAとBには、パッケージ内に独自のストアがあります(いくつかの一般的なアクションをリッスンする場合がありますが、一般的ではないアクションがさらにあると想定しています)。 また、両方のウィジェットはグローバルディスパッチャーに依存します。 reduxパターンでは、そのストアはどこに(物理的に)作成されますか。 ウィジェットAとBの両方のUIコンポーネントは、変更をリッスンし、状態を取得するためにストアインスタンスを使用する必要があります。 通常の流動では、それらは独自のストアインスタンスで動作します。 私は、シングルストアのコンセプトがこの状況でどのように機能するかを視覚化しようとしています。

@VivekPMenonこれは、私たちの一部が解決しようとしている有効な質問です。

ここでいくつかの洞察を得ることができます: https

また、ここでRedux-sagaについての私の回答を見てください

@slorberご指導ありがとうございます。 ここで出てくるアイデアを楽しみにしていますhttps://github.com/slorber/scalable-frontend-with-elm-or-redux

@taggartbg最終的に問題を開きましたか? 私は同様のことに興味があり

@slorber

まず、redux sagaは素晴らしいプロジェクトであり、それを取り入れたものであり、これは決してそれを減らそうとはしていません。

しかし、現在の状態では、どのプロジェクトでも使用しません。 ここにいくつかの理由があります

Sagasはステートレスです

ジェネレーターの状態をシリアル化することはできません。実行中は意味がありますが、完全にサガの状態にバインドされています。 これにより、一連の制限が発生します。

  • タイムトラベルジェネレーターはできません
  • それがサガの現在の状態につながった道をたどることはできません
  • 初期状態を定義することはできません
  • ビジネスロジックをアプリケーションの状態から分離することはできません[*]
  • あなたはサガを保存することはできません

[*]これは、アプリケーションのモデルの定義内でビジネスロジックを定義し、MVCのMとCを同じものにすることと解釈できます。これは、アンチパターンと見なすことができます。

アクションのディスパッチは非決定論的です

reduxを使用する大きな利点の1つは、状態がアクションにどのように反応するかを簡単に理解できることです。 しかし、redux-sagaは、サガのどの部分から始めているのかわからないため、アクションが予期しない結果になりがちです。

シリアル化可能な状態

問題は、サガの現在の状態がアプリケーションの現在の状態に影響を与え、人々は状態をシリアル化可能に保つことを強調しているということですが、それをどのようにサガに変換しますか? 状態を完全にシリアル化できない場合は、実際にはシリアル化されていません。 基本的に、サガも回復せずにアプリケーションの状態を回復することはできません。


ロジックを状態に保存するのは悪いことであり、私の意見では、これはある意味でreduxsagaが行うことです。

アプリケーションの状態が_your_アプリケーションに固有であると想定してもまったく問題ありません。 つまり、アプリケーションが_your_アプリケーション上で動作している限り、アプリケーションの状態は予測可能であると想定することもできます。 これは、実際にロジックを保存せずに、アプリケーションの動作を決定するストアの情報を保持しても問題がないことを意味します。

@eloytoro

私はあなたの懸念を理解しています。 redux-sagaの問題を見ると、これらのことが議論されており、解決策が見つかることがわかります。

また、redux-sagaはジェネレーターを使用して実装することを選択しますが、sagaパターンを実装する必要はまったくなく、長所と短所があります。

redux-saga内でredux状態を使用できる選択効果があるため、ジェネレーター内で状態を非表示にしたくない場合は、saga状態をredux-store内に直接配置できますが、より多くの作業が必要になります。

sagasの実行を追跡できるsagadevtoolsがあります。

タイムトラベルの可能な解決策はありますが、具体的な実装はまだありません

正しい。 非常に長いトピックで非常に興味深いものです。 私はさまざまな状態管理アプローチを比較してきました。

比較するとき、私はいくつかのことを念頭に置いています:

  1. 信頼できる唯一の情報源
  2. サーバー側でレンダリングし、クライアント側で初期データをフィードするのはどれほど簡単/難しいか
  3. 元に戻す/やり直しやリロード後の永続的な履歴ストレージなどのdevtools。
  4. アニメーション、アニメーション、アニメーション。 私はいつも自分自身に_「アニメーションはソリューションaまたはbでどのように機能するか」と自問しています。 私が開発している製品はUX中心であるため、これは私にとって重要です。 @jsonnull
  5. 別のアプリ内でのコンポーネントの再利用
  6. 整合性を維持するための参照データのハイドレーション(_major pain _)(サーバー側またはその他のデータソースからの初期データのフィードに関連)
  7. このような議論のための他の開発者のコ​​ミュニティ

ですから、これらは私が国家管理について考えるときに警戒している点です。 ほとんどのボックスにチェックマークを付けると、Reduxが勝者になります。 私はReduxをかなりよく知っており、クライアント側のjsの領域で落ち着いて呼吸するようになった最初のことでした。

私が調べてきた他の最も注目すべきプロジェクトは、SAMパターン(_考え方を理解するのが難しく、ツールがない_)とMobx(_可変ストレージで知られている_)です。

どちらも信頼できる唯一の情報源という考えを共有しており、それらがどのようにそれを処理するかを見るのは興味深いと思います。

Mobxに関する私の(もちろん限られた)研究はこれに帰着しています:

  1. Mobx(Reduxと同じように)は、信頼できる唯一の情報源を提唱していますが、観察可能な特性を持つクラスとしてです。 この概念は、全体としてreduxとfluxとの違いを生み出します。

    1. Mobxは、サブストア(_nested /グローバルシングルストアの一部_)も提唱しています。 私には、クライアント側のスキーマを定義するのとよく似ています。 おそらくサーバーにもスキーマがあるので、少し面倒です。 redux-ormとの類似点があります。 サブストアは、小道具としてコンポーネントに渡されることを目的としています(このようにして、コンポーネントは、状態を小道具またはグローバルツリーの特定のキーにマッピングすることに依存しません)。 ただし、単一のプロパティを渡すと、「プロパティ」の概念が崩れます。これらは、コンポーネントに必要なものを説明するものでなければなりません。 これは、React propTypesを使用して、渡されるストアの形状を定義できる場合、それが問題になるかどうかを実際に考えさせます。 また、ストアとサブストアをクラスとして使用すると、正規化を必要とせずにデータを直接参照できますが、次の理由で初期データをフィードする場合は意味がありません。

    2. ただし、クラスとして保存するという考え方は、開発者がすぐにシリアル化できないことを意味します。つまり、初期データをフィードする場合は、それを行う必要があります。

  2. 履歴と元に戻す/やり直しは、状態が変更された後のスナップショットを撮ることで実現できます。 ただし、シリアライザー、デシリアライザーは自分で作成する必要があります。 例を次に示します: https
  3. シリアライザーを作成することの良い点は、何も省略できることです。 非永続的なアニメーション関連のデータを保存するのに適した場所のようです。 したがって、シリアル化する場合は無視するだけで、アニメーションの手順でいっぱいのやり直し/やり直しはありません。
  4. Mobxは、 reselect + connectデコレータではなく、(ストアのメソッドとして)ストア内のデータの派生を保持することを提唱しています。 これは私にはちょっと間違っているようです。
  5. mobxは、その観察可能な性質により、効率的な純粋コンポーネントを簡単に定義できます。 とにかく内容が観察できるので、プロパティの浅い比較は問題ではありません。

SAMとReduxを比較して、ロジックがどのように流れるかを考えたいと思います。

  1. 戻ってきた:

    1. ストアを定義する

    2. ストアの変更を処理する関数を定義します(レデューサー)

    3. ストアの変更ごとにイベント(reduxアクション)識別子(reduxアクションタイプ)を定義し、一致したときに特定のレデューサーを実行します

    4. ストアをコンポーネントコンテナプロパティにマップし、オプションで、イベント/ディスパッチアクションをトリガーする関数としてユーザーインタラクション(クリック、タイプ、スクロールなど)のハンドラーを渡します。

    5. コンポーネントは、すべてのレデューサーと照合されるイベント(reduxアクション)をトリガーします

    6. 新しい状態でレンダリングします。

  2. SAM:

    1. ストアを定義します。

    2. ストアのアクセサー/セッター関数を定義します。 このセッターはすべてのデータを受け入れ、純粋にデータをストアのどこに配置するか、サーバーにデータを要求する必要があるのか​​、そこで永続化する必要があるのか​​を判断しようとするデータに基づいています。 データを検査するだけでは、データをどう処理するかを理解できるとは限らないため、識別子/フラグが使用されることがあります( isForgottenPassword )。 このセッターは、データを検証し、代わりにエラーを保存することも目的としています。 セッターが終了した後->新しい状態で再レンダリングします。

    3. データの一部を受け入れる関数を定義し、それを変換して(reduxレデューサーの代わりに)、変換されたデータを渡すセッターを実行します。 これらの関数は、ストアの形状について考えています。 彼らは渡されたデータしか知りません。

    4. ストアデータをコンポーネントコンテナのプロパティにマップしますが、ユーザーインタラクションハンドラーのハンドラー(クリック、入力、スクロールなど)を渡すことも義務付けられています。 これらは実際の関数(reduxではレデューサーと呼ばれます)であり、ディスパッチ呼び出しを実行する関数ではないことに注意してください。

    5. 注:アクションのディスパッチ、レデューサーのストレートパスはありません。

したがって、redux、SAM、Mobxはすべて、状態を1か所に保持します。 彼らがそれをする方法は、彼ら自身の挫折をもたらします。 これまでのところ、reduxとそのエコシステムに勝るものはありません。

私が見る最大の欠点は、シリアル化できない状態の処理でもあります。
私の場合、プラグインAPIがあるため、Reduxとは関係がないが、アプリケーションと対話する必要があるアクションとロジックがあります。 したがって、基本的には、最初に、特定の状態に基づいて一連のクラスを再作成する必要があります。

私は、この種のシリアル化できない状態をより良い方法で処理する方法について、いくつかの概念に取り組んでいます。 任意の状態からの再作成を含みます。 関数/オブジェクト参照以外のすべてをストアに戻すことができました。 残っているのは、アプリケーションと相互作用し、そのためにアクションと相互作用する、定義されたAPIを備えた独自のライフサイクルを持つオブジェクトです。 -UIレンダリングだけでなく、カスタムロジックについても、Reactコンポーネントに似ているように感じます。

ErikMeijerのこの記事を読んだことがあるかどうかはわかりません。 簡単な引用:

一般に信じられていることとは反対に、状態変数を不変にすることは、容認できない暗黙の命令効果を排除することにはほど遠いです。

おそらく、この不変性のナンセンスをすべて再検討する時が来たのでしょう。 単一の状態ツリーのサイズが大きくなると、それはパフォーマンスの低下だと誰もが言うでしょう。 SAMパターンにより、突然変異はプログラミングモデルの主要な市民になります。これを行うと、Sagasが不要になることから、効果を追跡するために必要なこれらすべての補助的なステートマシン(フェッチなど)に至るまで、多くの単純化が行われます。

この記事はあなたに有利に主張するものではありません。 Erik Meijerは「この不変性のナンセンス」を再考し、それを完全に受け入れます。 彼は、状態の突然変異を取り除くことは基本的な最初のステップであり、基本的ですが十分ではないと主張します。 彼の最終的な目標は、状態管理を時代遅れにする暗黙の副作用を完全に排除することです(管理する状態はありません!)。

私はエリック・マイヤーの作品のファンであり、彼が私と同じビジネス、技術、知的制約の下で活動していないことを念頭に置きながら、彼の言うことを熱心に読んだり聞いたりします。

ソフトウェアエンジニアリングの重要な問題は、割り当てがIS-Aの突然変異であると誰もが信じているように見えることです。 プログラミング言語は割り当てのみをサポートし、突然変異はプログラマーの解釈に任されています。

一連の割り当てに関数ラッパーを追加することは、自動的に状態を変更するための最良の方法になるわけではありません。 それはナンセンスです。

(アプリケーション)状態突然変異の背後に理論があります。それはTLA +と呼ばれ、奇妙なことに...エリックはTLA +について言及していません...

JJ-

混乱が多いようですので、詳しく説明させていただきます。 Erikの論文は、状態の変更に関しては、命令型プログラミング言語がいくらか壊れていることを雄弁に示しています。 私は誰もがそれに同意すると思います。

問題は、どのように対処して修正するかです。 これが「第一原理」アプローチを必要とする種類の問題であることに同意しませんか?

ランポート博士は、コンピュータサイエンスで私たち全員が同意できる原則はほぼ1つであり、「コンピュータサイエンスの多くはステートマシンに関するものです」と述べています。 「しかし、コンピューターサイエンティストは、計算を記述するために使用される言語に非常に集中しているため、それらの言語がすべてステートマシンを記述していることにほとんど気づいていません。」

実際、すべてのプログラミング言語では、セマンティクスに関係なく「ステートマシン」を指定できます。 残念ながら、多くのプログラミング言語は「状態アクション」ではなく「アクション」に偏っています。 これには正当な理由があり、アクションベースの形式ではるかに効率的にコード化される問題の大きなクラスがあります(ステートマシンの状態はほとんど無視できます)。 それらを無視すると、本質的に、すべてのアクションが任意の状態で可能であると述べますが、これは一般的に真実ではないことは誰もが知っています。

エリックのハハの例については、好きなだけ笑うことができます。
// prints Ha var ha = Ha(); var haha = ha+ha;

しかし、彼が述べているのは、あなたが与えられた状態で間違った行動を取っているということだけです。 それ以上でもそれ以下でもありません。

関数型プログラミングは何をもたらしますか? あまりありません。 それは、行動を起こすこととそれがもたらすかもしれない結果との間の関係を非常に複雑な方法で表現します。 それは、巨大な迷路に矢(スマートなモナドの矢でさえ)を投げて、あなたがあなたの目標を達成することを望んでいるようなものです。

TLA +は、効果を処理するためのはるかに自然な方法をもたらします。これは、最初に、ステップとは何か、突然変異がアクションにどのように関連するかについて明確な概念があるためです。 TLA +は無視してかまいませんが、地球が平らで宇宙船を操縦するのが非常に難しいという前提で、月にロケットを送ろうとしたと不平を言っているようなものです。 私たちは、十分な数の人々が彼らの言っていることを信じている限り、人々が現実を曲げることができると信じている世界に住んでいることを知っています...それでもあなたはどこにも行きません。

繰り返しになりますが、私はエリックがTLA +についてどう思っているかを知りたいと思っています。私は、LinkedInで彼とつながり、質問をしました。 答えは得られませんでした...結局、ランポート博士はチューリング賞を受賞しただけでしたが、おそらく彼の時間の価値はありません。

正直なところ、 @ jdubray 、あなたはこの時点で境界線をトローリングしています。 あなたはこのスレッド、他の問題、そして他の場所であなたの議論を何十回も繰​​り返しました。 あなたはダンとReduxコミュニティを侮辱し、資格情報を振り回し、「TLA +」と「Lamport」という言葉を、まるで聖杯であり、生命、宇宙、そしてすべてへの答えであるかのように呼び続けています。 あなたはSAMがReduxよりもはるかに優れていると主張しましたが、証拠を示すための多数の招待にもかかわらず、意味のある比較アプリの方法で多くを書くことはありませんでした。 今までにまだ納得していない人の心を変えるつもりはありません。 正直に言って、もっと生産的なことをするために時間を費やすことをお勧めします。

この時点でスレッドを閉じます。 コメントは続けることができると思いますが、ここで実行可能なものは絶対にありません。

@markeriksonどこにも行かなくても、この議論を続けたいと思います。このトピックは私にとって非常に興味深いものです。 彼の有毒なコメントを一掃し、良いオープンソースの議論を続けたほうがよいでしょう。
@antitoxic (しゃれは意図されていません)が、彼が言及しているアプローチがどこで役立つかを明確にしたことに加えて、彼の介入のために他の問題がどれほど悪いことが判明したかを知っているので、もう聞く意味はありません

私のコメントを削除しても大丈夫です、なぜそのような素晴らしい会話を台無しにするのですか?

@jdubrayあなただけがそれを

私はTLA +や良くないことは何も言いません、そして多分将来誰かがあなたがそれを売るより良い仕事をするでしょう。 それは私たちがまったく理解するのに役立たないあなたがそれを説明する方法であり、あなたの永続的な見下すような口調は私にTLA +について学ぶことに時間を費やすことを勧めません。

Erikの論文は、状態の変更に関しては、命令型プログラミング言語がいくらか壊れていることを雄弁に示しています。 私は誰もがそれに同意すると思います。

実際、すべての投稿で常にI believe everyone agrees on thatような用語を使用します。 これは、人々にとって良い、役立つ、または歓迎するものではありません。 私たち全員がデフォルトであなたに同意するわけではなく、私たちのほとんどはエリックの論文を読んでおらず、今後も読みません。 以前にたくさんの紙を読んでもらい、あなたに同意しない、または同意しない場合に彼らを馬鹿げていると感じさせずに、人々を歓迎する方法で説得できない場合、それはあなたのアイデアを広めるのに役立ちません。

TLA +は無視してかまいませんが、地球が平らで宇宙船を操縦するのが非常に難しいという前提で、月にロケットを送ろうとしたと不平を言っているようなものです。

これは間違いなく類推のように見え、確かに第一原理ではありません。 真剣に、あなたはこの声明が「華麗な議論」と見なされるべきだと思いますか?

TLA +にすべての注意を独占し、TLA +に興味のない人々が、平均よりも具体的な何かで月にロケットを送ろうとするのを実際に防ぐので、話し合わないときの議論は実際にはもっと華やかだと思いますReduxのように、人々は理解できます。

それでもTLA +が価値がないと言っているのではなく、間違った方法で、間違った人々に話しているだけです。 エリックがあなたに答えなかったのなら、それは彼もあなたを理解できないからだろうか? たぶんあなたはとても賢いので、ランポートだけがあなたを理解することができますか? 将来的にはチューリング賞を受賞するかもしれませんか? わかりませんが、確かなことの1つは、現在の議論では何もテーブルにもたらされないということです。

たぶん@gaearonのようにして、

@Niondir状態を回復する必要がある場合に、 考え出しました。
それは、従うべきパターンまたは一連のルールのようなものです

  • サガを分けて、それぞれが1つのput持たないようにします。 putを超えるサガがある場合は、それらを分割し、 call効果でチェーンします。 例えば
function* mySaga() {
  yield put(action1());
  yield put(action2());
}

// split into

function* mySaga() {
  yield put(action1());
  yield call(myNextSaga);
}

function* myNextSaga() {
  yield put(action2());
}
  • take効果のあるSagasは、 takeとsagaの開始点に従うロジックに分割する必要があります。 例えば
function* mySaga() {
  yield take(ACTION);
  // logic
}

// split it into

function* rootSaga() {
  yield takeLatest(ACTION, mySaga);
}

function* mySaga() {
  // logic
}
  • 佐賀ごとに一種の「チェックポイント」機能を作成します。つまり、初期化時に状態から読み取り、そこからcall必要な佐賀を読み取ります。
function* rootSaga() {
  // emit all forks and take effects that need to run in the background
  yield call(recoverCheckpoint);
}

function* recoverCheckpoint() {
  const state = yield select();
  if (state.isFetching) {
    // run the saga that left the state like this
  }
}

これが機能する理由の説明は、一般的に正しい一連の仮定に基づいています

  • takeエフェクトには、ストアにディスパッチするアクションが必要です。ユーザーがページを操作するか、sagasが既に実行されているまでアイドル状態のままであるため、回復している場合でも、これらのsagasをinitで常に呼び出すのが安全です。
  • サガごとに複数のput呼び出しを許可しないと、状態のアトミックな変更が可能になります。同じように、アクションは、サガがその容量に制限される単一の方法でのみ状態を変更できます。サガとアクションディスパッチの相関関係。

このようにサガを分割すると、いくつかのプラスの効果もあります

  • Sagasはより再利用可能です
  • Sagasはより簡単に変更されます
  • Sagasは理解しやすい

しかし@slorber

  • (10分)紹介ビデオはこちら: https
  • 私がビデオで話しているコードサンプルはここにあります: https

多くの人が、この「釣りゲーム」サンプルは、動的な状態表現や「次のアクション述語」など、SAM全体がどのように機能するかを直接示しているため、コード化するのに非常に興味深いと感じています。 NAPはSagasの必要性を軽減し、単一の状態ツリーであるReduxの原則#1を破る必要はありませんが、私は何を知っていますか?

Ableton Liveを使用していると、「Ableton LiveGUIをReact / Reduxアプリとして作成できるでしょうか?」という考えが頭に浮かぶことがあります。 答えはノーだと思います。許容できるパフォーマンスを得るのは難しすぎるでしょう。 Qtで書かれている理由と、Qtがシグナル/スロットアーキテクチャを備えている理由があります。

MeteorでReact / Reduxを使用しています。 コールバックのReduxアクションをMongo.Collection.observeChangesディスパッチすることで、mongoドキュメントをImmutable.js状態で保存します。 最近、数千のドキュメントをサブスクライブすると、Meteorが最初のサブスクリプション結果を1つずつ送信するため、数千のReduxアクションがディスパッチされ、数千のImmutable.js操作と数千の再レンダリングが発生するため、UIの起動が非常に遅くなることがわかりました。できるだけ。 よくわかりませんが、RethinkDBもこのように機能すると思います。

私の解決策は、これらの収集アクションを取り除いて、それらを調整された速度でバッチでディスパッチする特別なミドルウェアを作成することでした。これにより、1回の状態変更で最大800のドキュメントを追加できます。 パフォーマンスの問題は解決しましたが、イベントの順序を変更すると、状態に一貫性がなくなる可能性があるため、本質的にリスクが伴います。 たとえば、サブスクリプションステータスアクションが同じスロットルを介してディスパッチされていることを確認する必要がありました。これにより、すべてのドキュメントがRedux状態に追加される前に、サブスクリプションが準備完了としてマークされませんでした。

@ jedwards1211同様の問題が発生しました(初期データを構成する小さなメッセージがたくさんあります)。

バッチ更新を行うことで同じ方法で解決しました。 ミドルウェアの代わりに、(小さな)カスタムキューメカニズムを使用しました(すべての更新を配列にプッシュし、更新するものがある場合は定期的にすべての更新を同時に更新します。

@andreieftimieええ、プロットのドラッグやズームなど、すぐに更新する必要のあるUIインタラクションがあるため、私のアプリでは

その価値のために、私は「ブランチ」を作成することを考えました。 それは完全に単一店舗の原則に反しますが、それは良い妥協点のように見えました。

https://github.com/stephenbunch/redux-branch

@ jedwards1211アプリが複雑になるにつれて、特定のストアが全負荷を処理しないように、最終的に特定のタイプのデータと特定のタイプの更新を

バックエンドと同期させたいコア状態、実行中の現在のアクションに応じてアプリUIコンポーネントが正しく表示されるようにするアニメーション状態、追跡しているデバッグデータがあるとします。別々に。

この不自然な例では、これらすべてを単一のReduxストアから構築することは確かに可能ですが、アーキテクチャの方法によっては、状態ツリーのさまざまなブランチの懸念の間にぼやけた線があり、明確さが不足している可能性があります特定のアクションが相互作用することを意図している状態に関して。

状態のスコープを設定する方法と、状態に期待するパフォーマンス特性に応じて、さまざまな状態管理戦略を使用することは完全に合理的だと思います。 Reduxは、アクションをロールバックまたは再生したり、シリアル化して後で戻したりできる状態に適しています。 アニメーション状態の場合、必要に応じて可変ストアまたはリアクティブストアを使用できます。そうすることでオーバーヘッドが少なくなり、この一時的な(ただし必要な)相互作用状態でアプリケーション状態を汚染することはありません。

本当に迅速です。次のReactファイバーアーキテクチャを使用してポイントを示したいと思います。 ここにリンクがあります。私の理解が少し古くなっている場合はお詫びします。 大まかに言って、ファイバーアーキテクチャーは、Reactコンポーネントツリーを介して伝播するさまざまな種類の更新があり、これらの更新がいつどのように実行されるかについてさまざまな期待があることを認識しています。 アニメーションの更新が高速で、応答性が高く、信頼性が高く、大きなインタラクションの更新によってアニメーションなどにジャンクが発生することは望ましくありません。

そのため、ファイバーアーキテクチャは更新を作業パケットに分解し、実行中の作業に基づいた優先度に従ってそれらをスケジュールします。 アニメーションは優先度が高いため、中断されることはなく、機械的な更新が遅いほど優先度は低くなります。

Reactファイバーに関する多くの詳細をスキップし、おそらくプロセスでいくつかの間違いを犯しましたが、私のポイントは、これがさまざまなタイプのデータを使用するデータストアに必要だと思う一種のきめ細かいアプローチであるということです。

今日、複雑なアプリを作成している場合は、いくつかの異なるストアに支えられたReduxのようなトップレベルのストアクラスから始めます。 大まかに:

  • トップレベルのデータストアには、着信アクションのキューがあります
  • アクションは、迅速にディスパッチされる必要があるアニメーションアクション、または優先度の低いアプリケーションインタラクションのいずれかです。
  • キュー内のアニメーションアクションは、requestAnimationFrameループから監視可能なバックエンドにディスパッチされます
  • キューからのアニメーションアクションが完了すると、インタラクションアクションがreduxにディスパッチされます

この話は、Reduxに基づく完全な状態ソリューションにかなり近いようです。 Reduxは、一連のアクションの後に表示したいデータに適しており、注意深い処理が必要な状態は他にもたくさんあります。

@jsonnullアニメーションの状態をストアに保存する必要はありませんでした。ローカルの状態は、アニメーションのユースケースにとって常に理想的でした。 ローカルアニメーションの状態が完全に適していないユースケースがあるかどうか知りたいです。 しかし、彼らは新しいアーキテクチャについて素晴らしいアイデアを持っているようです。

@ jedwards1211ローカルアニメーションの状態が機能しない場合がいくつか考えられます。 確かに、すべてのアプリケーションで遭遇するわけではありませんが、十分な頻度で発生すると思います。

あるケースでは、ローカル状態がないRedux以外のライブラリを使用しています。 (はい、ここで少し不正行為をしていることはわかっています。)非常に無駄のないハイパースクリプトアプローチを使用している場合、仮想domはなく、純粋な関数型コンポーネントです。ローカル状態の代わりに、アニメーションを渡す必要があります。ルートノードまたは再レンダリングするノードに状態を示します。

この場合、いくつかのアニメーションの詳細が設定された状態ツリーを使用すると、ローカル状態の欠如を回避して、アニメーションをトリガーしたり、アニメーションを一定時間実行したりすることができます。

ここで重要なのは、これらのアニメーションを実行する方法が2つあることです。レンダリングの一部として実行し、「状態の関数としてのUI」メタファーをそのまま維持するか、DOMを個別に変更します。 ほとんどの場合、より良い答えは、変更するのではなく、単に再レンダリングすることです。


ここで、アニメーションの状態をローカルに保つ機能がある例を示します。その上にReactベースのUIを備えたブラウザーゲームを構築します。

  • 通常、ティックを使用してゲームを運転します...ゲーム時間は0から始まり、フレームごとに増加します。 コアゲームのアニメーションは、ローカル状態がないキャンバスまたはWebGLで実行される可能性があるため、これらのティックに基づいてアニメーションの開始時間と進行状況をベースにします。

例:スプライトキャラクターのアニメーションは10フレームで構成されており、アニメーションを400ミリ秒以上再生したいので、2ティックごとに描画されるスプライトを変更します。

より高い解像度が必要な場合は、ティックをタイムスタンプにすることもできます。

  • ゲームを一時停止することもできます。その場合、ReactUI側で実行される一部のアニメーションを停止することをお勧めします。

このゲームの例では、 tickアクションの一部としてティックをインクリメントする必要はありません...代わりに、おそらく観察可能な構造で、ティックを個別にインクリメントする必要があります。 また、キーボードのキャラクターの動きや攻撃などのReduxアクションをディスパッチするときに、現在のティックまたは現在の時刻をそれらに渡すので、アクションが発生したティックを記録でき、UIコンポーネントはアニメーションが開始された時刻をローカルに記録できます。 。

これで、グローバルアニメーション状態がインタラクション状態から分離されました。Redux状態のみを使用して、またはアクションを再生することで「再生」を実行する場合は、可能であり、状態の一部としてティックをインクリメントする必要はありません。木。

一般的に言えば、Reduxに開始/停止時間を渡し、コンポーネントに残りの状態をローカルに維持させることによって、アニメーションを調整することもはるかに優れています。

これはゲームだけに当てはまるわけではありません。 Reactを使用してサイトで一連の長時間実行されるアニメーションを実行する場合など、アニメーションの拡張シーケンスはこれを通過する可能性があります。

@jsonnullかっこいい、それは素晴らしい例です、それを共有してくれてありがとう。

簡単に言うと、Reduxでは、相互にリンクされた状態オブジェクトを保存することはできません。 たとえば、ユーザーは多くの投稿を持つことができ、投稿には多くのコメントを含めることができ、それらのオブジェクトを次の方法で保存したいと思います。

var user = {id: 'user1', posts: [], comments: []}
var post = {id: 'post1', user: user, comments: []}
user.posts.push(post);
var comment = {id: 'comment1', post: post, user: user}
post.coments.push(comment)
user.comments.push(comment)
appState.user = user

Reduxでは、循環参照を使用したオブジェクト階層の使用は許可されていません。 Mobx(またはCellx)では、オブジェクトを使用して1対多および多対多の参照を作成するだけで、ビジネスロジックが何度も簡素化されます。

@bgnorlov reduxパッケージには、あなたが話しているような循環参照を禁止するものは何もないと思います。それらは、レデューサーでstateクローンを作成することをより困難にするだけです。 また、Immutable.jsを使用して状態を表すと、循環参照を使用して状態を変更する方が簡単な場合がありますが、よくわかりません。

外部キーを使用してredux状態の関係をモデル化することもできますが、これはORMのようなオブジェクト参照を使用するほど便利ではないことを理解しています。

var user = {id: 'user1', postIds: [], commentIds: []}
var post = {id: 'post1', userId: user.id, commentIds: []}
user.postIds.push(post.id);
var comment = {id: 'comment1', postId: post.id, userId: user.id}
post.commentIds.push(comment.id)
user.commentIds.push(comment.id)
appState.userId = user.id
appState.posts = {[post.id]: post}
appState.comments = {[comment.id]: comment}

// then join things like so:
var postsWithComments = _.map(appState.posts, post => ({
  ...post,
  comments: post.commentIds.map(id => appState.comments[id]),
})

@ jedwards1211で、reduxの循環参照を使用して状態を複製するには、reducerは、変更の影響を受けるオブジェクトの新しいコピーをすべて返す必要があります。 オブジェクトへの参照が変更された場合、関連するオブジェクトも新しいコピーである必要があり、これは再帰的に繰り返され、変更のたびにまったく新しい状態が生成されます。 Immutable.jsは循環参照を保存できません。
正規化アプローチでは、イベントハンドラーがデータを必要とする場合、グローバル状態からIDでオブジェクトを取得するたびに必要になります。 例-イベントハンドラーのどこかでタスク兄弟をフィルタリングする必要があります(タスクは階層構造を持つことができます)reduxを使用すると、アプリの状態にアクセスするためにサンクをディスパッチする必要があります

var prevTask = dispatch((_, getState)=>getState().tables.tasks[task.parentId]).children.map(childId=>dispatch((_, getState)=>getState().tables.tasks[childId])).filter(task=>...) [0]

またはセレクター付き

var prevTask = dispatch(getTaskById(task.parentId)).children.map(childId=>dispatch(getTaskById(childId)).filter(task=>...)[0]

そしてこの定型文は、私が簡単に書くことができるとき、mobxバージョンと比較してコードを混乱に変えます

var prevTask = task.parent.children.filter(task=>...)[0]

@ jedwards1211、@bgnorlov:FWIWが、これはなぜ私のような理由の一つですReduxの-ORM 。 Reduxストアを正規化したままにすることができますが、これらのリレーショナル更新とルックアップをより簡単に行うことができます。 実際、その最後の例は基本的にRedux-ORMと同じです。

Redux-ORMの基本コアコンセプト、高度な使用法について説明したブログ投稿をいくつか書きました。

@markeriksonかっこいい、ヒントをありがとう!

@gaearon

注意すべき点の1つは、Reduxをすべての州で使用することを意図しているわけではないということです。 アプリにとって重要と思われるものは何でも。 入力とアニメーションの状態はReact(または別の一時的な状態の抽象化)で処理する必要があると私は主張します。 Reduxは、フェッチされたデータやローカルで変更されたモデルなどに適しています。

このコメントは非常に役に立ちました。 パーティーに遅れていることは知っていますが、それが私のポイントの一部かもしれません。 そのコメントが投稿されてから1年以上経ちましたが、このアイデアが表現されているのを私が見たのはこれだけです。

角張った、明らかに非フラックス/リダックスの背景から来て、あなた自身でそのアイデアを定式化することは非常に難しいです。 特に、多くの例がまだテキストボックス内のすべてのキーアップに対してアクションを作成している場合。 誰かがその引用をすべてのReduxドキュメントページの上部にある50pxのテキストに入れてくれることを願っています。

@leff同意しました。 私はその正確なアイデアをしばらく前に統合しましたが、それは十分に呼び出されません。 履歴管理にReduxを使用している場合でも、Reduxスタイルの状態管理に負担をかける必要のない一時的な状態がたくさんあることがよくあります。

これは、たとえば、豊富な元に戻す/やり直しを行う成熟したデスクトップアプリなど、作業する機会があった人々にとって必ずしも驚きではありません。 しかし、Reduxを介してこれらのアイデアを思いついた新参者の海にとって、それは啓示となるでしょう。

@bgnorlovは、Immutable.jsで循環参照を保存することが不可能であるという良い点です、私はそれについて考えたことはありません! でもそれは素晴らしい機能だと思います。

最近では、コンテキスト外で使用することのない一時的なビュー固有の状態(たとえば、 redux-formインスタンスの状態)であっても、ほとんどすべてをReduxに保存する傾向があります。 ほとんどの場合、問題なくそのような状態をreduxから移動できます。 しかし、利点は、将来それに対応するために外部コンポーネントが必要になった場合に備えて存在することです。 たとえば、フォームにエラーがある場合や送信中の場合に変更されるアイコンをナビゲーションバーに含めることができます。

覚えておくべき最も重要なことは、すべての正規化された状態をReduxに置くこと(つまり、ビューをレンダリングするために結合する必要があるもの)が最も使いやすくなることです。 何かと結合する必要のない状態は、おそらく問題を引き起こすことなくReduxの外部に住むことができますが、通常、Reduxに配置しても問題はありません。

FWIW、ドキュメントは、 http: //redux.js.org/docs/faq/OrganizingState.html#organizing -state-only-redux-stateに従って、すべてがReduxに入る必要はないことを指摘しています。

Reduxが「適切」であるかについて、オンラインでかなりの最近のおしゃべりがありました。 文字通りすべてをReduxに入れることのメリットを理解している人もいれば、面倒でサーバーから取得したデータのみを保存したいという人もいます。 したがって、ここには絶対に決まったルールはありません。

いつものように、ドキュメントを改善するためのアイデアがあれば、PRは大歓迎です:)

レデューサーを再利用する必要があり、redux状態で「サブ状態」を割り当てることができる場合は、それを簡単に行うためのプラグインを開発しました(React統合を使用)

https://github.com/eloytoro/react-redux-uuid

@eloytoro IMOサードパーティのレデューサーがハードコードされたアクションタイプまたは州内の場所で問題が発生した場合は、プロジェクトで問題を開く必要があります...人々が再利用可能なレデューサー/アクションを学ぶにつれて、やがてそれは受け入れられない設計慣行になりますクリエイターパターン。

@eloytoro私はそのようなものを書くつもりでした。 ご参考ありがとうございました!

@ jedwards1211あなたは間違った考えを持っていると思います。 サードパーティのレデューサーやそれらの問題についてはまったく触れていませんが、動的サイズのコレクションでレデューサーを再利用できるようにするための問題をスニペットがどのように解決できるかを紹介しようとしています。

@avesusそれがあなたのために働くことを願っています

@eloytoroああ、それはもっと理にかなっています。

昨年、私はReduxを使用してかなり複雑なゲームを作成しました。 また、Reduxを使用せず、代わりにすべてをUIストアとサーバーストア(入力を含む)に分離するカスタムビルドの最小限のフレームワークに参加しました。 あまり多くの詳細に立ち入ることなく、これまでの私の結論は次のとおりです。

単一の状態の方が常に優れていますが、やりすぎないでください。 すべてのkeyDown()をストアからビューに戻すことは、混乱を招き、不要です。 したがって、一時的な状態は、ローカルコンポーネントの状態(Reactなど)によって処理される必要があります。

観察可能なウェブが赤字と反応で炎症を起こしたため、ページ上の優れたアニメーションの量が減少したと思います。

@ mib32あなたは正しいかもしれません! うまくいけば、人々は最終的にReactで優れたアニメーションを作成することに慣れることでしょう。

このページは役に立ちましたか?
0 / 5 - 0 評価