web-dev-qa-db-fra.com

Gérer une minuterie dans React / Flux

Je travaille sur une application où je veux qu'un compte à rebours passe de, disons, 60 secondes à 0, puis change un peu de contenu, après quoi le minuteur redémarre à 60.

J'ai implémenté cela dans React et Flux mais depuis que je suis nouveau dans ce domaine, je rencontre toujours des problèmes.

Je veux maintenant ajouter un bouton marche/arrêt pour la minuterie. Je ne sais pas où mettre/gérer l'état de la minuterie.

J'ai un composant Timer.jsx qui ressemble à ceci:

var React = require('react');
var AppStore = require('../stores/app-store.js');
var AppActions = require('../actions/app-actions.js');

function getTimeLeft() {
  return {
    timeLeft: AppStore.getTimeLeft()
  }
}

var Timer = React.createClass({
  _tick: function() {
    this.setState({ timeLeft: this.state.timeLeft - 1 });
    if (this.state.timeLeft < 0) {
      AppActions.changePattern();
      clearInterval(this.interval);
    }
  },
  _onChange: function() {
    this.setState(getTimeLeft());
    this.interval = setInterval(this._tick, 1000);
  },
  getInitialState: function() {
    return getTimeLeft();
  },
  componentWillMount: function() {
    AppStore.addChangeListener(this._onChange);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  componentDidMount: function() {
    this.interval = setInterval(this._tick, 1000);
  },
  render: function() {
    return (
      <small>
        ({ this.state.timeLeft })
      </small>
    )
  }
});

module.exports = Timer;

Il récupère une durée de compte à rebours du magasin, où j'ai simplement:

var _timeLeft = 60;

Maintenant, quand je veux implémenter un bouton marche/arrêt, j'ai l'impression que je devrais également l'implémenter via Flux Actions, n'est-ce pas? Je pensais donc avoir quelque chose comme ça dans mon magasin:

dispatcherIndex: AppDispatcher.register(function(payload) {
  var action = payload.action;

  switch(action.actionType) {
    case AppConstants.START_TIMER:
      // do something
      break;
    case AppConstants.STOP_TIMER:
      // do something
      break;
    case AppConstants.CHANGE_PATTERN:
      _setPattern();
      break;
  }

  AppStore.emitChange();

  return true;
})

Cependant, puisque mon composant Timer gère actuellement setInterval, je ne sais pas comment faire fonctionner mes événements START/STOP_TIMER. Dois-je déplacer la substance setInterval du composant Timer vers le magasin et la transmettre d'une manière ou d'une autre à mon composant?

Le code complet peut être trouvé ici .

17
cabaret

J'ai fini par télécharger votre code et implémenter la fonction de démarrage/arrêt/réinitialisation que vous vouliez. Je pense que c'est probablement la meilleure façon d'expliquer les choses - pour montrer le code que vous pouvez exécuter et tester avec quelques commentaires.

En fait, je me suis retrouvé avec deux implémentations. Je les appellerai implémentation A et implémentation B.

J'ai pensé qu'il serait intéressant de montrer les deux implémentations. Espérons que cela ne cause pas trop de confusion.

Pour mémoire, l'implémentation A est la meilleure version.

Voici une brève description des deux implémentations:

Implémentation A

Cette version garde la trace de l'état au niveau du composant App. Le temporisateur est géré en passant props au composant Timer. Cependant, le composant de temporisation garde une trace de son propre état de temps restant.

Implémentation B

Cette version assure le suivi de l'état du temporisateur au niveau du composant Timer à l'aide d'un module TimerStore et TimerAction pour gérer l'état et les événements du composant.

Le gros inconvénient (et probablement fatal) de l'implémentation B est que vous ne pouvez avoir qu'un seul composant Timer. Cela est dû au fait que les modules TimerStore et TimerAction sont essentiellement des singletons.


Implémentation A

Cette version garde la trace de l'état au niveau du composant App. La plupart des commentaires ici se trouvent dans le code de cette version.

Le temporisateur est géré en passant props au temporisateur.

Liste des changements de code pour cette implémentation:

  • app-constants.js
  • app-actions.js
  • app-store.js
  • App.jsx
  • Timer.jsx

app-constants.js

Ici, je viens d'ajouter une constante pour réinitialiser la minuterie.

module.exports = {
  START_TIMER: 'START_TIMER',
  STOP_TIMER: 'STOP_TIMER',
  RESET_TIMER: 'RESET_TIMER',
  CHANGE_PATTERN: 'CHANGE_PATTERN'
};

app-actions.js

Je viens d'ajouter une méthode de répartition pour gérer l'action de réinitialisation du minuteur.

var AppConstants = require('../constants/app-constants.js');
var AppDispatcher = require('../dispatchers/app-dispatcher.js');

var AppActions = {
  changePattern: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.CHANGE_PATTERN
    })
  },
  resetTimer: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.RESET_TIMER
    })
  },
  startTimer: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.START_TIMER
    })
  },
  stopTimer: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.STOP_TIMER
    })
  }
};

