【问题标题】:Convert container component implicitly created with `connect()` to explicit container component将使用 `connect()` 隐式创建的容器组件转换为显式容器组件
【发布时间】:2017-03-30 22:37:44
【问题描述】:

我有一个名为Volume 的无状态功能组件,它使用connect() 创建容器组件:

const mapStateToProps = (state) => ({
  volume: state.get('volume')
})

let Volume = (props) => {
  if (props.volume === 'Infinity') {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        Incalculable
      </Text>
    )
  } else {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        {props.volume + ' litres'}
      </Text>
    )
  }
}

Volume.propTypes = {
  volume: React.PropTypes.string
}

Volume = connect(
  mapStateToProps,
  null
)(Volume)

export default Volume

我现在需要在其上实现 componentDidMount 生命周期方法,该方法将运行一个将整个 redux 存储作为参数的函数,然后调度一个操作来更新 store.volume,然后可以将其传递给初始卷要显示的表示组件。所以我想回归基础,不使用connect(),这样我就可以在容器组件中实现生命周期方法。我从来没有用过connect()

这是我的尝试:

import { Text } from 'react-native'
import React, { Component } from 'react'
import { formStyles } from '../../style'
import calcVol from '../../calcVol'
import { updateVolume } from '../../actions/updateDimension.action'

export class VolumeContainer extends Component {
  componentDidMount() {
    const { store } = this.context
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    )
    let litres = calcVol(store)
    store.dispatch(updateVolume(litres))
  }

  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    const { store } = this.context
    return (
      <VolumePresentational volume={store.getState().volume} />
    )
  }
}

VolumeContainer.contextTypes = {
  store: React.PropTypes.object
}

let VolumePresentational = (props) => {
  if (props.volume === 'Infinity') {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        Incalculable
      </Text>
    )
  } else {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        {props.volume + ' litres'}
      </Text>
    )
  }
}

VolumePresentational.propTypes = {
  volume: React.PropTypes.string
}

const styles = {
  text: {
    marginTop: 20,
    fontSize: 30,
    fontWeight: 'bold'
  }
}

export default VolumeContainer

代码进入了我的 volCalc(store) 函数,其中出现错误:

state.get 不是函数

所以我在 componentDidMount 中传递给 calcVol() 的商店一定不是商店。

我做错了什么?

【问题讨论】:

  • 我不明白你为什么要将一些商店数据拉入组件(volume),当组件挂载时在那里修改,然后将其发送回商店? calcVol 函数在做什么?
  • @AlexYoung calcVol 计算给定商店的交易量。该应用程序非常面向数学,因此商店仅包含执行计算所需的东西。我想在应用程序首次启动时进行初始计算,这将计算并显示音量

标签: reactjs react-native redux react-redux


【解决方案1】:

一般来说,如果某些东西可以完全从状态中计算出来(即它是该状态的函数),那么它不应该处于状态本身。比如这样的状态:

{
    x: 3,
    y: 2,
    z: 5,
    volume: 30
}

这会导致状态内的信息重复,并最终导致各种重要的问题,以保持事物彼此同步。 React 和 Redux 的共同主题应该是拥有“单一事实来源”,即信息存在于一个地方且仅存在于一个地方。在上面的示例中,有关音量的信息存储在 volume 属性中,但也存储在 xyz 属性的组合中 - 所以现在有两个真实来源。

就将这种方法应用于应用程序而言,最好在存储中尽可能保留最简单的数据形式,并将这些数据组合到返回我们需要的聚合数据的选择器函数中。

以上面的例子,我们会:

const state = {
    x: 3,
    y: 2,
    z: 5
}

还有一个选择器函数来计算音量:

const selectVolume = state => state.x * state.y * state.z;

如果计算的计算量很大,那么我们可以memoize选择器函数来避免重复计算相同的数据:

