Ce que cet article vous apporte est une introduction détaillée à la programmation réactive frontale et à ses lacunes (avec le code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. aide.
Beaucoup de choses dans le monde réel fonctionnent de manière réactive. Par exemple, nous recevrons des questions des autres, puis répondrons et donnerons les réponses correspondantes. Au cours du processus de développement, j'ai également appliqué beaucoup de design réactif et accumulé une certaine expérience, dans l'espoir d'inspirer les autres.
La principale différence entre la programmation réactive (Programmation réactive) et les idées de programmation ordinaires est que la programmation réactive fonctionne de manière push, tandis que la programmation non réactive fonctionne de manière pull. Par exemple, les événements sont une programmation réactive très courante. Nous faisons habituellement cela :
button.on('click', () => { // ...})
De manière non réactive, cela deviendra comme ceci :
while (true) { if (button.clicked) { // ... } }
Évidemment, que ce soit. en termes d'élégance du code ou d'efficacité d'exécution, les méthodes non réactives sont inférieures aux conceptions réactives.
Event Emitter
Event Emitter est une implémentation d'événement que la plupart des gens connaissent. Elle est très simple et pratique. Nous pouvons utiliser Event Emitter pour implémenter une conception de réactivité simple. , comme la recherche asynchrone suivante :
class Input extends Component { state = { value: '' } onChange = e => { this.props.events.emit('onChange', e.target.value) } afterChange = value => { this.setState({ value }) } componentDidMount() { this.props.events.on('onChange', this.afterChange) } componentWillUnmount() { this.props.events.off('onChange', this.afterChange) } render() { const { value } = this.state return ( <input value={value} onChange={this.onChange} /> ) } } class Search extends Component { doSearch = (value) => { ajax(/* ... */).then(list => this.setState({ list })) } componentDidMount() { this.props.events.on('onChange', this.doSearch) } componentWillUnmount() { this.props.events.off('onChange', this.doSearch) } render() { const { list } = this.state return ( <ul> {list.map(item => <li key={item.id}>{item.value}</li>)} </ul> ) } }
Ici, nous constaterons que l'implémentation d'Event Emitter présente de nombreuses lacunes et que nous devons libérer manuellement les ressources dans composantWillUnmount. Sa capacité d'expression est insuffisante, par exemple, lorsque nous devons agréger plusieurs sources de données lors d'une recherche :
class Search extends Component { foo = '' bar = '' doSearch = () => { ajax({ foo, bar }).then(list => this.setState({ list })) } fooChange = value => { this.foo = value this.doSearch() } barChange = value => { this.bar = value this.doSearch() } componentDidMount() { this.props.events.on('fooChange', this.fooChange) this.props.events.on('barChange', this.barChange) } componentWillUnmount() { this.props.events.off('fooChange', this.fooChange) this.props.events.off('barChange', this.barChange) } render() { // ... } }
Évidemment, l'efficacité du développement est très faible.
Redux
Redux utilise un flux d'événements pour implémenter la réactivité dans Redux, puisque le réducteur doit être une fonction pure, la seule façon d'implémenter la réactivité est l'abonnement. ou en middleware.
Si vous vous abonnez au magasin, puisque Redux ne peut pas savoir avec précision quelles données ont changé, il ne peut utiliser que la vérification sale. Par exemple :
function createWatcher(mapState, callback) { let previousValue = null return (store) => { store.subscribe(() => { const value = mapState(store.getState()) if (value !== previousValue) { callback(value) } previousValue = value }) } }const watcher = createWatcher(state => { // ...}, () => { // ...})
watcher(store)
Cette méthode présente deux inconvénients. Le premier est qu'il y aura des problèmes d'efficacité lorsque les données sont complexes et que la quantité de données est élevée. relativement grande ; Deuxièmement, si la fonction mapState dépend du contexte, elle sera difficile à gérer. Dans React-redux, le deuxième paramètre de mapStateToProps dans la fonction connect est les accessoires qui peuvent être transmis via le composant supérieur pour obtenir le contexte requis, mais de cette façon, l'écouteur devient un composant React et sera monté en tant que composant. monté. Et le déchargement est créé et détruit. Si on veut que cette réactivité soit indépendante des composants, il y aura un problème.
Une autre façon consiste à surveiller les modifications des données dans le middleware. Grâce à la conception de Redux, nous pouvons obtenir les modifications de données correspondantes en écoutant des événements spécifiques (Action).
const search = () => (dispatch, getState) => { // ...}const middleware = ({ dispatch }) => next => action => { switch action.type { case 'FOO_CHANGE': case 'BAR_CHANGE': { const nextState = next(action) // 在本次dispatch完成以后再去进行新的dispatch setTimeout(() => dispatch(search()), 0) return nextState } default: return next(action) } }
Cette méthode peut résoudre la plupart des problèmes, mais dans Redux, le middleware et le réducteur s'abonnent implicitement à tous les événements (Action), ce qui est évidemment déraisonnable, bien que dans Redux c'est tout à fait acceptable à condition qu'il n'y ait pas de performances problèmes.
Réactivité orientée objet
ECMASCRIPT 5.1 introduit les getters et les setters, et nous pouvons implémenter une réactivité via les getters et les setters.
class Model { _foo = '' get foo() { return this._foo } set foo(value) { this._foo = value this.search() } search() { // ... } }// 当然如果没有getter和setter的话也可以通过这种方式实现class Model { foo = '' getFoo() { return this.foo } setFoo(value) { this.foo = value this.search() } search() { // ... } }
Mobx et Vue utilisent cette méthode pour implémenter la réactivité. Bien entendu, nous pouvons également utiliser Proxy si la compatibilité n’est pas prise en compte.
Lorsque nous devons répondre à plusieurs valeurs puis obtenir une nouvelle valeur, nous pouvons le faire dans Mobx :
class Model { @observable hour = '00' @observable minute = '00' @computed get time() { return `${this.hour}:${this.minute}` } }
Mobx collectera les valeurs dont dépend le temps à runtime, et recalculer la valeur temporelle lorsque ces valeurs changent (déclenchant le setter) est évidemment beaucoup plus pratique et efficace que la méthode EventEmitter, et est plus intuitif que le middleware Redux.
Mais il y a aussi un inconvénient ici. L'attribut calculé basé sur le getter ne peut décrire que la situation de y = f(x). Cependant, dans de nombreux cas, en réalité, f est une fonction asynchrone, donc ce sera le cas. devenu y = wait f( x), getter ne peut pas décrire cette situation.
Pour cette situation, nous pouvons utiliser l'exécution automatique fournie par Mobx :
class Model { @observable keyword = '' @observable searchResult = [] constructor() { autorun(() => { // ajax ... }) } }
Le processus de collecte des dépendances d'exécution étant complètement implicite, nous rencontrons souvent ici un problème qui consiste à collecter des dépendances inattendues :
class Model { @observable loading = false @observable keyword = '' @observable searchResult = [] constructor() { autorun(() => { if (this.loading) { return } // ajax ... }) } }
Évidemment, le chargement ici ne doit pas être collecté par l'exécution automatique recherchée. Afin de résoudre ce problème, du code supplémentaire sera ajouté, et le code supplémentaire peut facilement conduire à des erreurs. Alternativement, nous pouvons également spécifier manuellement les champs obligatoires, mais cette méthode nécessite quelques opérations supplémentaires :
class Model { @observable loading = false @observable keyword = '' @observable searchResult = [] disposers = [] fetch = () => { // ... } dispose() { this.disposers.forEach(disposer => disposer()) } constructor() { this.disposers.push( observe(this, 'loading', this.fetch), observe(this, 'keyword', this.fetch) ) } }class FooComponent extends Component { this.mode = new Model() componentWillUnmount() { this.state.model.dispose() } // ...}
Et lorsque nous avons besoin de décrire la chronologie, Mobx est quelque peu incapable de le faire, par exemple vous. il faut retarder la recherche de 5 secondes.
Recommandations associées :
Utilisation un framework de développement front-end réactif très simple_html/css_WEB-ITnose
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!