module.exports = AppActions;

app-store.js

Voici où les choses changent un peu. J'ai ajouté des commentaires détaillés en ligne lorsque j'ai apporté des modifications.

var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');


// I added a TimerStatus model (probably could go in its own file)
// to manage whether the timer is "start/stop/reset".
//
// The reason for this is that reset state was tricky to handle since the Timer
// component no longer has access to the "AppStore". I'll explain the reasoning for
// that later.
//
// To solve that problem, I added a `reset` method to ensure the state
// didn't continuously loop "reset". This is probably not very "Flux".
//
// Maybe a more "Flux" alternative is to use a separate TimerStore and
// TimerAction? 
//
// You definitely don't want to put them in AppStore and AppAction
// to make your timer component more reusable.
//
var TimerStatus = function(status) {
  this.status = status;
};

TimerStatus.prototype.isStart = function() {
  return this.status === 'start';
};

TimerStatus.prototype.isStop = function() {
  return this.status === 'stop';
};

TimerStatus.prototype.isReset = function() {
  return this.status === 'reset';
};

TimerStatus.prototype.reset = function() {
  if (this.isReset()) {
    this.status = 'start';
  }
};


var CHANGE_EVENT = "change";

var shapes = ['C', 'A', 'G', 'E', 'D'];
var rootNotes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];

var boxShapes = require('../data/boxShapes.json');


// Added a variable to keep track of timer state. Note that this state is
// managed by the *App Component*.
var _timerStatus = new TimerStatus('start');


var _pattern = _setPattern();

function _setPattern() {
  var rootNote = _getRootNote();
  var shape = _getShape();
  var boxShape = _getBoxForShape(shape);

  _pattern = {
    rootNote: rootNote,
    shape: shape,
    boxShape: boxShape
  };

  return _pattern;
}

function _getRootNote() {
  return rootNotes[Math.floor(Math.random() * rootNotes.length)];
}

function _getShape() {
  return shapes[Math.floor(Math.random() * shapes.length)];
}

function _getBoxForShape(shape) {
  return boxShapes[shape];
}


// Simple function that creates a new instance of TimerStatus set to "reset"
function _resetTimer() {
  _timerStatus = new TimerStatus('reset');
}

// Simple function that creates a new instance of TimerStatus set to "stop"
function _stopTimer() {
  _timerStatus = new TimerStatus('stop');
}

// Simple function that creates a new instance of TimerStatus set to "start"
function _startTimer() {
  _timerStatus = new TimerStatus('start');
}


var AppStore = merge(EventEmitter.prototype, {
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },


  // Added this function to get timer status from App Store
  getTimerStatus: function() {
    return _timerStatus;
  },


  getPattern: function() {
    return _pattern;
  },

  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;

    switch(action.actionType) {
      case AppConstants.RESET_TIMER:
        // Handle reset action
        _resetTimer();
        break;
      case AppConstants.START_TIMER:
        // Handle start action
        _startTimer();
        break;
      case AppConstants.STOP_TIMER:
        // Handle stop action
        _stopTimer();
        break;
      case AppConstants.CHANGE_PATTERN:
        _setPattern();
        break;
    }

    AppStore.emitChange();

    return true;
  })
});

module.exports = AppStore;

App.jsx

Il y a de nombreuses modifications dans App.jsx, en particulier nous avons déplacé l'état vers le composant App du composant timer. Encore des commentaires détaillés dans le code.

