React Redux - 如何使用选择器在 mapStateStateToProps 中派生并聚合数据?

React Redux - how to derive and then aggregate data in mapStateStateToProps using a selector?

我有一个组件 return 是一个名为 Progress 的对象,其中有一个名为 Results 的数组。该数组包含具有各种属性的对象,其中之一称为 total

{ 
  Progress: {
    count: 100,
    results: [
     {total: 4, ...}, 
     {total: 10, ...},
     ...
    ]
  }
}

组件 Dashboard 从状态获取数据并将其映射到进度 属性。

 export class Dashboard extends Component {

    static propTypes = {
      progress: PropTypes.object.isRequired,
      getProgress: PropTypes.func.isRequired,
      totalResults: PropTypes.number.isRequired
    }

    componentDidMount() {
      this.props.getProgress()
    }

    ...
  }

  const selectProgress = state => state.progressReducer.progress

  const mapStateToProps = state => ({
    progress: selectProgress(state),
  })

  export default connect(mapStateToProps, { getProgress })(Dashboard)

我现在遇到的问题是如何添加一个新的 属性 派生自进度?

我知道我需要使用选择器,但我看不到 where/how 来执行此操作。

例如,我知道我可以像这样做一些琐碎(而且毫无意义)的事情:

const mapStateToProps = state => ({
  progress: selectProgress(state),
  count: selectProgress(state).count
})

它向组件添加了另一个 属性 count(是的,它只是复制了 属性 内部进度,因此它毫无意义)。

我需要做的是这样的:

const mapStateToProps = state => ({
  progress: selectProgress(state),
  resultsTotal: <loop through the results array and sum the property total>
})

1 - 我试过的方法

我试过这个,尽管我知道它不应该是这样的。这是为了说明我正在尝试做的事情 - 在我取得进展之后,将它传递给某个函数来计算总数,然后 return 作为 属性 到组件:

const selectResults = progress => {
  progress.results.reduce((acc, result) => {
    acc + result.total
  }, 0)
}

const mapStateToProps = state => ({
  progress: selectProgress(state),
  totalResults: selectResults(progress)
})

2 - 我试过的方法

我认为这会奏效,基本上让渲染视图在 JSX 中需要的点调用函数:

export class Dashboard extends Component {
  static propTypes = {
    progress: PropTypes.object.isRequired,
    getProgress: PropTypes.func.isRequired,
  }

  componentDidMount() {
    this.props.getProgress()
  }

  totalResults() {
    if (this.props.progress.results)
    return this.props.progress.results.reduce((acc, result) => {
      acc + result.total
    }, 0)
  }

  render() {
    ...
      <SummaryCard title='Students' value={this.totalResults()} />
    ...
  }
}

我现在想知道为什么这行不通 - 我不得不添加这一行:

if (this.props.progress.results)

因为执行此函数时进度当然是空的(即我猜是因为它在组件首次安装时执行,并且存储尚未 return 编辑数据)。

mapStateToProps 是一个函数。目前你正在使用一个简短的版本来立即 return 一个对象,但你可以将它作为一个复杂的函数,并且 return 最后一个对象:

const mapStateToProps = state => {
    const progress = selectProgress(state);
    return {
        progress,
        totalResults: progress !== undefined ? selectResults(progress) : undefined
    }
}

我发现这个问题的一个解决方案是使用优秀的 reselect 库。来自他们的 github 页面:

  • 选择器可以计算派生数据,允许 Redux 存储 最小的可能状态。
  • 选择器是高效的。选择器不会重新计算,除非它的参数之一发生变化。
  • 选择器是可组合的。它们可以用作其他选择器的输入。

所以我必须创建第二个选择器并将其链接到第一个选择器。

您可以在下面看到 progressSelector 会将其结果(progress 数据)传递到链中的下一个选择器 (totalResultsSelector):

这是完整的组件:

import React, { Component } from 'react';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect'
import { getProgress } from '../../actions/progress';

import SummaryCard from '../SummaryCard';
import { People } from 'react-bootstrap-icons';
import { Book } from 'react-bootstrap-icons';
import { Award } from 'react-bootstrap-icons';
import styles from './styles.scss';

export class Dashboard extends Component {
  static propTypes = {
    progress: PropTypes.object.isRequired,
    getProgress: PropTypes.func.isRequired,
    totalResults: PropTypes.number.isRequired
  }

  componentDidMount() {
    this.props.getProgress()
  }

  render() {
    return (
      <div className={styles.wrapper}>
        <Row xs={1} sm={3}>
          <Col>
            <SummaryCard title='Students' value={this.props.totalResults} icon={<People />} />
          </Col>
          <Col>
            <SummaryCard title='Courses' value={this.props.progress.count} icon={<Book />} />
          </Col>
          <Col>
            <SummaryCard title='Certified' value='0' icon={<Award />} />
          </Col>
        </Row>
      </div>
    )
  }
}

const progressSelector = state => state.progressReducer.progress

const totalResultsSelector = createSelector (
  progressSelector,
  progress => {
    if (!progress.results) return 0
      const total = progress.results.reduce((acc, result) => {
        return acc + result.total
      }, 0)
    return total
  }
)

const mapStateToProps = state => ({
  progress: progressSelector(state),
  totalResults: totalResultsSelector(state)
})

export default connect(mapStateToProps, { getProgress })(Dashboard)