const makeSelectVolume = () => {
    const memo = {};
    return state => {
        const (x, y, z} = state;
        // if we have memoized a value for these parameters return it
        if( x in memo && y in memo[x] && z in memo[x][y] ) {
            return memo[x][y][z];
        }
        // otherwise calculate it
        const volume = x * y * z;
        // and memoize it
        memo[x][y][z] = volume;
        return volume;
    }
}

幸运的是,优秀的库 reselect 自动创建了我们可以与 redux 一起使用的记忆选择器,所以我们不需要自己去记忆它们。通过重新选择,我们将使我们的选择器函数如下:

const makeSelectVolume = () => createSelector(
    state => state.x,
    state => state.y,
    state => state.z,
    (x, y, z) => x * y * z
};

现在我们只需要将它与我们的组件集成:

// other imports as usual
import { makeSelectVolume } from 'path/to/selector';

const Volume = (props) => {
  if (props.volume === 'Infinity') {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        Incalculable
      </Text>
    )
  } else {
    return (
      <Text
        style={{ ...formStyles.text, ...styles.text }}>
        {props.volume + ' litres'}
      </Text>
    )
  }
}

Volume.propTypes = {
  volume: React.PropTypes.string
}

const mapStateToProps = createStructuredSelector({
    volume: makeSelectVolume()
});

export default connect(
  mapStateToProps
)(Volume);

在您的情况下,只需将选择器中的体积计算逻辑替换为您的 calcVol 函数。请注意,calcVol 在这里必须是一个纯函数,即它不会以任何方式修改状态。

编辑

如果我们的状态是一个具有结构的 ImmutableJS 映射:

{
  widths: {
    width1: 2,
    width2: 7,
  },
  heights: {
    height1: 4,
    height2: 3
  }
}

我们可以编写一个选择器函数:

const makeSelectVolume = () => createSelector(
    state => state.get('widths'),
    state => state.get('heights'),
    (widths, heights) => calculateSomething(widths.toJS(), heights.toJS())
};

或作为:

const makeSelectVolume = () => createSelector(
        state => state.get('widths').get('width1'),
        state => state.get('widths').get('width2'),
        state => state.get('heights').get('height1'),
        state => state.get('heights').get('height2'),
        (width1, width2, height1, height2) => calculateSomething({ width1, width 2 },{ height1, height2 })
    };

如果您选择第二个,那么 memoization 将正常工作,但如果您选择第一个,您可能需要使用重新选择的 createSelectorCreator 来实现自定义相等检查器。

基本上重新选择检查是否有任何组合选择器的值发生了变化,默认情况下使用===(只有当它们在内存中是同一个对象时才返回true)。如果您从组合选择器返回不可变映射,那么您将需要检查映射在它们的值方面是否相同,例如使用不可变映射的equals 方法:

import { createSelectorCreator, defaultMemoize } from 'reselect';

const createImmutableMapSelector = createSelectorCreator(
    defaultMemoize,
    (a, b) => a.equals(b)
);

const makeSelectVolume = () => createImmutableMapSelector(
    state => state.get('widths'),
    state => state.get('heights'),
    (widths, heights) => calculateSomething(widths.toJS(), heights.toJS())
};

这会带来更多的复杂性,但利用了 Immutables 深度比较的快速性。不过,最终任何一种方式都会奏效。

【讨论】:

  • 谢谢,这看起来很棒。我的状态是一个 immutablejs 地图。为了从状态中获取我的高度,我会使用state.get('height').get('height1'),因为它具有嵌套高度。我会以这种方式将高度直接转移到您的示例中来代替 state.x 吗?
  • 几乎是的,但是您可以选择构成整体的各个选择器的粒度以最大限度地提高记忆效率 - 我将使用一些代码编辑我的答案。
  • 完成并开始工作。美丽的。我可以完全移除我的音量减小器。以前每次发送宽度、长度、厚度动作时,我都必须使用 redux-thunk 调用 calcVol(state),而不再需要了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-10
  • 2014-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
相关资源
最近更新 更多