var React = require('react');

var Headline = require('./components/Headline.jsx');
var Scale = require('./components/Scale.jsx');
var RootNote = require('./components/RootNote.jsx');
var Shape = require('./components/Shape.jsx');
var Timer = require('./components/Timer.jsx');


// Removed AppActions and AppStore from Timer component and moved
// to App component. This is done to to make the Timer component more
// reusable.
var AppActions = require('./actions/app-actions.js');
var AppStore = require('./stores/app-store.js');


// Use the AppStore to get the timerStatus state
function getAppState() {
  return {
    timerStatus: AppStore.getTimerStatus()
  }
}

var App = React.createClass({
  getInitialState: function() {
    return getAppState();
  },


  // Listen for change events in AppStore
  componentDidMount: function() {
    AppStore.addChangeListener(this.handleChange);
  },


  // Stop listening for change events in AppStore
  componentWillUnmount: function() {
    AppStore.removeChangeListener(this.handleChange);
  },


  // Timer component has status, defaultTimeout attributes.
  // Timer component has an onTimeout event (used for changing pattern)
  // Add three basic buttons for Start/Stop/Reset
  render: function() {
    return (
      <div>
        <header>
          <Headline />
          <Scale />
        </header>
        <section>
          <RootNote />
          <Shape />
          <Timer status={this.state.timerStatus} defaultTimeout="15" onTimeout={this.handleTimeout} />
          <button onClick={this.handleClickStart}>Start</button>
          <button onClick={this.handleClickStop}>Stop</button>
          <button onClick={this.handleClickReset}>Reset</button>
        </section>
      </div>
    );
  },


  // Handle change event from AppStore
  handleChange: function() {
    this.setState(getAppState());
  },


  // Handle timeout event from Timer component
  // This is the signal to change the pattern.
  handleTimeout: function() {
    AppActions.changePattern();
  },


  // Dispatch respective start/stop/reset actions
  handleClickStart: function() {
    AppActions.startTimer();
  },
  handleClickStop: function() {
    AppActions.stopTimer();
  },
  handleClickReset: function() {
    AppActions.resetTimer();
  }
});

module.exports = App;

Timer.jsx

Le Timer a également beaucoup de changements depuis que j'ai supprimé les dépendances AppStore et AppActions pour rendre le composant Timer plus réutilisable. Les commentaires détaillés sont dans le code.

var React = require('react');


// Add a default timeout if defaultTimeout attribute is not specified.
var DEFAULT_TIMEOUT = 60;

var Timer = React.createClass({

  // Normally, shouldn't use props to set state, however it is OK when we
  // are not trying to synchronize state/props. Here we just want to provide an option to specify
  // a default timeout.
  //
  // See http://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html)
  getInitialState: function() {
    this.defaultTimeout = this.props.defaultTimeout || DEFAULT_TIMEOUT;
    return {
      timeLeft: this.defaultTimeout
    };
  },


  // Changed this to `clearTimeout` instead of `clearInterval` since I used `setTimeout`
  // in my implementation
  componentWillUnmount: function() {
    clearTimeout(this.interval);
  },

  // If component updates (should occur when setState triggered on Timer component
  // and when App component is updated/re-rendered)
  //
  // When the App component updates we handle two cases:
  // - Timer start status when Timer is stopped
  // - Timer reset status. In this case, we execute the reset method of the TimerStatus
  //   object to set the internal status to "start". This is to avoid an infinite loop
  //   on the reset case in componentDidUpdate. Kind of a hack...
  componentDidUpdate: function() {
    if (this.props.status.isStart() && this.interval === undefined) {
      this._tick();
    } else if (this.props.status.isReset()) {
      this.props.status.reset();
      this.setState({timeLeft: this.defaultTimeout});
    }
  },

  // On mount start ticking
  componentDidMount: function() {
    this._tick();
  },


  // Tick event uses setTimeout. I find it easier to manage than setInterval.
  // We just keep calling setTimeout over and over unless the timer status is
  // "stop".
  //
  // Note that the Timer states is handled here without a store. You could probably
  // say this against the rules of "Flux". But for this component, it just seems unnecessary
  // to create separate TimerStore and TimerAction modules.
  _tick: function() {
    var self = this;
    this.interval = setTimeout(function() {
      if (self.props.status.isStop()) {
        self.interval = undefined;
        return;
      }
      self.setState({timeLeft: self.state.timeLeft - 1});
      if (self.state.timeLeft <= 0) {
        self.setState({timeLeft: self.defaultTimeout});
        self.handleTimeout();
      }
      self._tick();
    }, 1000);
  },

  // If timeout event handler passed to Timer component,
  // then trigger callback.
  handleTimeout: function() {
    if (this.props.onTimeout) {
      this.props.onTimeout();
    }
  }
  render: function() {
    return (
      <small className="timer">
        ({ this.state.timeLeft })
      </small>
    )
  },
});

