web-dev-qa-db-fra.com

Pourquoi un réducteur Redux est-il appelé réducteur?

Tout en apprenant Redux, je suis tombé sur Reducers. La documentation indique:

Le réducteur est une fonction pure qui prend l'état précédent et une action, et retourne l'état suivant. (previousState, action) => newState. Cela s'appelle un réducteur car c'est le type de fonction que vous transmettez à Array.prototype.reduce (réducteur,? InitialValue).

MDN décrit la méthode reduce comme:

La méthode Reduce () applique une fonction à un accumulateur et à chaque valeur du tableau (de gauche à droite) pour la réduire à une seule valeur.

Je ne comprends toujours pas pourquoi la définition Redux d'un réducteur car cela n'a aucun sens. Deuxièmement, la description MDN ne semble pas non plus correcte. La méthode reduce n'est pas toujours utilisée pour réduire à une seule valeur. Il peut être utilisé à la place de map et filter et est en fait plus rapide lorsqu'il est utilisé à la place du chaînage.

La description MDN est-elle incorrecte?

Revenant à la définition Redux d'un réducteur, il déclare:

C'est ce qu'on appelle un réducteur car c'est le type de fonction que vous transmettez à Array.prototype.reduce (réducteur,? InitialValue)

J'ai l'impression qu'un réducteur dans Redux est responsable de la modification de l'état. Un exemple de réducteur:

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

... Je ne vois pas comment c'est une fonction qui serait passée à reduce. Comment ces données sont-elles réduites à une seule valeur? S'il s'agit d'une fonction que vous passeriez à reduce alors state serait le rappel et action serait la valeur initiale.

Merci pour toute explication claire. C'est difficile à conceptualiser.

36
BugHunterUK

Le terme "réduire" est en fait un terme fonctionnel utilisé dans la programmation fonctionnelle. Dans un langage comme Haskell, F # ou même JavaScript, nous définissons une transformation qui prend une collection (de n'importe quelle taille) en entrée et renvoie une seule valeur en sortie.

Donc (pour ne pas être pédant, mais je trouve que cela m'aide) pensez-y visuellement. Nous avons une collection:

[][][][][][][][][][]

... que nous voulons réduire en une seule valeur.

N

En programmant fonctionnellement, nous le ferions avec une seule fonction que nous pourrions appeler récursivement sur chaque élément de la collection. Mais si vous faites cela, vous devez garder une trace de la valeur intermédiaire quelque part, non? Les implémentations non pures peuvent garder une sorte "d'accumulateur" ou de variable en dehors de la fonction pour garder une trace de l'état, comme ceci:

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

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

return accumulator;

Avec les fonctions pures, cependant, nous ne pouvons pas le faire - car par définition, les fonctions pures ne peuvent pas avoir d'effets en dehors de leur portée de fonction. Au lieu de cela ou en nous appuyant sur une variable externe qui encapsule notre "état" entre les appels, nous transmettons simplement l'état dans la méthode.

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

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

Dans ce cas, nous appelons la fonction un "réducteur" en raison de sa signature de méthode. Nous avons each (ou current - tout nom va bien), représentant un objet dans la collection; et state (ou previous), qui est passé à chaque itération de la fonction, représentant les résultats de la transformation que nous avons déjà effectuée sur les éléments précédents de la collection.

Notez que la documentation MDN que vous avez référencée est correcte; la fonction reduce() renvoie toujours une seule valeur. En fait, la méthode reduce dans n'importe quelle langue est un fonction d'ordre supérieur qui prend un "réducteur" (une fonction avec la signature de méthode définie ci-dessus) et renvoie une seule valeur. Maintenant, oui, vous pouvez faire d'autres choses avec, si votre fonction que vous appelez a des effets secondaires, mais vous ne devriez pas. (Essentiellement, n'utilisez pas .reduce() comme foreach.) Même si la méthode que vous appelez avec reduce a des effets secondaires, la valeur de retour de réduire lui-même sera une valeur unique, pas une collection.

Ce qui est cool, c'est que ce modèle ne doit pas seulement s'appliquer aux tableaux ou aux collections concrètes, comme vous l'avez vu dans React; ce modèle peut également être appliqué aux flux, car ce sont des fonctions pures.

