2016-10-30 4 views
1

Ich baue eine Wetter App mit React & Redux. Ich habe beschlossen, in unbekannte Gewässer als ein Noob zu Reagieren & Redux. Ich teile die Dinge in Präsentationskomponenten und ihre jeweiligen Container, die mit den Daten umgehen. Ich habe jedoch einige Probleme damit meinen Kopf einzuwickeln. Es kommt vielleicht darauf an, wie ich es versuche. Ich bin nur sehr unsicher.React + Redux: Trennung der Präsentation von den Daten

Im Moment habe ich SearchBar, CurrentWeather, & Forecast Komponenten und ein AppContainer, dass ich diese Komponenten in integrieren bin versucht. Ich habe die SearchBar Komponente in die AppContainer bisher integriert und es funktioniert ohne Probleme. Hier bin ich verwirrt. So habe ich dem Container die erforderlichen Aktionen und Komponenten bereitgestellt und der Container wurde connected. Wenn der Benutzer eine Suche durchführt, wird der API-Aufruf durchgeführt und der Status wird über die Reduzierungen aktualisiert.

Die Daten sollten über mapStateToProps jetzt korrekt sein?

Wie kann ich die Daten verwenden, nachdem der Benutzer die Aktion ausgeführt hat, aber nicht beim ersten Rendern verwendet wird? Wenn AppContainer diese drei Komponenten rendert, werde ich offensichtlich Requisiten an sie übergeben, damit sie rendern und funktionieren, wie sie erwartet werden. Ich denke, dass hier ein Lebenszyklus verwendet werden könnte. Ich bin mir nur unsicher, was oder wie ich sie verwenden soll. Mein Code für die AppContainer, SearcBar, & CurrentWeather sind unten. CurrentWeather & Forecast sind fast identisch (nur verschiedene Daten von verschiedenen Endpunkten für die API zur Verfügung stellen), also habe ich es nicht zur Verfügung gestellt. Ich habe auch die Aktionen oder Reduzierungen nicht zur Verfügung gestellt, weil ich weiß, dass sie gut funktionieren, bevor ich mich dazu entschloss, diesen Refactor zu versuchen. Vielleicht brauche ich mehr als einen Container, um das durchzuziehen? Jeder Rat oder jede Richtung würde sehr geschätzt werden, danke allen und eine gute Nacht.

** Haben Sie eine Nebenfrage: unter _weatherSearch habe ich event.preventDefault();, weil die SearchBar ein Formularelement ist. Muss ich das überhaupt bereitstellen? Wenn event ist nicht das, was passiert ist, aber die term Ich denke nein. Die event verwendet wird, wie unten in Form Element SearchBar gesehen:

onSubmit={event => getWeather(event.target.value)}

App Container:

import React, { Component } from 'react'; 
import { connect } from 'react-redux'; 
import { fetchCurrentWeather, fetchForecast } from '../actions/actions'; 

import SearchBar from '../components/SearchBar'; 
import CurrentWeather from '../components/CurrentWeather'; 

class AppContainer extends Component { 

    _weatherSearch(term) { 
    event.preventDefault(); 
    // Here is where we go to fetch weather data. 
    this.props.fetchCurrentWeather(term); 
    this.props.fetchForecast(term); 
    } 

    render() { 
    const getWeather = term => {this._weatherSearch(term);}; 
    return (
     <div className="application"> 
     <SearchBar getWeather={getWeather}/> 
     <CurrentWeather /> 
     </div> 
    ); 
    } 
} 

const mapStateToProps = ({ current, forecast }) => { 
    return { 
    current, 
    forecast 
    } 
} 

export default connect(mapStateToProps, 
    { fetchCurrentWeather, fetchForecast })(AppContainer); 

SearchBar:

import React from 'react'; 

const SearchBar = ({ getWeather }) => { 
    return(
    <form className='input-group' onSubmit={event => getWeather(event.target.value)}> 
     <input 
     className='form-control' 
     placeholder='Search a US City' /> 
     <span className='input-group-btn'> 
     <button className='btn btn-secondary' type='submit'>Submit</button> 
     </span> 
    </form> 
); 
} 

export default SearchBar; 

DasaktuelleWetter: * Hinweis: Ich habe nicht entfernt von der Logik oder Datenverarbeitung von CurrentWeather noch so ist es nicht refacto rot zu einer nur zur Präsentation gehörigen Komponente.

import React, {Component} from 'react'; 
import {connect} from 'react-redux'; 
import {unitConverter} from '../conversions/conversions_2.0'; 

export class CurrentWeather extends Component { 
    _renderCurrentWeather(cityData) { 
     const name = cityData.name; 
     const {temp, pressure, humidity} = cityData.main; 
     const {speed, deg} = cityData.wind; 
     const {sunrise, sunset} = cityData.sys; 
     return (
      <tr key={name}> 
       <td>{unitConverter.toFarenheit(temp)} F</td> 
       <td>{unitConverter.toInchesHG(pressure)}"</td> 
       <td>{humidity}%</td> 
       <td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td> 
      </tr> 
     ); 
    } 

    render() { 
     let currentWeatherData = []; 
     if (this.props.current) { 
      currentWeatherData = this.props.current.map(this._renderCurrentWeather); 
     } 
     return (
      <table className="table table-reflow"> 
       <thead> 
        <tr> 
         <th>Temperature</th> 
         <th>Pressure</th> 
         <th>Humidity</th> 
         <th>Wind</th> 
        </tr> 
       </thead> 
       <tbody> 
        {currentWeatherData} 
       </tbody> 
      </table> 
     ); 
    } 
} 

