我已经对redux应用程序进行了结构设计,以便在状态树的单独分支上处理数据模型。
{concerts, venues}
我还使用了react-navigation-redux-helpers将我的导航状态放到树中:
{concerts, venues, nav}
但是,我想记录有关特定模型可见性状态的信息。当显示ConcertScreen时,我想知道用户何时查看/停止查看特定的Concert ID(并通知服务器),最终目标是测量特定的Concert ID在屏幕上可见的时间。
我已通过在Concerts缩减程序中添加Navigation/NAVIGATE
,Navigation/RESET
和Navigation/BACK
的分支,并在Concert下的适当对象上设置visible: true
来做到这一点。
这很容易出错,因为可以通过除这些特定动作以外的动作来修改导航状态。 (例如,注销操作直接由nav reducer处理。)
我看到两种选择,两者都不理想:
使用props.navigation.addListener
来监听ConcertScreen上的焦点和模糊事件,触发自定义concertFocused
/ concertBlurred
动作,并在我的Concert简化器中代替{{ 1}}操作。
创建一个选择器,以从Navigation/*
状态计算当前可见的演唱会,并重构期望将nav
作为输入的业务逻辑,以使用选择器。
1的问题似乎是它增加了事件循环的开销,所有多余的动作都意味着额外的渲染开销。
2避免了额外的操作,但是似乎要进行大量重构而不是获得很多收益,这意味着我必须将业务逻辑移出concert reducer并放在其他地方。
说我使用选项2。我添加了一个中间件,该中间件可以在任何操作上将选择器应用于concert.visible
,并从中计算当前显示的Concert。如果我想测量持续时间,我将如何存储开始/结束时间?使用添加的数据执行新操作,以便Concert Reducer捕捉到它?这似乎像带有额外步骤的选项1。
我也可以使用此中间件向每个操作中添加一个字段,指示演唱会的显示状态,并让Concert Reducer在默认/失败情况下进行处理。人们这样做吗?
答案 0 :(得分:0)
我将以这种方式处理您的用例,以便我能最好地利用这两种解决方案。
首先,要调度许多动作,您担心会产生开销。使用选择器库(假设为reselect),库的备忘将防止不必要地重新呈现组件。
稍后,如果我对您的理解正确,那么您的目标是让服务器知道某个项目(音乐会)的可见性状态以及最终的可见时间。如果您的目标是仅通知服务器 ,而又不让其他应用程序的前端用户知道,那么为什么还要在Redux中继续跟踪它呢?您可以跳过Redux部分,仅将更新发送到服务器。
让我们假设您需要Redux进行跟踪。如您已经提到的,您可以尝试构建存储的方式,将visible
标志添加到Redux存储中的每个对象。但是,如果您商品的结构足够大,并且每次更改visible
标志时复制和更新对象的成本很高,则可以考虑创建专用的Store分支和reducer,这仅负责跟踪需求。像这样:
tracking : {
concerts: {
1: { visible: true, time: 10 }
}
}
现在,更新项目的标志,只需复制和修改上面的微小结构。甚至,您可以针对特定的项目类型(trackingConcerts
)使其更小,更具体。
* 请记住,由于我们不了解您的详细架构和商店详细信息,因此由您自己决定是否建立专门的Store分支会提高性能。
继续解决方案...
如前所述,依赖导航操作+中间件容易出错。关于用例,您有一个通用的组件页面(即,将分派具有通用名称的导航操作),但是在那里呈现了一个项目(音乐会)?同样渲染项目,将总是与修改中间件中的映射逻辑或您通过操作名称跟踪项目的任何位置结合在一起。另一个棘手的情况是当您在一页上呈现不同类型的项目(音乐会,场所)时。考虑到您只有一个导航项目,您将如何区分和跟踪这些项目?同样在这样的设置中,我没有看到一种处理项目可见时间的简单方法。
关于选择器作为解决方案-它们只是解决方案的一小部分。选择器负责选择和管理派生状态。没什么。
请给我看看代码。
我将在react on screen(或跟踪组件可见性的任何类似库)周围创建包装器组件,并仅实现跟踪组件的可见时间。
当组件可见性状态更改时,包装器将触发回调,并在componentDidUnmount
上触发包括可见时间的回调。
仅此而已!现在,您可以在这些回调上附加处理程序,并且可以更新Redux和/或通知服务器可见性更改,而无需依赖任何导航操作和中间件。
用法:
const App = () => (
<Tracking
onVisibilityChange={isVisible => {}}
onUnmount={visibleSeconds => {}}
>
<Concert id={1} />
</Tracking>
)
跟踪包装:
import TrackVisibility from 'react-on-screen'
const Tracking = ({ children, libraryProps, ...rest }) => (
<TrackVisibility {...libraryProps}>
<TrackingCore {...rest}>
{children}
</TrackingCore>
</TrackVisibility>
)
TrackingCore,我们的自定义跟踪逻辑:
class TrackingCore extends React.Component {
constructor (props) {
super(props)
this.state = {
visibleSeconds: 0,
interval: null
}
}
componentDidMount() {
this.track()
}
componentWillReceiveProps (nextProps) {
this.track(nextProps)
}
componentDidUnmount() {
const { visibleSeconds, interval } = this.state
const { onUnmount } = this.props
onUnmount(visibleSeconds)
clearInterval(interval)
}
track (nextProps) {
const { isVisible, onVisibilityChange } = this.props
const { visibleSeconds, interval } = this.state
const hasVisibilityChanged = (isVisible !== nextProps.isVisible) || !nextProps
const isVisibleValue = nextProps ? nextProps.isVisible : isVisible
// On visibility change, invoke the callback prop
if (hasVisibilityChanged) {
onVisibilityChange(isVisibleValue)
// If it becomes visible, start counting the `visibleSeconds`
if (isVisibleValue) {
this.setState({
interval: setInterval(() => this.setState({
visibleSeconds: visibleSeconds + 1
}), 1000)
})
} else {
clearInterval(interval)
}
}
}
render () {
return this.props.children
}
}