将使用 `connect()` 隐式创建的容器组件转换为显式容器组件
Convert container component implicitly created with `connect()` to explicit container component
我有一个名为 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 is not a function
所以我传递给 componentDidMount 中的 calcVol() 的商店不能是商店。
我做错了什么?
一般来说,如果某物可以完全根据状态计算(即它是该状态的函数),那么它不应该处于状态本身。比如一个状态如:
{
x: 3,
y: 2,
z: 5,
volume: 30
}
这会导致状态中的信息重复,并最终导致各种与保持事物同步的重要问题。贯穿 React 和 Redux 的共同主题应该是 "single source of truth",即信息存在于一处且仅存在于一处。在上面的示例中,有关音量的信息存储在 volume
属性 中,但也存储在 x
、y
和 z
属性的组合中- 所以现在有两个真相来源。
就将此方法应用于应用程序而言,最好在存储中保留最简单的数据形式,并在选择器函数中组合该数据,return 我们需要的聚合数据.
以上面的例子为例,我们将有:
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
来实现自定义相等性检查器。
本质上,重新选择检查是否有任何组成的选择器值已更改,默认情况下使用 ===
(只有 return 如果它们实际上是内存中的同一对象,则为真)。如果您 return 来自组合选择器的不可变映射,那么您需要检查这些映射的值是否相同,例如使用不可变映射的 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 深度比较的快速性。最终无论哪种方式都可以。
我有一个名为 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 is not a function
所以我传递给 componentDidMount 中的 calcVol() 的商店不能是商店。
我做错了什么?
一般来说,如果某物可以完全根据状态计算(即它是该状态的函数),那么它不应该处于状态本身。比如一个状态如:
{
x: 3,
y: 2,
z: 5,
volume: 30
}
这会导致状态中的信息重复,并最终导致各种与保持事物同步的重要问题。贯穿 React 和 Redux 的共同主题应该是 "single source of truth",即信息存在于一处且仅存在于一处。在上面的示例中,有关音量的信息存储在 volume
属性 中,但也存储在 x
、y
和 z
属性的组合中- 所以现在有两个真相来源。
就将此方法应用于应用程序而言,最好在存储中保留最简单的数据形式,并在选择器函数中组合该数据,return 我们需要的聚合数据.
以上面的例子为例,我们将有:
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
来实现自定义相等性检查器。
本质上,重新选择检查是否有任何组成的选择器值已更改,默认情况下使用 ===
(只有 return 如果它们实际上是内存中的同一对象,则为真)。如果您 return 来自组合选择器的不可变映射,那么您需要检查这些映射的值是否相同,例如使用不可变映射的 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 深度比较的快速性。最终无论哪种方式都可以。