J'espère que cela t'aides. Pour ce que ça vaut, la définition sur le site Redux pourrait être améliorée (car le concept de réducteur n'est pas uniquement dû à la méthode prototype Array de Javascript). Vous devez soumettre un PR!

Edit: Il y a un article Wikipedia sur le sujet. Notez que réduire a des noms différents et, dans les langages fonctionnels, il est communément appelé Fold. https://en.wikipedia.org/wiki/Fold_ (fonction d'ordre supérieur) #Folds_as_structural_transformations

28
jedd.ahyoung

La raison pour laquelle un réducteur redux est appelé reducer est parce que vous pouvez "réduire" un collection of actions Et un initial state (Du magasin) sur lequel effectuer ces actions pour obtenir le final state résultant.

Comment? Pour répondre à cela, permettez-moi de définir à nouveau un réducteur:

La méthode Reduce () applique une function (reducer) à une accumulator et à chaque valeur du tableau (de gauche à droite) pour la réduire à une seule valeur .

Et que fait un réducteur redux?

Le réducteur est un pur function qui prend l'état actuel et une action, et retourne l'état suivant. Notez que l'état est accumulated car chaque action sur la collection est appliquée pour changer cet état.

Donc, étant donné un collection of actions, Le réducteur est appliqué sur chaque valeur de la collection (de gauche à droite). La première fois, il renvoie le initial value. Maintenant, le réducteur est appliqué à nouveau sur cet état initial et la première action pour retourner l'état suivant. Et l'élément de collection suivant (action) est appliqué à chaque fois sur le current state Pour obtenir le next state Jusqu'à ce qu'il atteigne la fin du tableau. Et puis, vous obtenez the final state. À quel point cela est cool!

11
code4kix

Désolé, mais je ne suis pas d'accord avec les réponses précédentes. Je ne prendrais pas en charge la dénomination reducer. Je suis passionné par FP et l'immuabilité. Ne me blâmez pas, lisez la deuxième partie, mais je veux d'abord dire pourquoi je ne suis pas d'accord.

Il est vrai que les réducteurs sont la séquence de transformations, mais la séquence elle-même - pourrait faire partie d'une autre séquence. Imaginez-le, comme les maillons - une partie de la chaîne. Mais la chaîne elle-même pourrait faire partie d'une chaîne plus longue. Chaque lien est la "transition" de l'état global. Alors, quelle est la théorie derrière cela?

N'est-ce pas en fait la "machine à états finis"? - proche, mais pas. C'est en fait le système de transition .

Un système de transition étiqueté est un tuple (S, Λ, →) où S est un ensemble d'états, Λ est un ensemble d'étiquettes et → est un ensemble de transitions étiquetées

Ainsi, S - sont définis de nos états

Λ - est notre soi-disant "actions" (mais étiquettes en théorie)

... et

réducteurs "transitions étiquetées"! Je le nommerais ainsi, si je suis créateur de cette bibliothèque.

Comprendre cette théorie m'a aidé à implémenter ma bibliothèque, où je peux avoir un système de transition de bas niveau dans le cadre de système de transition de haut niveau (comme la chaîne - pourrait toujours faire partie d'une chaîne plus longue) - et ayant toujours un état Redux global unique.

5
Black Akula

C'est ce qu'on appelle un réducteur car c'est le type de fonction que vous transmettez à Array.prototype.reduce (réducteur,? InitialValue)

Array.reduce

C'est très similaire à ce que vous passeriez à Array.reduce comme rappel (réducteur). La partie importante étant:

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.

Où state est la "previousValue" et action est la "currentValue".

2
DTing

J'ai l'impression qu'un réducteur dans Redux est responsable de la modification de l'état. Un exemple de réducteur:

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

... Je ne vois pas comment c'est une fonction qui serait passée pour réduire. Comment ces données sont-elles réduites à une seule valeur? S'il s'agit d'une fonction que vous passeriez pour réduire, alors l'état serait le rappel et l'action serait la valeur initiale.

// 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

Ces réponses sont bonnes mais je pense que c'est beaucoup plus simple que vous ne le pensez. J'avoue que j'ai eu une confusion similaire au début. La méthode de réduction sur les tableaux Javascript prend un accumulateur et une fonction de rappel.

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

La raison pour laquelle on l'appelle un réducteur dans redux est parce qu'il a à peu près une structure similaire.

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
}); 

Donc, chaque fois que nous exécutons un réducteur, nous prenons quelque chose, le modifions et retournons une copie de la même chose. À chaque itération, nous indiquons la même chose. Ok, oui, nous devons faire une copie en redux afin de ne pas modifier directement l'état, mais symboliquement chaque fois que nous l'exécutons, c'est un peu comme la façon dont la réduction commence avec un état initial dans l'exemple ci-dessus de 1. Ensuite, nous ajoutons 2 à notre état initial et revenons à 3. Maintenant, nous exécutons à nouveau notre "réducteur" avec un état initial de 3 et ajoutons 3 et nous nous retrouvons avec 6.

1
Will Adamowicz