web-dev-qa-db-ja.com

Reduxレデューサーがレデューサーと呼ばれるのはなぜですか?

Reduxを学びながら、Reducersに出会いました。ドキュメントの状態:

レデューサーは、前の状態とアクションを取り、次の状態を返す純粋な関数です。 (previousState、アクション)=> newState。それはArray.prototype.reduce(reducer、?initialValue)に渡す関数の型であるため、リデューサーと呼ばれます。

MDNはreduceメソッドを次のように説明します。

Reduce()メソッドは、アキュムレーターと配列の各値(左から右へ)に対して関数を適用して、単一の値に減らします。

Reduxのレデューサーの定義が意味をなさないので、私はまだ混乱しています。第二に、MDNの記述も正しくないようです。 reduceメソッドは、常に単一の値に減らすために使用されるとは限りません。 mapfilterの代わりに使用でき、チェーンの代わりに使用すると実際に高速になります。

MDNの説明は間違っていますか?

レデューサーのRedux定義に戻り、次のように述べています。

Array.prototype.reduce(reducer、?initialValue)に渡す関数のタイプであるため、リデューサーと呼ばれます。

Reduxのレデューサーが状態を変更する責任があるという印象を受けています。減速機の例:

const count = function(state, action) {
    if(action.type == 'INCREMENT') {
        return state + 1;
    } else if(action.type == 'DECREMENT') {
        return state - 1;
    } else {
        return state;
    }
}

...これがreduceに渡される関数であることがわかりません。そのデータはどのようにして単一の値に削減されますか?これがreduceに渡す関数の場合、stateがコールバックになり、actionが初期値になります。

明確な説明をありがとう。概念化するのは難しいです。

36
BugHunterUK

「削減」という用語は、実際には関数型プログラミングで使用される関数型の用語です。 Haskell、F#、またはJavaScriptなどの言語では、(任意のサイズの)コレクションを入力として受け取り、出力として単一の値を返す変換を定義します。

だから(つまらないものではないが、私はこれが私を助けると思う)視覚的にそれを考える。コレクションがあります:

_[][][][][][][][][][]
_

...これを単一の値にまとめたいと思います。

_N
_

機能的にプログラミングする場合、コレクションの各要素に対して再帰的に呼び出すことができる単一の関数でこれを行います。しかし、そうする場合は、中間値をどこかで追跡する必要がありますよね?非純粋な実装では、次のように状態を追跡するために、関数の外側に何らかの「アキュムレーター」または変数を保持する場合があります。

_var accumulator = 0;
var myArray = [1,2,3,4,5];

myArray.reduce(function (each) {
    accumulator += 0;
});

return accumulator;
_

ただし、純粋な関数では、これを行うことはできません。定義により、純粋な関数は関数のスコープ外で効果を発揮できないためです。代わりに、呼び出し間で「状態」をカプセル化する外部変数に依存して、メソッドで状態を渡すだけです。

_var myArray = [1,2,3,4,5];

return myArray.reduce(function (accumulator, each) {
    return accumulator + each;
}, 0);
_

この場合、メソッドのシグネチャのため、関数を「リデューサー」と呼びます。コレクション内のオブジェクトを表すeach(またはcurrent-任意の名前で構いません)があります。およびstate(またはprevious)は、関数の各反復に渡され、コレクション内の前の要素に対して既に行った変換の結果を表します。

参照したMDNドキュメントは正しいことに注意してください。 reduce()関数は常に単一の値を返します。実際、どの言語のreduceメソッドも 高階関数 であり、「リデューサー」(上記で定義されたメソッドシグネチャを持つ関数)を取り、単一の値を返します。さて、はい、あなたが呼び出す関数が副作用を持っている場合、あなたはcanそれで他のものを行うことができますが、すべきではありません。 (本質的に、foreachとして.reduce()を使用しないでください。)reduceで呼び出すメソッドに副作用がある場合でも、return valuereduce自体はコレクションではなく単一の値になります。

クールなのは、Reactで見たように、このパターンは配列や具体的なコレクションに適用するだけではないということです。このパターンは純粋な関数であるため、ストリームにも適用できます。

お役に立てれば。価値があるものとしては、Reduxサイトの定義を改善することができます(レデューサーの概念はJavascriptのArrayプロトタイプメソッドだけではないため)。 PRを送信してください!

編集:主題に関するウィキペディアの記事があります。 reduceには異なる名前があり、関数型言語では一般にFoldとして知られていることに注意してください。 https://en.wikipedia.org/wiki/Fold_(higher-order_function)#Folds_as_structural_transformations

28
jedd.ahyoung

redux reducerreducerと呼ばれる理由は、(ストアの)_collection of actions_と_initial state_を「削減」できるためです。これらのアクションを実行して、結果の_final state_を取得します。

