我有一个名为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
,然后可以传递到要显示的初始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不是函数
所以我在storeDidMount中传入calcVol()的商店一定不能成为商店。
我做错了什么?
答案 0 :(得分:1)
一般来说,如果可以完全从状态计算某些东西(即它是该状态的函数),那么它本身不应该处于状态。例如,状态如:
{
x: 3,
y: 2,
z: 5,
volume: 30
}
这导致状态内的信息重复,并最终导致各种非平凡的问题,使事物彼此保持同步。 React和Redux的共同主题应该是“单一的事实来源”,即信息只存在于一个地方和一个地方。在上面的示例中,有关卷的信息存储在volume
属性中,但也存储在x
,y
和z
属性的组合中,因此存在现在有两个真相来源。
在将此方法应用于应用程序方面,最好在商店中保留最简单的数据形式,并将这些数据合并到返回我们需要的聚合数据的选择器函数中。
以上面的例子为例,我们有:
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将正常工作,但如果您选择第一个,您可能需要使用reselect的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深度比较的快速性。最终,无论哪种方式都可行。