web-dev-qa-db-fra.com

S'abonner au changement de propriété unique en magasin dans Redux

Sous Redux, je peux facilement m'abonner pour stocker les modifications avec

store.subscribe(() => my handler goes here)

Mais que se passe-t-il si mon magasin est rempli d'objets différents et que je souhaite souscrire à des modifications apportées uniquement à un objet spécifique du magasin?

47
karol.barkowski

Il n’ya aucun moyen de s’abonner à une partie du magasin en utilisant subscribe directement, mais comme le dit le créateur de Redux lui-même - ne pas utiliser subscribe directement! Pour que le flux de données d’une application Redux fonctionne réellement, vous aurez besoin d’un composant qui enveloppe l’ensemble de votre application. Ce composant sera abonné à votre magasin. Le reste de vos composants seront des enfants de ce composant wrapper et n'obtiendront que les parties de l'état dont ils ont besoin.

Si vous utilisez Redux avec React, alors il y a une bonne nouvelle - le paquet officiel react-redux s'occupe de cela pour vous! Il fournit ce composant wrapper, appelé <Provider />. Vous aurez alors au moins un "composant intelligent" qui écoute les changements d'état transmis par le Provider du magasin. Vous pouvez spécifier les parties de l'état qu'il doit écouter, et ces morceaux de l'état seront transmis comme accessoires à ce composant (et bien sûr, il peut les transmettre à ses propres enfants). Vous pouvez spécifier cela en utilisant la fonction connect () sur votre composant "intelligent" et en utilisant la fonction mapStateToProps comme premier paramètre.

Enveloppez le composant racine avec le composant Provider qui souscrit au stockage des modifications

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Désormais, tout enfant de <App /> Entouré de connect() sera un composant "intelligent". Vous pouvez passer en mapStateToProps pour choisir certaines parties de l'état et lui donner celles-ci comme accessoires.

const mapStateToProps = (state) => {
    return {
        somethingFromStore: state.somethingFromStore
    }
}

class ChildOfApp extends Component {
    render() {
        return <div>{this.props.somethingFromStore}</div>
    }
}

//wrap App in connect and pass in mapStateToProps
export default connect(mapStateToProps)(ChildOfApp)

Il est évident que <App /> Peut avoir de nombreux enfants et vous pouvez choisir les parties de l'état que le mapStateToProps doit écouter pour chacun de ses enfants. Je suggérerais de lire la documentation sur tilisation avec React pour mieux comprendre ce flux.

89
Andy Noelker

Redux n'offre qu'un moyen générique de savoir quand le magasin a été mis à jour: la méthode subscribe. Les rappels à subscribe ne reçoivent aucune information sur ce qui pourrait avoir changé, car l'API subscribe est délibérément de bas niveau et exécute simplement chaque rappel sans argument. Tout ce que vous savez, c'est que le magasin a été mis à jour d'une manière ou d'une autre.

A cause de cela, quelqu'un doit écrire une logique spécifique pour comparer l'ancien état au nouvel état et voir si quelque chose a changé. Vous pouvez gérer cela en utilisant React-Redux, en spécifiant une fonction mapStateToProps pour votre composant, en implémentant componentWillReceiveProps dans votre composant et en vérifiant si des accessoires spécifiques du magasin ont changé.

Il existe également quelques bibliothèques d’addon qui essaient de gérer ce cas: https://github.com/ashaffer/redux-subscribe et https://github.com/jprichardson/redux -watch . Les deux vous permettent essentiellement de spécifier une partie spécifique de l'état à examiner, en utilisant différentes approches.

11
markerikson

En plus des propos d'Andy Noelker, mapStateToProps ne transmet pas seulement une partie de l'état correctement à votre arborescence des composants, il souscrit également aux modifications apportées directement à ces parties de l'état souscrites.

Il est vrai que chaque fonction mapStateToProp que vous associez au magasin est appelée chaque fois qu’une partie de l’état est modifiée, mais le résultat de l’appel devient superficiel par rapport à l’appel précédent - si les clés de niveau supérieur auxquelles vous vous êtes abonné n'a pas changé (la référence reste la même). Ensuite, mapStateToProps n’appellerait pas le rendu de nouveau. Donc, si vous voulez que le concept fonctionne, vous devez garder mapStateToProps simple, aucune fusion, changement de type ou autre, ils doivent simplement transmettre des parties de l'état.

Si vous souhaitez réduire les données de l'état lors de la souscription, par exemple si vous aviez une liste de données dans l'état et que vous souhaitez le convertir en objet avec les ID en tant que clés, ou si vous souhaitez joindre plusieurs états dans des structures de données, vous devez combiner mapStateToProps. avec la bibliothèque createSelector de la bibliothèque reselect, en effectuant toutes ces modifications dans le sélecteur. Les sélecteurs sont des fonctions pures qui réduisent et mettent en cache les fragments d’état passés en entrée. Si l’entrée n’a pas changé - ils renvoient exactement la même référence que lors du dernier appel - sans effectuer la réduction.

2
IvanX

créé sur hack pour aider à comprendre les abonnés peut être différencié en fonction des données de magasin, avec capacité de magasin multiple.

//import { createStore } from 'redux';
let createStore = require('redux').createStore;
let combineReducers = require('redux').combineReducers;
/**
 * This is a reducer, a pure function with (state, action) => state signature.
 * It describes how an action transforms the state into the next state.
 *
 * The shape of the state is up to you: it can be a primitive, an array, an object,
 * or even an Immutable.js data structure. The only important part is that you should
 * not mutate the state object, but return a new object if the state changes.
 *
 * In this example, we use a `switch` statement and strings, but you can use a helper that
 * follows a different convention (such as function maps) if it makes sense for your
 * project.
 */
function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1
        case 'DECREMENT':
            return state - 1
        default:
            return state
    }
}

function messanger(state = 'Mr, khazi', action) {
    switch(action.type) {
        case 'WELCOME':
            return 'Hello, Mr Khazi';
        case 'BYE':
            return 'Bye, Mr Khazi';
        case 'INCREMENT':
            return 'Incremented khazi';
        default:
            return state;
    }
};

function latestAction(state = null, action) {
    switch(action.type) {
        case 'WELCOME':
            return '$messanger';
        case 'BYE':
            return '$messanger';
        case 'INCREMENT':
            return '$messanger, $counter';
        case 'DECREMENT':
            return '$counter';
        default:
            return state;
    }
};

let reducers = {
    counts: counter,
    message: messanger,
    action: latestAction
};

let store = createStore(
    combineReducers(reducers, latestAction)
);

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
//let store = createStore(counter)

// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => {
    if(store.getState().action.indexOf('messanger') !== -1) {
        console.log('subscribed for counter actions', store.getState());
    }
});

store.subscribe(() => {
    if (store.getState().action.indexOf('counter') !== -1) {
        console.log('subscribed for messanger actions', store.getState());
    }
});

// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
console.log('----------------Action with both subscriber-------------');
store.dispatch({ type: 'INCREMENT' });
console.log('---------------Action with counter subscriber-----------');
store.dispatch({ type: 'DECREMENT' });
console.log('---------------Action with messenger subscriber---------');
store.dispatch({ type: 'WELCOME' });

/*
    every reducer will execute on each action.

*/
0
Khazi Afzal