module.exports = Timer;

Implémentation B

Liste des changements de code:

  • app-constants.js
  • timer-actions.js (nouveau)
  • timer-store.js (nouveau)
  • app-store.js
  • App.jsx
  • Timer.jsx

app-constants.js

Ceux-ci devraient probablement aller dans un fichier nommé timer-constants.js car ils traitent du composant Timer.

module.exports = {
  START_TIMER: 'START_TIMER',
  STOP_TIMER: 'STOP_TIMER',
  RESET_TIMER: 'RESET_TIMER',
  TIMEOUT: 'TIMEOUT',
  TICK: 'TICK'
};

timer-actions.js

Ce module est explicite. J'ai ajouté trois événements - timeout, tick et reset. Voir le code pour plus de détails.

var AppConstants = require('../constants/app-constants.js');
var AppDispatcher = require('../dispatchers/app-dispatcher.js');

module.exports = {

  // This event signals when the timer expires.
  // We can use this to change the pattern.
  timeout: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.TIMEOUT
    })
  },

  // This event decrements the time left
  tick: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.TICK
    })
  },

  // This event sets the timer state to "start"
  start: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.START_TIMER
    })
  },

  // This event sets the timer state to "stop"
  stop: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.STOP_TIMER
    })
  },

  // This event resets the time left and sets the state to "start"
  reset: function() {
    AppDispatcher.handleViewAction({
      actionType: AppConstants.RESET_TIMER
    })
  },
};

timer-store.js

J'ai séparé le minuteur du AppStore. C'est pour rendre le composant Timer un peu plus réutilisable.

Le magasin Timer conserve la trace de l'état suivant:

  • état de la minuterie - Peut être "démarrer" ou "arrêter"
  • temps restant - Temps restant sur la minuterie

Le magasin Timer gère les événements suivants:

  • L'événement de démarrage du minuteur définit le statut du minuteur pour qu'il démarre.
  • L'événement d'arrêt du minuteur définit le statut du minuteur sur arrêt.
  • L'événement tick diminue le temps restant de 1
  • L'événement de réinitialisation de la minuterie définit le temps restant à la valeur par défaut et définit l'état de la minuterie pour démarrer

Voici le code:

var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');

var CHANGE_EVENT = "change";
var TIMEOUT_SECONDS = 15;

var _timerStatus = 'start';
var _timeLeft = TIMEOUT_SECONDS;

function _resetTimer() {
  _timerStatus = 'start';
  _timeLeft = TIMEOUT_SECONDS;
}

function _stopTimer() {
  _timerStatus = 'stop';
}

function _startTimer() {
  _timerStatus = 'start';
}

function _decrementTimer() {
  _timeLeft -= 1;
}

var TimerStore = merge(EventEmitter.prototype, {
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  getTimeLeft: function() {
    return _timeLeft;
  },

  getStatus: function() {
    return _timerStatus;
  },

  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;

    switch(action.actionType) {
      case AppConstants.START_TIMER:
        _startTimer();
        break;
      case AppConstants.STOP_TIMER:
        _stopTimer();
        break;
      case AppConstants.RESET_TIMER:
        _resetTimer();
        break;
      case AppConstants.TIMEOUT:
        _resetTimer();
        break;
      case AppConstants.TICK:
        _decrementTimer();
        break;
    }

    TimerStore.emitChange();

    return true;
  })
});

module.exports = TimerStore;

app-store.js