どうやって?それに答えるために、レデューサーを再度定義します。

reduce() メソッドは、accumulatorおよび配列の各値(左から右へ)に対してfunction (reducer)を適用して、単一の値に減らします。 。

そして、還元剤は何をしますか?

レデューサーは、現在の状態とアクションを取り、次の状態を返す純粋なfunctionです。コレクションの各アクションはこの状態を変更するために適用されるため、状態はaccumulatedであることに注意してください。

したがって、_collection of actions_を指定すると、レデューサーはコレクションの各値に(左から右に)適用されます。初めて、_initial value_を返します。これで、この初期状態と次の状態を返す最初のアクションにリデューサーが再び適用されます。そして、次のコレクション項目(アクション)は毎回_current state_に適用され、配列の最後に達するまで_next state_を取得します。そして、_the final state_を取得します。なんてかっこいい!

11
code4kix

申し訳ありませんが、以前の回答には同意しません。 I しないreducerの命名をサポートします。私はFPと不変性に情熱を注いでいます。2番目の部分を読んで、私を責めないでください。

レデューサーは変換のシーケンスですが、シーケンス自体は別のシーケンスの一部である可能性があります。リンクのように想像してください-チェーンの一部。しかし、チェーン自体はより長いチェーンの一部である可能性があります。各リンクは、グローバル状態の「遷移」です。それよりも、その背後にある理論は何ですか?

それは実際には「有限状態マシン」ではありませんか? -近いが、そうではない。実際には 移行システム です。

ラベル付き遷移システムはタプル(S、Λ、→)です。Sは状態のセット、Λはラベルのセット、→はラベル付き遷移のセットです。

したがって、S-状態のセットです

Λ-いわゆる「アクション」です(ただし、理論的にはlabels

...そして

減速機 「ラベル付き遷移」!私がこのライブラリの作成者である場合、そのように名前を付けます。

この理論を理解することで、ライブラリを実装することができました。ここでは、低レベル遷移システムの一部として低レベル遷移システムを使用できますより長いチェーンの)-そして単一のグローバルなRedux状態をまだ持っています。

5
Black Akula

Array.prototype.reduce(reducer、?initialValue)に渡す関数のタイプであるため、リデューサーと呼ばれます。

Array.reduce

これは、コールバック(レデューサー)としてArray.reduceに渡すものと非常に似ています。重要な部分は:

callback
  Function to execute on each value in the array, taking four arguments:
    previousValue
      The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
    currentValue
      The current element being processed in the array.

ここで、状態は「previousValue」であり、アクションは「currentValue」です。

2
DTing

Reduxのレデューサーが状態を変更する責任があるという印象を受けています。減速機の例:

const count = function(state, action) {
    if (action.type == 'INCREMENT') {
        return state + 1;
    } else if (action.type == 'DECREMENT') {
        return state - 1;
    } else {
        return state;
    }
}

...これがreduceに渡される関数であることがわかりません。そのデータはどのようにして単一の値に削減されますか?これがreduceに渡す関数の場合、状態はコールバックになり、アクションは初期値になります。

// count function from your question
const count = function (state, action) {
    if (action.type == 'INCREMENT') {
        return state + 1;
    } else if (action.type == 'DECREMENT') {
        return state - 1;
    } else {
        return state;
    }
}

// an array of actions
const actions =
  [ { type: 'INCREMENT' }
  , { type: 'INCREMENT' }
  , { type: 'INCREMENT' }
  , { type: 'INCREMENT' }
  , { type: 'DECREMENT' }
  ]

// initial state
const init = 0
  
console.log(actions.reduce(count, init))
// 3 (final state)
// (INCREMENT 4 times, DECREMENT 1 time)
1
user633183

これらの答えは良いのですが、あなたが思っているよりもずっと簡単だと思います。最初は同様の混乱があったことを認めます。 Javascript配列のreduceメソッドは、アキュムレーターとコールバック関数を取ります。

const arr = [1, 2, 3]
const sum = arr.reduce((accumulator, element) => {
  accumulator += element;
  return accumulator;
}); // sum equals 6 now

Reduxでリデューサーと呼ばれる理由は、おおまかに似た構造を持っているためです。

const sum = arr.reduce((accumulator, element) => {  // accumulator is the initial state
  accumulator += element; // we do something to modify the initial state
  return accumulator;  // we return that and it becomes the new state
}); 

そのため、レデューサーを実行するたびに、何かを取り込んで変更し、同じもののコピーを返します。各反復で、同じものを指し示しています。わかりました、はい、reduxでコピーを作成して状態を直接変更しないようにする必要がありますが、シンボルを実行するたびに、上記の例の初期状態でreduceが開始するようなものです。 2を初期状態に戻し、3に戻ります。今、初期状態3で「リデューサー」を再度実行し、3を追加して、6になります。

1
Will Adamowicz