在模型状态下记录 redux 导航状态?

Posted

技术标签:

【中文标题】在模型状态下记录 redux 导航状态?【英文标题】:Recording redux navigation state in model state? 【发布时间】:2019-10-30 04:06:30 【问题描述】:

我已经构建了我的 redux 应用程序,以便在状态树的不同分支上处理我的数据模型。

concerts, venues

我还使用 react-navigation-redux-helpers 将导航状态放入树中:

concerts, venues, nav

但是,我想记录有关特定模型的可见性状态的信息。当 ConcertScreen 显示时,我想知道用户何时查看/停止查看特定 Concert ID(并让服务器知道),最终目标是测量特定 Concert ID 在屏幕上可见的时间。

我通过将Navigation/NAVIGATENavigation/RESETNavigation/BACK 的分支添加到concerts reducer 并在concerts 下的适当对象上设置visible: true 来完成此操作。

这很容易出错,因为导航状态可以通过这些特定操作以外的操作进行修改。 (例如,由 nav reducer 直接处理的注销操作。)

我看到了两种选择,都不太理想:

    使用 props.navigation.addListener 在 ConcertScreen 上监听焦点和模糊事件,触发自定义 concertFocused/concertBlurred 操作,并在我的 Concert reducer 中处理这些操作,而不是 Navigation/* 操作。

    创建一个选择器,从 nav 状态计算当前可见的 Concert,并重构期望 concert.visible 作为输入的业务逻辑以改用选择器。

1 的问题似乎是它增加了事件循环的开销,所有额外的动作都意味着额外的渲染开销。

2 避免了额外的操作,但似乎进行了很多重构却没有太多收获,这意味着我必须将业务逻辑从 Concert reducer 中移出并放在其他地方。

假设我使用选项 2。我添加了一个中间件,在任何操作中,该中间件将选择器应用于 state.nav 并从中计算 Concert 当前显示的内容。如果我想测量持续时间,我将如何存储开始/结束时间?使用添加的数据触发一个新操作,以便 Concert reducer 捕获它?这看起来就像带有额外步骤的选项 1。

可以也让这个中间件向每个动作添加一个字段来指示 Concert 显示状态,并让 Concert reducer 在默认/失败情况下处理它。人们会这样做吗?

【问题讨论】:

如果我遇到同样的情况,我会使用你的解决方案2。 @FatihMertDoğancan,选项 2 最终看起来像选项 1,但有额外的步骤(更新了问题)。 我的胆量感觉会与解决方案 2 一致。但是这个描述很模糊,我明白你的意图,但不确定你是如何实现这个想法的。一些代码会很好。 创建一个专用的包装器组件(可能是 HOC)怎么样,而不依赖于导航或任何与 redux 相关的操作?在此组件中,您将拥有一个监听器,它将仅跟踪模型实例(音乐会 1、场地 1 等)的可见屏幕时间,并且在 componentDidUnmount 上,您将在 Store 中保留/调度可见时间。你怎么看?如果适合您,我可以将其分解为答案。 @frank,您能否查看我的详细答案并提供反馈/问题(如果有)?谢谢:) 【参考方案1】:

我会以这样一种方式处理您的用例,以便我将充分利用这两种解决方案。

首先,调度了许多动作,您担心渲染开销。使用选择器库,比如reselect,库记忆将防止不必要的组件重新渲染。

稍后,如果我对您的理解正确,您的目标是让服务器知道项目(音乐会)的可见性状态以及最终的可见时间。如果您的目标是通知服务器,而不让其他应用程序的前端用户知道,为什么还要在 Redux 中继续跟踪它?你可以跳过 Redux 部分,只向服务器发送更新。

假设您需要 Redux 进行跟踪。正如您已经提到的,您可以尝试构建 Store,将 visible 标志添加到 Redux 存储中的每个对象。但是如果您的项目结构足够大并且每次更改 visible 标志时复制和更新对象的成本很高,您可以考虑创建一个专用的 Store 分支和减速器,它只负责跟踪需求。类似的东西:

tracking : 
  concerts: 
    1:  visible: true, time: 10 
  

现在,更新项目的标志,只需复制和修改上述微小结构。甚至,您可以针对特定的项目类型 (trackingConcerts) 使其更小、更具体。

* 请记住,这样一个专门的商店分支是否会提高性能由您自己决定,因为我们不知道您的详细架构和商店细节。

继续解决方案...

正如您所提到的,依赖导航操作 + 中间件很容易出错。如果您有一个通用的组件页面(即,将调度具有通用名称的导航操作),但您在其中呈现您的一个项目(音乐会),那该用例又如何呢?同样渲染一个项目,总是与修改中间件中的映射逻辑或通过操作名称跟踪项目的任何位置相结合。另一个棘手的情况是当您在一个页面上呈现不同类型的项目(音乐会、场地)时。考虑到您只有 1 个导航项,您将如何区分和跟踪这些项目?同样在这样的设置中,我看不到处理项目可见时间的简单方法。

关于选择器作为解决方案 - 它们只能是解决方案的一小部分。选择器负责选择和管理派生状态。仅此而已。

请给我代码。

我将围绕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
  

【讨论】:

以上是关于在模型状态下记录 redux 导航状态?的主要内容,如果未能解决你的问题,请参考以下文章

在 redux 状态更改后,React 导航不会更改导航屏幕

redux 状态变化时如何导航?

每次使用反应路由器导航到redux组件时,如何阻止状态存储数据在redux组件中累积

React s-s-r路由器-导航到子路由时的redux状态丢失

在 react redux 应用程序中手动导航时状态丢失

React-navigation-redux-helpers 错误:此导航器同时具有导航和容器道具,因此不清楚它是不是应该拥有自己的状态