React + Redux:将表示与数据分离

React + Redux: Separating the presentation from the data

我正在使用 React 和 Redux 构建一个天气应用程序。作为 React 和 Redux 的新手,我决定冒险进入未知领域。我将事情拆分成表示组件和它们各自的容器来处理数据。不过,我在解决这个问题时遇到了一些问题。这可能归结为我是如何尝试去做的,我真的不确定。

现在我有 SearchBarCurrentWeather、和 Forecast 组件以及我正在尝试将这些组件集成到其中的 AppContainer。到目前为止,我已将 SearchBar 组件集成到 AppContainer 中,并且没有任何问题。这是我感到困惑的地方。所以我已经向容器提供了所需的操作和组件,并且容器已经 connected 所以当用户进行搜索时,将进行 api 调用并且状态将通过缩减器更新。

数据应该可以通过 mapStateToProps 现在正确吗?

在用户执行操作后如何使用该数据,但在初始渲染时未使用它?如果 AppContainer 正在渲染这三个组件,我显然会向它们传递道具,以便它们按预期渲染和运行。我在想这是可以使用生命周期的地方我只是不确定使用哪个或如何使用它们。我的 AppContainerSearcBarCurrentWeather 代码如下。 CurrentWeatherForecast 几乎相同(只是为 api 提供来自不同端点的不同数据)所以我没有提供。我也没有提供动作或减速器,因为我知道在我决定尝试此重构之前它们工作正常。也许我需要不止一个容器来完成这个?任何建议或指导将不胜感激,谢谢大家,祝你晚安。

** 有一个附带问题:关于 _weatherSearch 我有 event.preventDefault(); 因为 SearchBar 是一个表单元素。我什至需要提供这个吗?如果 event 不是传递的内容而是 term 我认为不是。 eventSearchBar 的表单元素中的使用如下所示:

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

应用程序容器:

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

搜索栏:

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;

CurrentWeather:*注意:我还没有从 CurrentWeather 中删除任何逻辑或数据处理,因此它还没有被重构为仅展示的组件。

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

您的渲染函数非常动态。你可以省略任何你喜欢的:

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

这就是您处理缺失数据的方式。您要么不显示任何内容,要么先显示要搜索的消息,或者如果它正在加载,您可以显示旋转器或颤动器。

上面用于隐藏的技术 CurrentWeather 是在我们想要隐藏组件时将布尔值传递给 React。 React 忽略 truefalsenullundefined.

请注意,最好只在 mapStateToProps 中传递您实际上将在组件本身内部使用的数据。在您的代码中,您传递了 currentforecast 但您没有使用它们。

Redux 将在任何 mapStateToPropsmapDispatchToPropsprops 数据更改时重新呈现。通过返回数据,您将永远不会使用您指示 Redux 在不需要时重新呈现。

我自己是一个 react-redux 菜鸟 :-) 我遇到过类似的问题。

据我所知,您所做的 container/presentational 分离看起来不错,但您可以更进一步,分离容器的 fetching安装中.

我指的解决方案是人们对 "higher-order components" 和 "wrapper components" 的不同称呼:(以下代码未经测试 - 仅用于说明目的)

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;    

注意顶部的 = (Wrapped) => { 部分。这就是真正的 "wrapping",只要您在渲染挂钩中引用它,参数就可以命名为任何名称。

现在在您的 AppContainer 中,您会得到一个 this.props.foo,它充当一个标志,告诉您 fooAction() 已经完成,您可以使用它相应地呈现您的展示组件。在 fooAction 完成之前,您可以确定传入 AppContainer 的 foo 将是 false.

为了将我刚才所说的转化为代码,您的 AppContainer 可能看起来像这样:

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

像这样使用包装组件的好处是

  • 您可以更好地控制呈现数据的方式和时间
  • 解释 "loading" 机制和逻辑
  • 避免像 dispatches within componentWillMount hooks and having to deal with the consequences.
  • 这样古怪的问题

查看此博客 post 以了解有关 HoC 的更多信息:https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750