function mapStateToProps({current}) { 
    return {current}; 
} 

export default connect(mapStateToProps)(CurrentWeather); 

Antwort

2

Ihre Renderfunktion ist sehr dynamisch. Sie können alles weglassen, was Sie möchten:

class AppContainer extends Component { 

    _weatherSearch(term) { 
    // event.preventDefault(); We can't do this because we don't have an event here... 

    this.props.fetchCurrentWeather(term); 
    this.props.fetchForecast(term); 
    } 

    render() { 
    const getWeather = term => { this._weatherSearch(term); }; 
    return (
     <div className="application"> 
     <SearchBar getWeather={getWeather}/> 
     { Boolean(this.props.current) && <CurrentWeather /> } 
     </div> 
    ); 
    } 
} 

const mapStateToProps = ({ current }) => ({ current }); 

export default connect(mapStateToProps, 
    { fetchCurrentWeather, fetchForecast })(AppContainer); 

So behandeln Sie fehlende Daten. Sie zeigen entweder nichts oder eine Nachricht, um zuerst zu suchen, oder wenn sie geladen wird, können Sie einen Spinner oder Throbber anzeigen.

Die oben verwendete Methode zum Ausblenden CurrentWeather besteht darin, einen Booleschen Wert an Reagieren zu übergeben, wenn die Komponente verborgen werden soll. Reagiert ignoriert true, false, null und undefined.

Beachten Sie, dass es eine gute Idee ist, nur Daten in mapStateToProps zu übergeben, die Sie tatsächlich in der Komponente selbst verwenden. In Ihrem Code übergeben Sie current und forecast, aber Sie verwenden sie nicht.

Redux wird neu gerendert, wenn sich eine der Daten mapStateToProps, mapDispatchToProps oder props ändert. Wenn Sie Daten zurückgeben, werden Sie niemals Redux zum erneuten Rendern anweisen, wenn es nicht notwendig ist.

2

Ich bin ein react-redux noob selbst :-) und ich bin auf ähnliche Probleme gestoßen.

Soweit ich das beurteilen kann, der Behälter/Präsentations-Trennung Sie gemacht haben sieht gut aus, aber man kann holen und die Behältermontage geht noch einen Schritt weiter und trennt.

Die Lösung, die ich zu beziehen habe, ist, was die Leute „Komponenten höherer Ordnung“ auf verschiedene Weise aufrufen und „Wrapper Komponenten“: (die folgenden Code nicht getestet - es ist nur zu Veranschaulichungszwecken ist)

import {connect} from blah; 

const AppWrap = (Wrapped) => { 
    class AppWrapper extends Component { 
    constructor(props) { 
     super(props); 
     this.state = {foo: false}; 
    } 
    componentWillMount() { 
     this.props.actions.fooAction() 
     .then(() => this.setState({foo: false})); 
    } 
    render() { 
     return (<Wrapped {...this.props} foo={this.state.foo}/>); 
    } 
    } 

    function mapState(state) { blah } 
    function mapDispatch(dispatch) { blah } 

    return connect(mapState, mapDispatch)(AppWrapper); 
} 

export default AppWrap;  

Beachten Sie den = (Wrapped) => { Teil an der Spitze. Das macht das eigentliche "Wrapping", und das Argument kann so lange benannt werden, wie Sie im Render-Hook darauf verweisen.

Jetzt in Ihrem AppContainer, erhalten Sie eine this.props.foo, die als ein Flag zeigt Ihnen, dass fooAction() abgeschlossen hat, und Sie können es verwenden, um Ihre Präsentationskomponenten entsprechend zu rendern. Bis zum Abschluss von fooAction können Sie sicher sein, dass die in AppContainer übergebene foofalse ist.

zu setzen, was ich gerade gesagt habe in Code, könnte Ihr AppContainer etwas wie folgt aussehen:

import AppWrapper from './AppWrapper'; 

class AppContainer extends Component { 
    constructor(props) { 
    super(props); 
    } 
    render() { 
    return (!this.props.foo) ? <div>bar</div> : (
     <div blah> 
     <SearchBar blah/> 
     <CurrentWeather blah/> 
     </div> 
    ); 
    } 
} 

export default AppWrapper(AppContainer); 

Der Vorteil einer Wrapper-Komponente wie folgt zu verwenden ist, dass

  • Sie mehr die Kontrolle übernehmen können über wie und wann genau die Daten gerendert werden
  • Konto für "Laden" Mechanismen und Logik
  • vermeiden skurrile Probleme wiemachen müssenHaken und sich mit the consequences befassen müssen.

Werfen Sie einen Blick auf diesen Blog-Post für weitere Informationen über Hocs: https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

+0

tut Ihr 'AppWrapper' die Logik für die Präsentations-Komponenten enthalten dann und Sie passieren nur' props' zu den untergeordneten Komponenten nach unten wiedergegeben wird in 'AppContainer'? – rockchalkwushock

+0

Ich denke, wo Sie die Logik platzieren, ist eine bevorzugte Sache. Ich dachte entlang der Linien, wo alles genau so war wie vorher, außer dass du die asynchronen Aktionen ausführst, sie in "AppWrapper" steckst und den "Zustand" dieser Aktionen in eine Requisite verwandelst. Zusammenfassung: Wrapper enthält asynchrone Aktionen, Container enthält Logik, Komponenten enthalten jsx. – omul