Cela pourrait être nommé pattern-store.js, Bien que vous deviez apporter quelques modifications pour qu'il soit réutilisable. Plus précisément, j'écoute directement l'action/l'événement TIMEOUT du temporisateur pour déclencher un changement de modèle. Vous ne voudrez probablement pas cette dépendance si vous souhaitez réutiliser le changement de modèle. Par exemple, si vous souhaitez modifier le motif en cliquant sur un bouton ou quelque chose.

Mis à part cela, je viens de supprimer toutes les fonctionnalités liées à la minuterie du AppStore.

var AppDispatcher = require('../dispatchers/app-dispatcher.js');
var AppConstants = require('../constants/app-constants.js');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/Object.assign');

var CHANGE_EVENT = "change";

var shapes = ['C', 'A', 'G', 'E', 'D'];
var rootNotes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];

var boxShapes = require('../data/boxShapes.json');

var _pattern = _setPattern();

function _setPattern() {
  var rootNote = _getRootNote();
  var shape = _getShape();
  var boxShape = _getBoxForShape(shape);

  _pattern = {
    rootNote: rootNote,
    shape: shape,
    boxShape: boxShape
  };

  return _pattern;
}

function _getRootNote() {
  return rootNotes[Math.floor(Math.random() * rootNotes.length)];
}

function _getShape() {
  return shapes[Math.floor(Math.random() * shapes.length)];
}

function _getBoxForShape(shape) {
  return boxShapes[shape];
}

var AppStore = merge(EventEmitter.prototype, {
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  getPattern: function() {
    return _pattern;
  },

  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;

    switch(action.actionType) {
      case AppConstants.TIMEOUT:
        _setPattern();
        break;
    }

    AppStore.emitChange();

    return true;
  })
});

module.exports = AppStore;

App.jsx

Ici, je viens d'ajouter quelques boutons pour démarrer/arrêter/réinitialiser. Au clic, une TimerAction est envoyée. Donc, si vous avez cliqué sur le bouton "stop", nous appelons TimerAction.stop()

var React = require('react');

var Headline = require('./components/Headline.jsx');
var Scale = require('./components/Scale.jsx');
var RootNote = require('./components/RootNote.jsx');
var Shape = require('./components/Shape.jsx');
var Timer = require('./components/Timer.jsx');
var TimerActions = require('./actions/timer-actions.js');


var App = React.createClass({
  render: function() {
    return (
      <div>
        <header>
          <Headline />
          <Scale />
        </header>
        <section>
          <RootNote />
          <Shape />
          <Timer />
          <button onClick={this.handleClickStart}>Start</button>
          <button onClick={this.handleClickStop}>Stop</button>
          <button onClick={this.handleClickReset}>Reset</button>
        </section>
      </div>
    );
  },
  handleClickStart: function() {
    TimerActions.start();
  },
  handleClickStop: function() {
    TimerActions.stop();
  },
  handleClickReset: function() {
    TimerActions.reset();
  }
});

module.exports = App;

Timer.jsx

L'un des principaux changements est que nous utilisons un TimerAction et un TimerStore au lieu des AppAction et AppStore qui étaient utilisés à l'origine. La raison est d'essayer de rendre le composant Timer un peu plus réutilisable.

La minuterie a l'état suivant:

  • status Le statut de la minuterie peut être "start" ou "stop"
  • timeLeft Temps restant sur la minuterie

Notez que j'ai utilisé setTimeout au lieu de setInterval. Je trouve setTimeout plus facile à gérer.

La majeure partie de la logique se trouve dans la méthode _tick. Fondamentalement, nous continuons d'appeler setTimeout tant que l'état est "start".

Lorsque le temporisateur atteint zéro, nous signalons l'événement timeout. Le TimerStore et l'AppStore sont à l'écoute de cet événement.

  1. Le TimerStore réinitialisera simplement la minuterie. Identique à l'événement de réinitialisation.
  2. L'AppStore changera le modèle.

Si le temporisateur n'est pas arrivé à zéro, on soustrait une seconde en signalant l'événement "tick".

Enfin, nous devons gérer le cas où le chronomètre est arrêté puis redémarré plus tard. Cela peut être géré via le hook componentDidUpdate. Ce hook est appelé lorsque l'état du composant change ou que les composants parents sont rendus de nouveau.

Dans la méthode componentDidUpdate, nous nous assurons de démarrer le "ticking" uniquement si le statut est "start" et que l'identifiant de timeout n'est pas défini. Nous ne voulons pas que plusieurs setTimeouts soient exécutés.

var React = require('react');

var TimerActions = require('../actions/timer-actions.js');
var TimerStore = require('../stores/timer-store.js');

function getTimerState() {
  return {
    status: TimerStore.getStatus(),
    timeLeft: TimerStore.getTimeLeft()
  }
}

var Timer = React.createClass({
  _tick: function() {
    var self = this;
    this.interval = setTimeout(function() {
      if (self.state.status === 'stop') {
        self.interval = undefined;
        return;
      }

      if (self.state.timeLeft <= 0) {
        TimerActions.timeout();
      } else {
        TimerActions.tick();
      }
      self._tick();
    }, 1000);
  },
  getInitialState: function() {
    return getTimerState();
  },
  componentDidMount: function() {
    TimerStore.addChangeListener(this.handleChange);
    this._tick();
  },
  componentWillUnmount: function() {
    clearTimeout(this.interval);
    TimerStore.removeChangeListener(this.handleChange);
  },
  handleChange: function() {
    this.setState(getTimerState());
  },
  componentDidUpdate: function() {
    if (this.state.status === 'start' && this.interval === undefined) {
      this._tick();
    }
  },
  render: function() {
    return (
      <small className="timer">
        ({ this.state.timeLeft })
      </small>
    )
  }
});

module.exports = Timer;
20
Gohn67

Ne stocke pas l'état dans les composants

L'une des principales raisons d'utiliser Flux est de centraliser l'état de l'application. À cette fin, vous devez éviter d'utiliser la fonction setState d'un composant. De plus, dans la mesure où les composants enregistrent leur propre état, cela ne devrait être que pour les données d'état de nature très éphémère (par exemple, vous pouvez définir l'état localement sur un composant qui indique si une souris survole).

Utiliser des créateurs d'actions pour les opérations asynchrones

Dans Flux, les magasins sont censés être synchrones. (Notez que c'est un point quelque peu controversé parmi les implémentations de Flux, mais je suggère définitivement de rendre les magasins synchrones. Une fois que vous autorisez le fonctionnement asynchrone dans les magasins, cela rompt le flux de données unidirectionnel et altère le raisonnement de l'application.). Au lieu de cela, l'opération asynchrone devrait vivre dans votre Action Creator. Dans votre code, je ne vois aucune mention d'un créateur d'action, donc je soupçonne que cela pourrait être la source de votre confusion. Néanmoins, votre véritable Timer devrait vivre dans le créateur d'action. Si votre composant doit effectuer le temporisateur, il peut appeler une méthode sur le Créateur d'actions, le Créateur d'actions peut créer/gérer le temporisateur et le temporisateur peut envoyer des événements qui seront gérés par le store.

Mise à jour : Notez que lors du panel Flux React-Conf 2014, un développeur travaillant sur une grande application Flux a déclaré que pour cette application particulière, il autorisait la récupération asynchrone des données opérations dans les magasins (GETs mais pas PUTs ou POSTs).

Facebook's Flux Flow Chart

7
Gil Birman

Je supprimerais le minuteur du magasin et pour l'instant, je gérerais simplement les modèles. Votre composant de minuterie aurait besoin de quelques petites modifications:

var Timer = React.createClass({
  _tick: function() {
    if (this.state.timeLeft < 0) {
      AppActions.changePattern();
      clearInterval(this.interval);
    } else {
      this.setState({ timeLeft: this.state.timeLeft - 1 });
    }
  },
  _onChange: function() {
    // do what you want with the pattern here
    // or listen to the AppStore in another component
    // if you need this somewhere else
    var pattern = AppStore.getPattern();
  },
  getInitialState: function() {
    return { timeLeft: 60 };
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  componentDidMount: function() {
    this.interval = setInterval(this._tick, 1000);
    AppStore.addChangeListener(this._onChange);
  },
  render: function() {
    return (
      <small>
        ({ this.state.timeLeft })
      </small>
    )
  }
});
2
Shawn